惯用的方式设置&更新Clojure命名空间标志?

时间:2013-07-15 00:03:01

标签: clojure ring

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

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

(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中做这样的事情最惯用的方式是什么?

1 个答案:

答案 0 :(得分:3)

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

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

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

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

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

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

最后,如果应用程序级别(全局,线程无关)默认值可能在运行时更改(可能是“关闭所有缓存”请求的结果),则可以将它们存储在Atoms中。

将标志值传递给关心

的函数

第一种方法:动态Vars

一旦你这样做,你就必须将标志传递给实际执行标志相关操作的函数;这里动态Vars和显式参数是最自然的选择。

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

NB。如果在binding块的范围内启动新线程,它们将不会自动看到此binding块安装的绑定 - 您必须安排它们进行传输。 bound-fn宏对于创建自动处理此功能的函数很有用;有关详细信息,请参阅(doc bound-fn)

使用带有下述所有标志的单个地图的想法在这里也是相关的,如果为了合理的方便可能不是同样必要的;实质上,你将使用单个动态Var而不是许多。

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

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

使用这种方法,您可能希望将数据提取功能拆分为函数以从缓存中获取值,从数据存储中获取值的函数以及调用其中一个的标记感知函数另外两个取决于标志值。这样,您可以单独测试它们。 (虽然如果单个函数真的完全无关紧要,我会说最初只创建一个标记版本是可以的;只记得在开发过程中分解出任何变得更复杂的部分。)