我使用绑定有两个原因:
- 运行覆盖常量或其他符号的其他值的测试
- 使用“全局”资源,例如数据库连接或消息代理通道
测试
我正在开发一个分布式系统,该系统具有多个组件,这些组件通过消息交换发送消息来进行通信。这些交易所具有全局名称,我将其定义如下:
(ns const)
(def JOB-EXCHANGE "my.job.xchg")
(def CRUNCH-EXCHANGE "my.crunch.xchg")
;; ... more constants
这些常量在许多地方被用来将消息发送到正确的地方。为了测试我的代码,我的部分测试套件运行使用实际消息交换的代码。但是,我不希望我的测试干扰实际系统。
为了解决这个问题,我将我的测试代码包装在一个binding
覆盖这些常量的调用中:
;; in my testing code:
(binding [const/CRUNCH-EXCHANGE (str const/CRUNCH-EXCHANGE (gensym "-TEST-"))
const/CRUNCH-TASK-QUEUE (str const/CRUNCH-TASK-QUEUE (gensym "-TEST-"))]
;; tests here
)
在这个binding
函数内部,我可以调用任何使用常量的代码,它会使用被覆盖的值。
使用全球资源
我使用绑定的另一种方法是“修复”特定范围内的全局或单例资源的值。这是我编写的 RabbitMQ 库的示例,其中我将 RabbitMQ 的值绑定Connection
到符号*amqp-connection*
,以便我的代码可以使用它:
(with-connection (make-connection opts)
;; code that uses a RabbitMQ connection
)
的实现with-connection
非常简单:
(def ^{:dynamic true} *amqp-connection* nil)
(defmacro with-connection
"Binds connection to a value you can retrieve
with (current-connection) within body."
[conn & body]
`(binding [*amqp-connection* ~conn]
~@body))
我的 RabbitMQ 库中的任何代码都可以使用该连接*amqp-connection*
并假定它是有效的、打开的Connection
. 或者使用该(current-connection)
函数,当您忘记将 RabbitMQ 调用包装在 a 中时,该函数会引发描述性异常with-connection
:
(defn current-connection
"If used within (with-connection conn ...),
returns the currently bound connection."
[]
(if (current-connection?)
*amqp-connection*
(throw (RuntimeException.
"No current connection. Use (with-connection conn ...) to bind a connection."))))