如何摆脱Compojure应用程序中的全局数据

时间:2013-03-24 15:41:34

标签: clojure compojure

http://mindbat.com/2013/03/clojurewest-2013-day-one-notes/上有一条注释:

  • def'ing refs和顶层的原子基本上是通过单身的全局可变状态,请避免
  • 建议使用构造函数返回您想要使用的状态变量,然后将该状态传递给每个函数

我认为这是一个很好的建议,但我不完全确定如何在Ring / Compojure应用程序中实现它。任何人都可以举一个具体的例子说明这是如何运作的吗?

我特别感兴趣的是如何将defroutesinitapp组合在一起,并摆脱该范围内的全局变量。

2 个答案:

答案 0 :(得分:5)

我从斯图亚特的演讲中理解的是这样的:

(ns state.core)

(defn create-user-module [] (atom []))

(defn add-user [module user]
  (swap! module conj user))

(defn get-users [module]
  @module)

现在你的“核心”中没有全局状态,因为操纵状态的函数希望将它作为参数。这些允许轻松测试,因为您可以为每个测试创建“用户模块”的新实例。此外,该模块的客户端不应关心它们在create-user-module函数中获得的内容,它们应该只是传递它而不检查它,这样您就可以随时更改用户模块实现。如果您要实现多个实现,Stuart还会讨论如何为这些模块创建协议。

尝试回答你的问题,一个环形适配器只是1个参数的函数,而compojure只是一个路由库,所以你可以使用如下的闭包创建一个web-app:

(ns state.web
  (:use compojure.core)
  (:require [state.core :as core]))

(defn web-module [user-module]
  (routes
   (GET "/all" [] (core/get-users user-module))))

现在,您可以调用Web模块来创建Web应用程序,并将所需的依赖项作为参数传递。当然,您仍然需要有人使用正确的用户模块创建Web应用程序,因此您只需要一个“主要”功能,将所有内容连接在一起:

(ns state.main
  (:require state.core
            state.web)
  (:use ring.adapter.jetty))

(defn start []
  (let [user-module (state.core/create-user-module)
        web-module (state.web/web-module user-module)]
    (run-jetty web-module {:port 3000 :join? false})))

(defn stop [app]
    (.stop app))

start将通过您的应用main方法调用。这只是意味着您需要切换到lein-run插件。

现在,鉴于你在询问init(来自我认为的lein ring插件),我猜你计划在一个容器中部署你的webapp。由于lein ring插件必须在java servlet fw约束内工作,并且处理程序最终编译为java servlet,所以你可以做的最好的事情就是:

(ns state.web
  (:use compojure.core)
  (:require [state.core :as core]))

(def module-deps (atom {})

(defn init-app [] (swap! module-deps conj [:user-module (core/create-user-module)]))

(defroutes web-module []
   (GET "/all" [] (core/get-users (:user-module @module-deps))))

这仍然意味着您的核心命名空间很容易测试,但您仍然在Web命名空间中具有全局状态,但我认为这是“正确”封装的,如果您必须使用Java容器,则可能已经足够好了。 / p>

这只是为什么库比框架“更好”的另一个论点:)

答案 1 :(得分:2)

在很多情况下你需要全局状态,因此你无法避免它,你可以做的是管理它正确,我想这就是2点所说的:

不是一个好方法:

(ns data)
(def users (atom []))

(ns pages)
(defn home []
    (do-something data/@users)

(defn save []
    (let [u @users]
       (swap! data/users ....)

好方法:

(ns data)
(def- users (atom []))
(defn get-users [] @users)
(defn update-user [user] (swap! @users ...))

(ns pages)
; use the functions exposed by data ns to interact with data rather then poking the atom directly.

基本上所有对任何类型状态的访问都应该从应用程序的其他部分中抽象出来。您的所有业务逻辑函数都应该将状态作为参数并返回新状态,而不是自己选择状态并直接更新它。