我正在尝试在 clojure 中创建一个模块化应用程序。
假设我们有一个博客引擎,它由两个模块组成,例如 - 数据库模块和文章模块(存储博客文章的东西),所有模块都带有一些配置参数。
所以 - 文章模块依赖于存储,并且拥有文章模块和数据库模块的两个实例(具有不同的参数)允许我们在两个不同的数据库中托管两个不同的博客。
我试图实现这一点,为每个已初始化的模块即时创建新的命名空间,并在这个命名空间中定义具有部分应用参数的函数。但我认为这种方法是某种黑客行为。
这样做的正确方法是什么?
我正在尝试在 clojure 中创建一个模块化应用程序。
假设我们有一个博客引擎,它由两个模块组成,例如 - 数据库模块和文章模块(存储博客文章的东西),所有模块都带有一些配置参数。
所以 - 文章模块依赖于存储,并且拥有文章模块和数据库模块的两个实例(具有不同的参数)允许我们在两个不同的数据库中托管两个不同的博客。
我试图实现这一点,为每个已初始化的模块即时创建新的命名空间,并在这个命名空间中定义具有部分应用参数的函数。但我认为这种方法是某种黑客行为。
这样做的正确方法是什么?
“模块”是一个名词,就像史蒂夫·耶格的“名词王国”一样。
尽可能地坚持参数(动词)的无副作用或纯函数,除了抽象的最顶层。您可以随心所欲地组织这些功能。在最顶层,您将拥有一些应用程序状态,有很多方法可以管理它,但我使用最多的一种是将这些顶级服务隐藏在 clojure 协议下,然后在 clojure 记录中实现它(可能包含对数据库连接或类似的引用)。
这种方法最大限度地提高了灵活性,并防止您将自己写到角落里。它类似于 java 的依赖注入。Stuart Sierra 最近在 Clojure/West 2013 上就这些主题做了一场精彩的演讲,但视频尚未发布。
请注意与您的方法的区别。您需要将对象的管理和解析与其生命周期分开。将它们绑定到命名空间可以快速访问,但这意味着您作为使用该代码的客户端编写的任何函数现在都在访问全局状态。使用协议,您可以将全局状态的实现细节与访问接口分开。
如果你需要一个激励性的例子来说明为什么这很有用,请考虑一下,你将如何拦截对全局可访问的服务的所有访问?好吧,您将下推完整实现并将入口点作为包装函数,而不是将相关细节推到更靠近客户端代码的位置。如果您想要代码的某些客户端而不是其他客户端的某些行为怎么办?现在你被困住了。这只是预期先发制人地做出那些不可避免的权衡,让你的生活更轻松。