DI en Clojure

WSUG

18 Décembre 2013

Claude  Falguière  /   @cfalguiere

exemples de code  https://github.com/cfalguiere/Presentations/tree/master/Pres-DI-Clojure/demos

Clojure + DI

Perplexitéw

Usages en Java

  • mocker pour les tests
  • découpler, modulariser
  • assembler des composants qui ne se connaissent pas
  • isoler la configuration

Framework de test Midje


(defn finder [] #(str "value from db with " %)) 
(defn pretty-printer [key] (str "This is a " ((finder) key) ))
;; test Midge
(fact "it should display This is a"
  (pretty-printer "Bob") => "This is a fake value"
  (provided
   (finder) => (fn [key] "fake value") )) ;; test stub

Dynamic rebinding

  • immutable par défaut
  • altérable par with-redefs, alter-var-root ou binding

(def x 1)  ;; x -> 1
(with-redefs [x 3] x)  ;; x -> 3 temporairement 

(def y 1)  ;; y -> 1
(alter-var-root #'y inc) ;; incrémentation atomique 
(y) ;; y -> 2

(def ^:dynamic z 1)
(binding [z 4] z) ;; z -> 4 temporairement et pour ce thread

Dynamic rebinding de fonctions


(defn ^:dynamic finder [key] (str "value from the db")) 
(defn pretty-printer [key] (str "This is a " (finder key) )) 
;;test
(defn fake-finder [key] "fake value")

(deftest pretty-print-with-fake
  (is ( = "This is a fake value"
	  (binding [finder fake-finder] ;; test stub
	    (pretty-printer "Bob")) )))

Refactorer - découpler

  • Faut-il vraiment une dépendance ?

(defn finder [key] (str "value from the db")) 
(defn pretty-printer [value] (str "This is a " value)) 

(defn -main [& args]
  (pretty-printer (finder "Alice"))

;; ou
(defn -main [& args]
  (let [value (finder "Alice")]
	(pretty-printer value) )) 
;; test
(fact "it should display This is a"
      (pretty-printer "fake value") => "This is a fake value")

Refactorer - découpler

  • Higher order functions
  • -> injecter des fonctions

(defn finder [key] (str "value from the db")) 
(defn pretty-printer [key finder] (str "This is a " (finder key) ))
(defn -main [& args]
  (pretty-printer "Alice" finder))
;; test
(defn fake-finder [key] "fake value")
(fact "it should display This is a"
      (pretty-printer "Bob" fake-finder) => "This is a fake value")

Assembler des composants

  • application partielle (curryfication)
  • partial

(defn finder [key] (str "value from the db")) ;; very complex finder
(defn pretty-printer [finder key] (str "This is a " (finder key)))

;; application partielle
(def all-in-one-printer (partial pretty-printer finder)) 

(defn -main [& args]
   (all-in-one-printer "Alice"))

Configurer

  • équivalent de DI
  • assemblage par currying

(defn finder [dbname key] (str "value from db " dbname)) 
(defn pretty-printer [finder key] (str "This is a " (finder key)))

(defn -main [dbname]
  (let [printer (partial pretty-printer (partial finder dbname))]  
   (printer "Alice") ))
 
lein run DBProd
"This is a value from the db DBProd"

Configurer

  • partage d'un contexte de configuration
  • altérable par les factories

(def config { :dbname "DBProduits" }) 

(defn finder [key] (str "value from db " (config :dbname))) 
(defn pretty-printer [finder key] (str "This is a " (finder key)))

(defn -main [& args]
  (let [printer (partial pretty-printer finder)]  
   (printer "Alice") ))
 
lein run
"This is a value from the db DBProduits"

Configurer

  • Polymorphisme avec Protocol
;; core
(defprotocol Finder (finder [this arg]))

(deftype FakeFinder [] Finder
  (finder [this arg] (test8.fake/fake-finder arg)))

(deftype RealFinder [] Finder
  (finder [this arg] (test8.finder/db-finder arg)))

(defn pretty-printer [value] (str "This is a "  value))

(defn -main [& args]
  (pretty-printer (finder (FakeFinder.) "Alice") ))
;; fake module
(ns test8.fake)
(defn fake-finder [key] "fake value")
lein run
"This is a fake value"

La cours des grands

  • Services à Prismatic

Frameworks

  • Graph
  • Flow
  • Jig

Graph

;; défini les opération et l'algorithme
(def stats-graph
  "A graph specifying the same computation as 'stats'"
  {:n  (fnk [xs]   (count xs))
   :m  (fnk [xs n] (/ (sum identity xs) n))
   :m2 (fnk [xs n] (/ (sum #(* % %) xs) n))
   :v  (fnk [m m2] (- m2 (* m m)))})

;; compile le calcul pour 1, 2, 3, 6
(def stats-eager (graph/eager-compile stats-graph))
(into {} (stats-eager {:xs [1 2 3 6]})))

;; resultat
{:n 4 :m 3 :m2 (/ 25 2) :v (/ 7 2)}

Flow

;; défini le process
(def process-flow
  (flow/flow
   :result  ([gamma delta epsilon]
               (subprocess-d gamma delta epsilon))
   :gamma   ([alpha beta]  (subprocess-a alpha beta))
   :delta   ([alpha gamma] (subprocess-b alpha gamma))
   :epsilon ([gamma delta] (subprocess-c gamma delta))))

;; compile le process
(def compute (flow/compile process-flow [:alpha :beta]))

(compute {:alpha 1 :beta 2})
;;=> {:result 14, :epsilon 7, :delta 4, :gamma 3,
;;    :alpha 1, :beta 2}

Jig

  • repo: https://github.com/juxt/jig
  • configuration par fichier
  • gestion des dépendances entre composants
  • cycle de vie des composants (init, start, stop)
;; fichier de configuration
{:jig/components
 {:sudoku
  {:jig/component sudoku.jig/Website
   :jig/project "examples/sudoku/project.clj"
   :puzzle [8 0 0 0 0 0 0 0 0 ...
(deftype Website [config]
  Lifecycle
  (init [_ system] ...
  (start [_ system] system)
  (stop [_ system] system))
(defroutes handler
  (GET "/sudoku.html" [:as req]
   ...
     (let [puzzle (-> req :jig/config :puzzle)] ...

Questions

Claude Falguière - @cfalguiere