如何使用Stuart Sierra';Clojure中的组件库

如何使用Stuart Sierra';Clojure中的组件库,clojure,dependency-injection,functional-programming,components,Clojure,Dependency Injection,Functional Programming,Components,我正在努力弄清楚如何在Clojure应用程序中使用。通过观察他的作品,我认为我已经对导致他创建图书馆的问题有了一个很好的理解;然而,我正在努力解决如何在一个新的、相当复杂的项目上实际使用它 我意识到这听起来很模糊,但我觉得有一些关键的概念我还没有理解,一旦我理解了,我就会很好地掌握如何使用组件。换句话说,Stuart的文档和视频非常详细地讨论了组件的WHAT和WHY,但我忽略了HOW 是否有任何详细的教程/演练涉及: 为什么你会在一个非平凡的Clojure应用程序中使用组件 一种方法论,用于分

我正在努力弄清楚如何在Clojure应用程序中使用。通过观察他的作品,我认为我已经对导致他创建图书馆的问题有了一个很好的理解;然而,我正在努力解决如何在一个新的、相当复杂的项目上实际使用它

我意识到这听起来很模糊,但我觉得有一些关键的概念我还没有理解,一旦我理解了,我就会很好地掌握如何使用组件。换句话说,Stuart的文档和视频非常详细地讨论了组件的WHAT和WHY,但我忽略了HOW

是否有任何详细的教程/演练涉及:

  • 为什么你会在一个非平凡的Clojure应用程序中使用组件
  • 一种方法论,用于分解非平凡Clojure应用程序中的功能,以便以合理的最佳方式实现组件。当您拥有的只是一个数据库、一个应用服务器和一个web服务器层时,它就相当简单了,但我很难理解如何将它用于一个有许多不同层的系统,这些层都需要协调地工作
  • 在使用组件构建的非平凡Clojure应用程序中实现开发/测试/故障切换等的方法

提前感谢

简而言之,组件是一个专门的DI框架。它可以在给定两个映射的情况下设置注入系统:系统映射和依赖映射

让我们看看一个虚构的web应用程序(免责声明,我在表单中键入了这个,但没有实际运行它):

这允许我们在需要时调用
(create system)
来创建整个应用程序的新实例

使用
(组件/启动创建的系统)
,我们可以运行系统提供的服务。在本例中,是Web服务器正在侦听端口和开放数据库连接

最后,我们可以使用
(component/stop created system)
停止它,以停止系统运行(例如,停止web服务器,断开与数据库的连接)

现在让我们看看我们应用程序的
组件.clj

(ns myapp.components
  (:require [com.stuartsierra.component :as component]
            ;; lots of app requires would go here
            ;; I'm generalizing app-specific code to
            ;; this namespace
            [myapp.stuff :as app]))

(defrecord Db [host port]
   component/Lifecycle
   (start [c]
      (let [conn (app/db-connect host port)]
        (app/db-migrate conn)
        (assoc c :connection conn)))
   (stop [c]
      (when-let [conn (:connection c)]
        (app/db-disconnect conn))
      (dissoc c :connection)))

(defrecord AppHandler [db cookie-config]
   component/Lifecycle
   (start [c]
      (assoc c :handler (app/create-handler cookie-config db)))
   (stop [c] c))

;; you should probably use the jetty-component instead
;; https://github.com/weavejester/ring-jetty-component
(defrecord Server [app host port]
   component/Lifecycle
   (start [c]
      (assoc c :server (app/create-and-start-jetty-server
                        {:app (:handler app)
                         :host host 
                         :port port})))
   (stop [c]
      (when-let [server (:server c)]
         (app/stop-jetty-server server)
      (dissoc c :server)))
那么我们刚才做了什么?我们有了一个可重新加载的系统。我认为一些使用clojurescript的开发人员开始看到相似之处

这意味着我们可以在重新加载代码后轻松地重新启动系统。转到
user.clj

(ns user
    (:require [myapp.system :as system]
              [com.stuartsierra.component :as component]
              [clojure.tools.namespace.repl :refer (refresh refresh-all)]
              ;; dev-system.clj only contains: (def the-system)
              [dev-system :refer [the-system]])

(def system-config
  {:web-server {:port 3000
                :host "localhost"}
   :db {:host 3456
        :host "localhost"}
   :handler {cookie-config {}}}

(def the-system nil)

(defn init []
  (alter-var-root #'the-system
                  (constantly system/create-system system-config)))

(defn start []
  (alter-var-root #'the-system component/start))

(defn stop []
  (alter-var-root #'the-system
                  #(when % (component/stop %))))

(defn go []
  (init)
  (start))

(defn reset []
  (stop)
  (refresh :after 'user/go))
要运行系统,我们可以在repl中键入:

(user)> (reset)
这将重新加载我们的代码,并重新启动整个系统。如果启动,它将关闭正在运行的现有系统

我们还有其他好处:

  • 端到端测试很简单,只需编辑配置或替换组件以指向进程内服务(我使用它指向进程内kafka服务器进行测试)
  • 理论上,您可以为同一个JVM多次生成应用程序(实际上没有第一点那么实用)
  • 在进行代码更改时,不需要重新启动REPL,并且必须重新启动服务器
  • 与ring reload不同的是,无论应用程序的用途如何,我们都有一种统一的方法来重新启动应用程序:后台工作程序、微服务或机器学习系统都可以以相同的方式进行架构
值得注意的是,由于所有内容都是进程中的,所以组件不会处理任何与故障转移、分布式系统或错误代码相关的内容;)

组件可以帮助您在服务器中管理大量的“资源”(也称为有状态对象):

  • 连接到服务(队列、数据库等)
  • 时间流逝(调度程序、cron等)
  • 日志记录(应用程序日志记录、异常日志记录、度量等)
  • 文件IO(blob存储、本地文件系统等)
  • 传入的客户端连接(web、套接字等)
  • 操作系统资源(设备、线程池等)
若您只有一个web服务器+db,那个么这个组件可能看起来有点过头了。但现在很少有网络应用程序能做到这一点


旁注:
系统
移动到另一个名称空间可以降低开发时刷新
系统
var的可能性(例如,调用
刷新
,而不是
重置
)。

我只想对这样一个答案的实用性说一声“阿门”。
组件在沃尔玛使用,非常不平凡的Clojure应用程序。这里有一个谈话可能会有所启发。
(user)> (reset)