Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

リローダブルClojureアプリケーション

114 views

Published on

Stuart SierraのComponentフレームワークの使用法解説
Shibuya.lisp #44

Published in: Software
  • Be the first to comment

  • Be the first to like this

リローダブルClojureアプリケーション

  1. 1. Reloadable ClojureApp Stuart Sierra's Component Framework
  2. 2. » Signifier » 93-00 » 01-16 ・通信系スタートアップで開 発 » 16 7 Signifier Clojure » Clojure
  3. 3. Clojure » REPL » » ->Prismatic Schema
  4. 4. REPL » pallet/alembic ... (load-project) » user=> (use 'my-ns.a-namespace :reload) » :reload » » defmulti/defprotocol/macro » Closure
  5. 5. clojure/tools.namespace user=> (require '[clojure.tools.namespace.repl :refer [refresh]]) nil user=> (refresh) :reloading (com.example.util com.example.app com.example.app-test) :ok
  6. 6. clojure/tools.namespace » ring.middleware.reload (ns myapp (:require [compojure.core :refer [GET defroutes]] [ring.middleware.reload :refer [wrap-reload]])) (defroutes app (GET "/" [] "hello world")) (def reloadable-app (wrap-reload app)) » reload >> weavejester/ns-tracker >> clojure/tools.namespace
  7. 7. c.t.namespace » » def ・シングルトン (def state-of-world (ref {})) (def object-handle (atom nil))
  8. 8. c.t.namespace » (defn create-application [] {:state-of-world (ref {}) :object-handle (atom nil)}) def DB connection, config, etc...
  9. 9. ... » Stuart Sierra's Component » Record/Map ・終了処理を管理する » API » » Map (System) »
  10. 10. » danielsz/system » » Jetty, HTTP kit, Carmine, ElasticSearch, etc... » (e.g. Jetty) » API
  11. 11. project.clj (defproject a-project-ec "0.1.0-SNAPSHOT" (...snip...) :dependencies [[funcool/clojure.jdbc "0.9.0"] [hikari-cp "1.7.4"] [honeysql "0.8.0"] [http-kit "2.1.18"] [metosin/compojure-api "1.1.7" :exclusions [org.clojure/tools.reader]] [migratus "0.8.31"] [org.clojure/clojure "1.8.0"] [org.danielsz/system "0.3.1"] [org.postgresql/postgresql "9.4.1211"] [ring "1.5.0" :exclusions [commons-codec org.clojure/tools.reader]]] (...snip...)
  12. 12. user.clj (ns user (:require [system.repl :as repl :refer [system set-init! start stop reset]] [clojure.tools.namespace.repl :refer [refresh]] [a-project-ec.systems :refer [dev-system]])) (set-init! #'dev-system) (defn reload [] (stop) (refresh :after 'system.repl/reset))
  13. 13. systems.clj (ns a-project-ec.systems (:require [com.stuartsierra.component :as component] [a-project-ec.components.customer-master :refer [new-customer-master]] [a-project-ec.components.jetty :refer [new-web-server]] [a-project-ec.components.migratus :refer [new-migratus]] [a-project-ec.components.orders :refer [new-orders]] [a-project-ec.components.product-master :refer [new-product-master]] [a-project-ec.components.ring-handler :refer [new-ring-handler]] [system.components.hikari :refer [new-hikari-cp]] [system.components.repl-server :refer [new-repl-server]] [system.core :refer [defsystem]]))
  14. 14. systems.clj (defn dev-system [] (component/system-map :customer-master (new-customer-master) :hikari-cp (component/using (new-hikari-cp {:adapter "postgresql" :username "a-project" :password "password" :database-name "a-project" :server-name "localhost" :port-number 5432}) [:migratus]) :migratus (new-migratus {:store :database :migration-dir "migrations" :db {:classname "org.postgresql.ds.PGSimpleDataSource" :subprotocol "postgresql" :subname "//localhost:5432/a-project" :user "a-project" :password "a-project123"}}) :orders (component/using (new-orders) [:hikari-cp :product-master :customer-master]) :product-master (new-product-master) :ring-handler (component/using (new-ring-handler) [:customer-master :orders :product-master]) :web (component/using (new-web-server (Integer. (or (env :http-port) "3000"))) {:handler :ring-handler}))) (defn prod-system [] (dev-system))
  15. 15. systems.clj » component/system-map Component SystemMap » component/using Map/Record Vector Inject (component/system-map :customer-master (new-customer-master) :hikari-cp (component/using (new-hikari-cp {:adapter "postgresql" :username "a-project" :password "password" :database-name "a-project" :server-name "localhost" :port-number 5432}) [:migratus]) :migratus (new-migratus {:store :database :migration-dir "migrations" ...
  16. 16. System » System user=>(start) » user=>(stop) » user=>(reload)
  17. 17. product-master.clj (ns a-project-ec.components.product-master (:require [again.core :as again] [cheshire.core :as json] [com.stuartsierra.component :as component] [a-project-ec.retry :as retry] [org.httpkit.client :as http] [schema.core :as s] [slingshot.slingshot :refer [throw+]] [taoensso.timbre :as log]))
  18. 18. product-master.clj (def sheetsu-api-url "https://sheetsu.com/apis/v1.0/123456abcde") (def sheetsu-basic-auth-user "a-user") (def sheetsu-basic-auth-pass "a-password") (defn load-product-master [product-master] (again/with-retries retry/http-call-intervals (let [options {:basic-auth [sheetsu-basic-auth-user sheetsu-basic-auth-pass]} {:keys [status headers body error] :as resp} @(http/get sheetsu-api-url options)] (if (or (nil? status) (>= status 300)) (do (log/warn "load-product-master: failed to call sheetsu api." error) (throw+ {:type :rest-api-failure :message error :status status})) (reset! product-master (json/parse-string body true))))) product-master)
  19. 19. product-master.clj (defrecord ProductMaster [product-master] component/Lifecycle (start [component] (log/info "Starting ProductMaster...") (assoc component :product-master (load-product-master product-master))) (stop [component] (log/info "Stopping ProductMaster...") (dissoc component :product-master))) (defn new-product-master [] (map->ProductMaster {:product-master (atom nil)}))
  20. 20. def ... (defn load-product-master [product-master sheetsu-api-url sheetsu-basic-auth-user sheetwsu-basic-auth-pass] (again/with-retries retry/http-call-intervals (let [options {:basic-auth [sheetsu-basic-auth-user sheetsu-basic-auth-pass]} {:keys [status headers body error] :as resp} @(http/get sheetsu-api-url options)] (if (or (nil? status) (>= status 300)) (do (log/warn "load-product-master: failed to call sheetsu api." error) (throw+ {:type :rest-api-failure :message error :status status})) (reset! product-master (json/parse-string body true))))) product-master) (defrecord ProductMaster [product-master] component/Lifecycle (start [component] (log/info "Starting ProductMaster...") (assoc component :product-master (load-product-master product-master))) (stop [component] (log/info "Stopping ProductMaster...") (dissoc component :product-master))) (defn new-product-master [url user pass] (map->ProductMaster {:sheetsu-api-url url :sheetsu-basic-auth-user user :sheetsu-basic-auth-pass pass :product-master (atom nil)}))
  21. 21. systems.clj config :product-master (new-product-master "https://sheetsu.com/apis/v1.0/123456abcde" "a-user" "a-pass")
  22. 22. Injection hikari-cp-> orders (ns a-project-ec.systems (:require ... [system.components.hikari :refer [new-hikari-cp]])) :hikari-cp (component/using (new-hikari-cp {:adapter "postgresql" :username "a-project" :password "password" :database-name "a-project" :server-name "localhost" :port-number 5432}) [:migratus]) :orders (component/using (new-orders) [:hikari-cp :product-master :customer-master])
  23. 23. Injection orders.clj (ns a-project-ec.components.orders (:require [clj-uuid :as uuid] [com.stuartsierra.component :as component] [a-project-ec.crud :as crud] [a-project-ec.schema.a-project :refer :all] [jdbc.core :as jdbc] [schema.core :as s] [taoensso.timbre :as log]))
  24. 24. Injection orders.clj (def salt "12345668-abcd-1111-2222-1a2b3c4d5e6f") (defrecord Orders [hikari-cp product-master customer-master]) (defn new-orders [] (map->Orders {}))
  25. 25. Injection orders.clj (s/defn order-item-id :- s/Uuid [order-id :- s/Uuid product-code :- ProductCode] (uuid/v5 order-id product-code)) (s/defn create :- s/Uuid [this :- Orders order :- OrderRequest] (log/info "order-req:" order) (let [{:keys [hikari-cp product-master customer-master]} this order-id (-> (uuid/v1) (uuid/v5 salt) (uuid/v5 (:retailer-id order))) order (-> order (assoc :id order-id) (assoc :order-items (map #(assoc % :id (order-item-id order-id (:product-code %)) :order-id order-id) (:order-items order))))] (jdbc/atomic hikari-cp (let [id (crud/create hikari-cp :orders (dissoc order :order-items))] (for [order-item (:order-items order)] (crud/create hikari-cp :order-items order-item)) id))))
  26. 26. Injection crud.clj (s/defn create :- s/Str [conn table :- s/Keyword kv :- {s/Keyword s/Any}] (let [kv (assoc kv :created-at :%now :updated-at :%now) sql (-> (h/insert-into table) (h/values [kv]) (ph/returning :id) sql/format)] (log/debug "create:" sql) (:id (first (jdbc/fetch conn sql)))))
  27. 27. Close over » systems.clj :ring-handler (component/using (new-ring-handler) [:customer-master :orders :product-master]) » ring-handler.clj (ns a-project-ec.components.ring-handler (:require [com.stuartsierra.component :as component] [compojure.api.sweet :refer :all] [a-project-ec.routes.customer-master :refer [customer-master-routes]] [a-project-ec.routes.order :refer [order-routes]] [a-project-ec.routes.product-master :refer [product-master-routes]] [ring.util.http-response :refer :all] [schema.core :as s] [taoensso.timbre :as log]))
  28. 28. Close over (defmethod compojure.api.meta/restructure-param :components [_ components acc] (update-in acc [:letks] into [components `(::components ~'+compojure-api-request+)])) (defn wrap-components [handler components] (fn [req] (handler (assoc req ::components components)))) (s/defn make-handler [this handler-fn] (wrap-components handler-fn this))
  29. 29. Close over (def app (api {:swagger {:ui "/a-project-ec/api-docs" :spec "/a-project-ec/swagger.json" :data {:basePath "/" :info {:title "a-project E-commerce API" :description "API for Cigar Retailers"}}}} customer-master-routes order-routes product-master-routes)) (s/defrecord RingHandler [product-master] component/Lifecycle (start [component] (log/info "Starting RingHandler...") (assoc component :handler-fn (make-handler component #'app))) (stop [component] (log/info "Shutting down RingHandler...") (dissoc component :deps :handler-fn))) (s/defn new-ring-handler [] (map->RingHandler {}))
  30. 30. Ring-handler Jetty Inject (ns a-project-ec.components.jetty (:require [a-project-ec.components.ring-handler :as h] [taoensso.timbre :as log] [com.stuartsierra.component :as component] [ring.adapter.jetty :refer [run-jetty]] [schema.core :as s]) (:import (org.eclipse.jetty.server Server)))
  31. 31. Ring-handler Jetty Inject (s/defrecord WebServer [port :- s/Int server :- Server handler :- h/->RingHandler] component/Lifecycle (start [component] (log/info "Starting Jetty...") (if (nil? (:server component)) (try (let [handler (:handler-fn handler) server (run-jetty handler {:port port :join? false})] (assoc component :server server)) (catch java.net.BindException e (log/warn e "Jetty has already started.") component)) component)) (stop [component] (log/info "Shutting down Jetty...") (when server (try (.stop server) (catch Throwable t (log/warn t "Failed to stp Jetty."))) (dissoc component :server))))
  32. 32. Ring-handler Jetty Inject (defn new-web-server ([port] (map->WebServer {:port port})) ([port handler] (map->WebServer {:port port :handler handler})))
  33. 33. compojure-api Component (defroutes order-routes (context "/a-project-ec/api/orders" [] :tags ["Orders"] (POST "/" [] :components [orders] :return OrderId :summary " " :body [order-request OrderRequest] (ok (orders/create orders order-request)))))
  34. 34. FAQ » » System » » System Inject » » system.repl/system (:orders system.repl/system) Component
  35. 35. Component » (reload) REPL » REPL » » DB ring middleware REPL »
  36. 36. » @k2nakamura » http://signifier.jp

×