2

我有一个基于 ring/compojure 的 Web API,我需要能够根据启动标志或将参数传递给请求来选择性地打开和关闭缓存(或任何标志)。

我尝试将标志设置为动态变量:

(def ^:dynamic *cache* true)

(defmacro cache [source record options & body]
  `(let [cachekey# (gen-cachekey ~source ~record ~options)]
     (if-let [cacheval# (if (and (:ttl ~source) ~*cache*) (mc/generic-get cachekey#) nil)]
       cacheval#
       (let [ret# (do ~@body)]
         (if (and (:ttl ~source) ~*cache*) (mc/generic-set cachekey# ret# :ttl (:ttl ~source)))
         ret#))))

...但这仅允许我更新绑定块中的标志,这对于包装每个数据获取功能并不理想,并且不允许我在启动时选择性地设置标志

然后我尝试在原子中设置标志,这允许我在启动时设置标志,如果某个参数被传递给请求,则可以轻松更新标志,但更新将更改所有线程的标志和不仅仅是特定请求的标志。

在 Clojure 中做这样的事情最惯用的方法是什么?

4

1 回答 1

3

首先,*cache*在宏定义中取消引用意味着它的编译时值将包含在编译输出中,并且在运行时重新绑定它不会产生任何影响。如果您希望在运行时查找该值,则不应取消引用*cache*

至于实际问题:如果您希望各种数据获取功能对缓存设置做出反应,无论如何您都需要以某种方式将其传达给它们。此外,还有两个独立的关注点:(1)计算相关的标志值,(2)使它们可供处理程序使用,以便它可以将它们传达给关心的函数。

计算标志值并使它们可用于主处理程序

对于基于每个请求的决策,检查一些传入的参数和设置,您可能希望使用一个中间件来确定各种标志的正确值并将assoc它们放到请求映射中。这样,位于该中间件下游的处理程序将能够在请求映射中查找它们,而无需知道它们是如何计算的。

您当然可以安装多个中间件,每个中间件负责计算一组不同的标志。

如果您确实使用中间件,您可能希望它处理默认值。在这种情况下,下面关于动态变量的部分中关于在启动时设置默认值的说明可能不相关。

最后,如果应用程序级别(全局的、线程独立的)默认值可能在运行时发生变化(可能是“关闭所有缓存”请求的结果),您可以将它们存储在 Atoms 中。

将标志值传递给关心的函数

第一种方法:动态变量

完成此操作后,您必须将标志传达给实际执行与标志相关的操作的函数;这里动态变量和显式参数是最自然的选择。

使用动态 Var 意味着您不必为每个涉及此类函数的函数调用显式地执行此操作;相反,您可以按请求执行一次,例如。在启动时安装默认值也是很有可能的;例如,您可以使用alter-var-root它。(或者您可以简单地根据从环境中获得的信息来定义 Var 的初始值。)

注意。如果您在一个块的范围内启动新线程binding,它们将不会自动看到该binding块安装的绑定——您必须安排它们被传输。该bound-fn宏对于创建自动处理此问题的函数很有用;详情见(doc bound-fn)

使用带有下面描述的所有标志的单个地图的想法在这里也很重要,如果为了合理的方便可能不是同样必要的话;本质上,您将使用单个动态 Var 而不是多个。

第二种方法:显式参数和标志映射

另一个自然的选择是简单地将任何相关标志传递给需要它们的函数。如果您在映射中传递所有标志,您可以在单个映射中组合与请求相关的所有选项并将其传递给所有标志感知函数,而无需关心任何给定函数需要哪些标志(因为每个函数将简单地检查它关心的旗帜的地图,不考虑其他旗帜)。

使用这种方法,您可能希望将数据获取功能拆分为从缓存中获取值的函数、从数据存储中获取值的函数和调用其他两个函数之一的标志感知函数,具体取决于关于标志值。例如,您可以通过这种方式单独测试它们。(虽然如果个别功能真的完全微不足道,我会说一开始只创建夺旗版本是可以的;只要记住要考虑到在开发过程中变得更复杂的任何部分。)

于 2013-07-15T00:23:15.543 回答