23

Tôi đang cố gắng tìm cách sử dụng Stuart Sierra's component library trong ứng dụng Clojure. Từ việc xem Youtube video của anh ấy, tôi nghĩ rằng tôi đã hiểu rõ vấn đề dẫn đến việc anh ta tạo ra thư viện; tuy nhiên tôi đang cố gắng tìm ra cách để thực sự sử dụng nó trên một dự án mới phức tạp, hợp lý.Cách sử dụng thư viện thành phần của Stuart Sierra ở Clojure

Tôi nhận ra điều này nghe có vẻ rất mơ hồ, nhưng cảm giác như có một số khái niệm quan trọng mà tôi đang thiếu, và một khi tôi hiểu nó, tôi sẽ nắm bắt tốt về cách sử dụng các thành phần. Nói cách khác, tài liệu và video của Stuart được đưa vào WHAT và WHY của các thành phần với chi tiết đáng kể, nhưng tôi đang bỏ lỡ CÁCH.

Có bất kỳ loại chi tiết hướng dẫn/hương ra khỏi đó mà đi vào:

  • lý do tại sao bạn muốn sử dụng các thành phần ở tất cả cho một ứng dụng không tầm thường Clojure
  • một phương pháp cho cách bạn muốn phá vỡ các chức năng trong một ứng dụng Clojure không tầm thường, như vậy mà các thành phần có thể được thực hiện trong một thời trang hợp lý tối ưu. Thật đơn giản khi tất cả những gì bạn có là ví dụ: một cơ sở dữ liệu, một máy chủ ứng dụng và một tầng máy chủ web, nhưng tôi đang cố gắng nắm bắt cách bạn sử dụng nó cho một hệ thống có nhiều lớp khác nhau mà tất cả đều cần phải làm việc cùng nhau một cách chặt chẽ
  • cách tiếp cận phát triển/thử nghiệm/chuyển đổi dự phòng/v.v. trong một ứng dụng Clojure không tầm thường đó là được xây dựng sử dụng các thành phần

Cảm ơn trước

+1

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

+2

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ỏ. –

Trả lời

32

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).

Các vấn đề liên quan