5

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

  • 在顶层定义 refs 和 atom 基本上是通过单例实现的全局可变状态,请避免
  • 建议使用构造函数返回您要使用的状态变量,然后将该状态传递给每个函数

我认为这是一个很好的建议,但我不完全确定如何在 Ring/Compojure 应用程序中实现这一点。谁能举一个具体的例子来说明这将如何工作?

我特别感兴趣如何以这种方式组合defroutes,initapp一起摆脱该范围内的全局变量。

4

2 回答 2

5

我从 Stuart 的演讲中了解到是这样的:

(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 应用程序:

(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 模块来创建一个 webapp,并将所需的依赖项作为参数传递。当然,您仍然需要有人使用正确的用户模块创建 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将从您的 appmain方法中调用。这只是意味着您需要切换到 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 容器,可能就足够了。

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

于 2013-03-27T01:10:11.987 回答
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.

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

于 2013-03-25T04:22:45.130 回答