Slide 1

Slide 1 text

Functional Composition with Dependency Injection Shantanu Kumar (@kumarshantanu)

Slide 2

Slide 2 text

Who am I? • Principal Engineer at Concur • Author of “Clojure High Performance Programming” • Open Source contributor: https://github.com/ kumarshantanu • Using Clojure since early 2009 • @kumarshantanu on Twitter

Slide 3

Slide 3 text

Layered design web- cart-items service- cart-items cache-fetch- cart-items db-fetch- cart-items cache-store- connection db-conn- pool

Slide 4

Slide 4 text

(defn service-cart-items [user-id] (->> (cart-item-ids user-id) (mapv cache-cart-item))) Layered design: Code (defn web-cart-items [user-session] (service-cart-items (:user-id user-session))) (defn cache-cart-item [cache item-id] (or (cache-get cache item-id) (let [item (db-cart-item item-id)] (cache-put! cache item) item))) (defn db-cart-item [conn-pool item-id] (with-conn [conn conn-pool] (sql-fetch conn item-id)))

Slide 5

Slide 5 text

Pause & Think • Should a layer know about the underlying layer’s implementation detail (cache, connection pool)? • What exactly is the “contract” between the caller and the underlying layer? Is the contract explicit? • How do you test the service layer for failures? Can failures be simulated? Can tests be run in parallel? • How easy is it to instrument or wrap an underlying layer implementation? (resilience, asynchrony etc.)

Slide 6

Slide 6 text

Inter-layer contract • Functions (different arities for contract vs impl) • Protocols (usually backed by defrecord) • Multi-methods

Slide 7

Slide 7 text

(defn service-cart-items [item-finder user-id] (->> (cart-item-ids user-id) (mapv item-finder))) Layered design: HoF (defn web-cart-items [service user-session] (service (:user-id user-session))) (defn cache-cart-item [db-item-finder cache item-id] (or (cache-get cache item-id) (let [item (db-item-finder item-id)] (cache-put! cache item) item))) (defn db-cart-item [conn-pool item-id] (with-conn [conn conn-pool] (sql-fetch conn item-id)))

Slide 8

Slide 8 text

Dependency Injection • Dependency Inversion principle: “Depend upon Abstractions. Do not depend upon concretions.” • Creating partially applied functions… • Or, creating record (defrecord) instances • Injections cascade across layers • Boilerplate, Code maintenance, and Automation • Trade offs

Slide 9

Slide 9 text

Friction in DI approaches • Dynamic binding • Passing the world • Protocol impedance mismatch • Var dissociation (clojure.core/partial) • Boilerplate and code maintenance • Incidental bootstrapping • Global mutable dependencies (clojure.core/alter-var-root)

Slide 10

Slide 10 text

App initialization phase • First class application initialization • Decomplected from definition and state • Runtime split into initialization and run • Opportunity for composition, even performance optimization • Dependency Injection is well suited for initialization phase

Slide 11

Slide 11 text

Dependency Injection Made Easy https://github.com/kumarshantanu/dime

Slide 12

Slide 12 text

Why a library for DI/HoF? • Automate boilerplate (fns that make partials) • Visually distinguish dependencies vs runtime args • Pet (fns making partials) vs Cattle (using library) • Focus on business logic, not on incidental code

Slide 13

Slide 13 text

(defn ^{:expose :item-finder} cache-cart-item [^:inject db-item-finder ^:inject cache item-id] (or (cache-get cache item-id) (let [item (db-item-finder item-id)] (cache-put! cache item) item))) (defn ^{:expose :db-item-finder} db-cart-item [^:inject conn-pool item-id] (with-conn [conn conn-pool] (sql-fetch conn item-id))) Layered design: DIME (defn web-cart-items [^:inject service user-session] (service (:user-id user-session))) (defn ^{:expose :service} service-cart-items [^:inject item-finder user-id] (->> user-id cart-item-ids (mapv item-finder)))

Slide 14

Slide 14 text

DIME Injection Example (ns example-app.init (:require [dime.core :as di] [dime.var :as dv])) (defn resolve-deps [seed] (let [{:keys [web-cart-items]} (di/inject-all graph seed)] ;; now can call `web-cart-items` with 1 arg ...)) (def graph (dv/ns-vars->graph ['example-app.db 'example-app.cache 'example-app.biz 'example-app.web]))

Slide 15

Slide 15 text

Visualization with Lein-viz

Slide 16

Slide 16 text

Using REPL with DIME • Define vars — (def injected nil) (def de-init nil) • Update #’injected with all injected objects • Update #’de-init with fn to de-initialize the injected • ((get injected :expose-key) runtime-args…) • Reload support via clojure/tools.namespace

Slide 17

Slide 17 text

DIME Advantages • Automatic generation of cascading partials • Automatic injection of dependencies • Declarative, hence visualization is possible • Easy to simulate dependencies for testing • Amenable to parallel testing • Can be invoked late, at initialization time

Slide 18

Slide 18 text

DIME Challenges • Opinionated in favor of Higher-order Functions • Emacs M-. may not work (true for any HoF) • Exposed names should be namespaced for clarity • Automated cascading injection may be hard to grasp • CLJS support (though CLJS has reified vars/ns now) • Lack of tooling for navigating exposed names

Slide 19

Slide 19 text

Thank You! @kumarshantanu (Twitter, Github)