Working as <inject role here> in a variety of
projects, companies, languages and frameworks since 2002
web projects, CMS, eCommerce
natural language processing, data analysis
Just someone using Lisp for many years
with an interest in software architecture
What this is not about
What this is about
They call it a classico
Classical designs for procedural languages show a very
characteristic structure of dependencies. As (the
following figure) illustrates, these dependencies
typically start from a central point, for instance the
main program or the central module of an application. At
this high level, these modules typically implement
abstract processes. However, they are directly depending
on the implementation of the concrete units, i.e. the very
functions. These dependencies are causing big problems in
practice. They inhibit changing the implementation of
concrete functions without causing impacts on the overall
system.
Gernot Starke, "Effektive Softwarearchitekturen"
A mace of dependencies
The more entangled the code, the more difficult
it becomes to change
Abstract processes depend on concrete implementations
Dependencies often cross architectural boundaries
Explicit vs. implicit
Explicit: everything that is obvious from the outside
function signature, argument lists to functions
(defn)
:refered names in namespace declaration
compile time errors
Implicit: everything inside only (implementation)
using names not given as arguments
requiring namespaces only or use :refer :all
correctness at run time
Explicit vs. implicit
Known unknowns are better than unknown unknowns
Or are they?
Encapsulation: hiding details that should not matter
Relevant information readily
available
Architectural boundaries and decisions should be
obvious
So many questions
Developer
If and how should a user be able to influence behavior?
What do I want to hide so I can change the
implementation without breaking assumptions?
User
What arguments need to be provided?
Does this function use something internally I know
nothing about that might come back to haunt me?
Can I influence the behavior of this function?
Onion architecture
Dependencies always point inwards
Inner levels might define and use abstractions of
outer layers
Enables changing outer layers without requiring changes to inner layers
Dependency in parameter explicit, but signature implicit
Shifts dependency problem to caller
Close over dependencies
(defn make-playfn
"Returns a function that will play all songs"
[blarefn songgetterfn]
(fn ; Plays all songs, potentially randomized
[randomize]
(let [songs (songgetterfn)
songs (if randomize (shuffle songs) songs)]
(map (fn [song]
(blarefn song))
songs))))
Close over dependencies
Factory function returns a function that closes over dependencies
Returned closure takes only domain objects as arguments
## Service locator pattern
- Indirection uses functions as first-class objects
- Dependency on lookup function at compile time
- Explicit signature definition, but only checked at run time
- Dynamic binding of `*services*` allows changes at run time
## Funsig library
- Hides the service locator pattern behind macros
- Signature validation & multiple implementations
- Caller has no say in choice of implementation
- Implementation and caller depend on abstraction at compile time
## clj-di
- clj-di also uses service locator
- clj-di makes 'dependency injection' explicit
- Signatures of registered functions are implicit only,
check at run time
- Provides a `with-registered` for substitutions in tests
## Multi-methods
- `defmulti` provides abstract "signature" specification
plus dispatch function
- Implementation is chosen via dispatch function
- Dispatch does not have to happen in terms of types
- Can't use multi-methods to group different methods
like protocols, simply use namespaces
## TL;DR Multi-methods
- Inversion of control
+ Caller depends on abstraction at compile time
+ Implementation depends on abstraction (multimethod)
- Implicit _detailed_ knowledge of dispatch function required
- If you don't need flexibility of dispatch function,
using multi-methods for resolving dependencies is
over-engineered
Stateful dependencies
- DDD distinguishes between domain entities and services
- In OOP, services implemented by objects
+ objects manage state locally
- In FP, functions implement services
+ strive for "pure" functions without side-effects
+ state management is still important
Resource management
- Services often dependend on states that are _resources_
+ not domain entities
+ technical "details" like connections, channels, ...
- Lifecycle management of resources in applications is often critical
## Closures, revisited
- We can configure state before function call
- This separates dependency setup from call time
- But we can't change the state easily
## Component
- Explicit management of complex dependencies
- Components need and obey a _life-cycle_ protocol
- All components are created and all dependencies are “injected” when _system_ starts up
- Components make up an application, so don't use component in your library
- Supports Stuart Sierra's reloaded workflow
When some piece of code fun-a depends on some
managed state, it needs to become a component A
In turn, some fun-b
that wants to call fun-a now needs to depend on
component A, too and hence
also needs to become a component (B)
B will hold a reference
to a component A at run time
## TL;DR Component
- Dependencies of components can be inspected and changed
(entire sub-graph of dependencies)
- “Full buy-in” can lead to code in which most
functionality is managed with protocols and records
- Only “inject” needed dependencies
+ System map is a global state (map) of dependencies
+ Don't pass around system map or access the system map as a var (global environment) in code
## Mount
- _Making application state enjoyably reloadable_
- Same purpose as component, but explicitly takes a different approach
+ Claims to be “more clojure-ish”
+ provides lifecycle functions (no protocol)
- _trust the Clojure compiler for dependency resolution_
+ aka `:require`/`:refer`
(facts "Handling state with mount"
(with-state-changes [(before :facts (mount/start))
(after :facts (mount/stop))]
(fact "We can play over the configured device"
(play false)
=> (just ["Will stream David Bowie -- Blackstar " ; ...
## Mount characteristics
- States have explicit start and stop functions (or values)
- Dependencies involve only vars and namespaces
- Doesn't mandate use of records or protocols
+ behavior implemented with normal functions
- Vars hold `DeferableState` objects which implement
`IDeref` interface and which behave like values when
`:started`
## TL;DR Mount
- mount focusses on state, but doesn't offer an obvious way
to change behavior
+ _orthogonal_ to what mount strives to do well
+ you can use protocols for abstracting behavior
- Code using states rely on global state -- the global environment which holds vars
- Implicit state dependencies vs. explicit argument passing in component library
## TL;DR Dependencies in Clojure
- Clojure provides many ways to manage dependencies
- A lot of choice wrt. being explicit or leaving things implicit
- Variety regarding time and place of dependency resolution
+ either once and global per application ("singleton" like)
+ or everywhere locally within the application
+ there is no controlled middle ground (e.g. inject new state on request / transaction boundary)
## Keep it simple
- Don't overengineer, focus on important things
- Always be composing – cf. Zach Tellman
+ Prefer small pure functions
+ Reduce dependency problem to “integration functions”
+ Testing motivation for DI mostly goes away
- Split out dependencies of code on components / services
+ that you think could change
+ or of which you already have multiple implementations
# THANK YOU
- Source code [https://github.com/schaueho](https://github.com/schaueho)
- Blog [http://www.find-method.de/](http://www.find-method.de/)
- Google+ [https://plus.google.com/Holger.Schauer](https://plus.google.com/Holger.Schauer)
## Image credits
- [Needle](http://www.torange.us/Backgrounds-textures/wallpapers/Medical-background-19870.html) from www.tOrange.us under CC-SA 4.0
- [Balls](https://commons.wikimedia.org/wiki/File:Colorful_Super_ball.jpg) from 高橋 宗史 under CC-by-SA 2.5
- [Wasserhahn](https://commons.wikimedia.org/wiki/File:Wasserhahn_aus_Holzwand.JPG) from High Contrast under CC-by-3.0 DE