【问题标题】:How to get rid of global data in a Compojure application如何摆脱 Compojure 应用程序中的全局数据
【发布时间】:2013-03-14 02:06:45
【问题描述】:

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

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

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

我对如何以这种方式将defroutesinitapp 组合在一起并摆脱该范围内的全局变量特别感兴趣。

【问题讨论】:

    标签: clojure compojure


    【解决方案1】:

    我从 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-module 来创建 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 将从您的应用程序main 方法中调用。这只是意味着你需要切换到 lein-run 插件。

    现在,鉴于您正在询问init(我假设来自 lein ring 插件),我猜您计划将您的 web 应用程序部署在容器中。由于 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 容器,可能就足够了。

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

    【讨论】:

      【解决方案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.
      

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

      【讨论】:

      • @hsestupin: 这是一个问题还是一个肯定:)
      • 确认书。我的意思是当状态不能通过直接引用来改变时,我完全同意这种方法。
      • 这看起来像封装的全局状态,但仍然很糟糕。请参阅vimeo.com/46163090 进行演讲。
      • @dAni 能否请您提及演讲中的建议,特别是解决方案(而不是仅仅说它不好)
      • @Ankur 抱歉,我认为 St3fan 没有看到演示文稿,而只是看到了摘要,因此 Stuart 的完整演示文稿足以解释(30 分钟!)它的含义。有关如何将其应用于 compojure 的完整说明,请参阅我的回复。
      猜你喜欢
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多