Nói tóm lại, Component là một khuôn khổ DI chuyên. Nó có thể thiết lập một hệ thống tiêm cho hai bản đồ: bản đồ hệ thống và bản đồ phụ thuộc.
Hãy nhìn vào một ứng dụng web làm-up (từ chối trách nhiệm, tôi đã gõ này trong một hình thức mà không thực sự chạy nó):
(ns myapp.system
(:require [com.stuartsierra.component :as component]
;; we'll talk about myapp.components later
[myapp.components :as app-components]))
(defn system-map [config] ;; it's conventional to have a config map, but it's optional
(component/system-map
;; construct all components + static config
{:db (app-components/map->Db (:db config))
:handler (app-components/map->AppHandler (:handler config))
:server (app-components/map->Server (:web-server config))}))
(defn dependency-map
;; list inter-dependencies in either:
;; {:key [:dependency1 :dependency2]} form or
;; {:key {:name-arg1 :dependency1
;; :name-arg2 :dependency2}} form
{:handler [:db]
:server {:app :handler})
;; calling this creates our system
(def create-system [& [config]]
(component/system-using
(system-map (or config {})
(dependency-map)))
này cho phép chúng ta gọi (create-system)
để tạo ra một thể hiện mới của toàn bộ ứng dụng của chúng tôi khi chúng ta cần.
Sử dụng (component/start created-system)
, chúng tôi có thể chạy các dịch vụ của hệ thống mà nó cung cấp. Trong trường hợp này, đó là máy chủ web đang lắng nghe trên một cổng và một kết nối db mở.
Cuối cùng, chúng tôi có thể dừng nó bằng (component/stop created-system)
để ngăn hệ thống chạy (ví dụ: - dừng máy chủ web, ngắt kết nối khỏi db).
Bây giờ chúng ta hãy nhìn vào components.clj
của chúng tôi cho các ứng dụng của chúng tôi:
(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)))
Vì vậy, những gì đã làm chúng tôi chỉ làm gì? Chúng tôi có một hệ thống nạp lại được. Tôi nghĩ rằng một số nhà phát triển clojurescript sử dụng figwheel bắt đầu thấy những điểm tương đồng.
Điều này có nghĩa là chúng tôi có thể dễ dàng khởi động lại hệ thống của mình sau khi chúng tôi tải lại mã. Vào số 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 {}}}
(defn init []
(alter-root-var #'the-system
(constantly system/create-system system-config)))
(defn start []
(alter-root-var #'the-system component/start))
(defn stop []
(alter-root-var #'the-system
#(when % (component/stop %))))
(defn go []
(init)
(start))
(defn reset []
(stop)
(refresh :after 'user/go))
Để chạy một hệ thống, chúng ta có thể gõ này trong repl của chúng tôi:
(user)> (reset)
nào sẽ được tải lại mã của chúng tôi, và khởi động lại toàn bộ hệ thống.Nó sẽ tắt hệ thống thoát đang chạy nếu nó đang hoạt động.
Chúng tôi nhận được các lợi ích khác:
- End để kết thúc thử nghiệm rất dễ dàng, chỉ cần chỉnh sửa cấu hình hoặc thay thế một phần để trỏ đến các dịch vụ trong quá trình (Tôi đã sử dụng nó để trỏ đến một quá trình trong máy chủ kafka để kiểm tra).
- Bạn về mặt lý thuyết có thể sinh ra ứng dụng của bạn nhiều lần cho cùng một JVM (không thực sự thiết thực như điểm đầu tiên).
- Bạn không cần phải khởi động lại REPL khi bạn thực hiện thay đổi mã và phải khởi động lại máy chủ của bạn
- Không giống như chiếc nhẫn tải lại, chúng tôi có được một cách thống nhất để khởi động lại ứng dụng của chúng tôi không phân biệt mục đích của nó: một công nhân nền, microservice, hoặc hệ thống học máy có thể được thiết kế theo cùng một cách.
Nó đáng chú ý là, vì mọi thứ đều trong quá trình, Component không xử lý bất cứ điều gì liên quan đến fail-over, hệ thống phân phối, hoặc mã bị lỗi;)
Có rất nhiều "tài nguyên" (aka đối tượng stateful) mà thành phần có thể giúp bạn quản lý trong một máy chủ:
- Connections với các dịch vụ (hàng đợi, dbs, vv)
- Passage of Time (scheduler, cron, vv)
- loggin g (ứng dụng khai thác gỗ, khai thác gỗ ngoại lệ, số liệu, vv)
- file IO (cửa hàng blob, hệ thống tập tin địa phương, vv)
- kết nối Incoming client (web, ổ cắm, vv)
- Tài OS (thiết bị , các nhóm luồng, v.v.)
Thành phần có vẻ quá mức cần thiết nếu bạn chỉ có máy chủ web + db. Nhưng ít ứng dụng web chỉ là những ngày này.
Side Lưu ý: Moving the-system
vào không gian tên khác làm giảm khả năng làm mới the-system
var khi phát triển (ví dụ - gọi refresh
thay vì reset
).
Tôi sẽ đưa ra một 'Amen' lớn cho tiện ích của câu trả lời như vậy. – RatavaWen
Các thành phần được sử dụng tại Walmart, ứng dụng Clojure không tầm thường. Có một cuộc nói chuyện ở đây https://www.youtube.com/watch?v=av9Xi6CNqq4 Có thể làm sáng tỏ. –