Dependency Injections

What

  • A technique that enables loose coupling

  • collaborating classes should rely on infrastructure to provide necessary services

  • A dependency references other classes to complete some work

    • delegate work to another class

    • send a message to another object from the calling class

      • Just calling an instance method of an object with or without arguments

  • All classes can have dependencies, it is how they managed

  • Dependencies are provided to a class instead of the class creating them

    • This happens at creation time called object composition in a composition root

  • the app has control and proactively injects the required objects.

  • Dependencies are not defined by the number of modules but the number of times each module depends on another module

  • Manage dependency lifecycle

  • Allow for dependencies to be intercepted

  • https://www.techyourchance.com/dependency-injection-myths-debunked/

  • http://www.bryancook.net/2011/08/on-dependency-injection-and-violating.html

  • https://dzone.com/articles/about-dependency-injection

  • https://completedeveloperpodcast.com/episode-185/

  • goolge talk https://www.youtube.com/watch?v=o-ins1nvbDg&feature=youtu.be

  • https://developer.android.com/training/dependency-injection#kotlin

  • https://benjiweber.co.uk/blog/2014/09/13/frameworkless-dependency-injection/

Issues

  • can be over reliant on frameworks or libraries to do this for you

    • pollute code with framework (reduced separation of concerns)

  • Needs to start an app with DI at the beginning, otherwise it becomes harder later on

  • can add more complexity

Why

  • Improves maintainability

  • that reduces coupling and promotes the open-closed principle.

  • provides references to objects that the class depends on, instead of allowing the class to gather the dependencies itself.

  • it is about knowing as little as possible.

    • SRP

  • allows classes to “not know” how their dependencies are assembled, where they come from, or what actual implementations are fulfilling their contracts.

  • not using the “new” keyword in your classes and demanding instances of your dependencies to be provided to your class by its clients.

  • reduces local complexity of the class and makes it dumber, which is a good thing.

  • Without knowing who the provider of the contract is or how to get an instance of it, our class can focus on its single responsibility

  • reduce coupling

  • requires less understanding of the rest of the system to modify and unit test the class

  • By removing the assembly code from your classes, you make them more independent, reusable, and testable.

  • Dependency inversion relies on this idea

Relevant for late binding

  • late binding = ability to replace parts of an app with recompiling code

    • ie third party addons, or support different runtime env

  • Can change the implementation of different database/algorithm, depending on env/settings/configuration/inputs

  • This allows the decision to decide on what service to use as late as possible, to defer the choice

    • then can use one service, but not tied to it, so can easily swap it out for another service with minimal changes

Relevant for unit testing

  • Allows you to swap out injected dependencies with ones that can be controlled to setup situations for different test cases

Type of Abstract factories

  • Methods that create objects of certain kind

  • DI is not a service locator, or a service to call to get dependencies

  • DI is a way to structure code so that you never have to imperatively ask for Dep

Does not require a DI Container

  • A DI container aim is to make it easier compose classes and wire up an application

    • it might add unnecessary overhead for it to be worth using

    • it is an optional tool to implemetn DI

  • Using DI without a container library is called PURE DI

  • Pure DI allows you implement DI without compromises

    • might take a more work

    • easier to follow

Intercept dependencies

  • With DI, can intercept each dependency instance and act on it before it is passed to the consumer

  • Decorating

  • Allows for cross cutting concerns to be applied outside of the consumer

Seams

  • Is a place where an application is assembled from it's constituent parts

  • Where you can disassemble the application and work with the modules in isolation

  • Use of interface, so that class relies on the interface rather than implementation

  • allows you to handle volatile dependencies

Managing volatile dependencies

  • dep that change or can change should be injected

  • Stable dependencies dont need to be injected

    • Might be useful testability or other reasons

Stable Dependencies

  • Criteria

    • The class/module already exists

    • expectation that new versions won't contain breaking changes

    • The types in question contain deterministic algorithms

    • never expect to have to do the following with another class

      • replace

      • wrap

      • decorate

      • intercept the class/module

    • specialised libraries that encapsulate algorithms relevant to your app

  • stable if they are not volatile

Volatile Dependencies

  • Dependencies that don't provide a sufficient foundation for apps

  • You introduce seams in your code to inject volatile dependencies

  • Criteria

    • The dep introduces a requirement to set up and configure a runtime env for an app

      • ie databases, out of process resources like message queues, webservices, filesystems

      • leads to lack of late binding, extensibility, testing

    • The dep does not yet exist or is still in development

    • The dep isn't installed on all machines in the dev organisation

      • ie paid for 3rd party libraries, dep that cannot be installed on all OS

      • leads to disabled testability

    • The dep contains non deterministic behaviour

      • ie randomness, time/date

      • leads to hard to control behaiour for testing

DI Scope

  • As the class has no control of managing it's dep in DI, it has no control of the lifetime (when it goes out of scope, when it is created/reused) of the object injected

    • Does not know when it is created or goes out of scope

    • This simplifies the consumer

  • Thus the composition root can control the lifecycle of a dependency/objects/instances

  • Depending on your language you dont have to worry about the object scope, as this is handled by garbage collector

    • When no class references the dependency it is eligible for garbage collection

  • In application, multiple consumers might use the same dependency

    • You can either inject a separate instance into each consumer

    • or you can choose to share a single instance across multiple consumers

Intercepting Dependencies

  • DI allows the power to modify dependencies before passing them on to the consumer

  • Form of decorator pattern

  • This is related to AOP (aspect oriented programming)

  • can apply logging, auditing, access control, validation etc, to maintain separation of control

Composition Root

  • Describes where and how you should compose an application's object graphs

    • where the object graphs performs the actual work of the app

  • it is a single logical location

  • Object graphs are composed as close as possible to the application's entry point

    • especially for loosely coupled classes

  • This can be

    • Main method

    • it can span multiple classes and then called in main method or via DI container

  • Using constructor injection, pushes the responsibility for the creation of their dependencies up to the consumer, which also pushes the consumer's dep to their consumers.

  • Should be the only place where you use a DI container

    • Otherwise leads to service locator pattern (anit pattern)

  • composition root should be in one module

  • It is not part of the UI layer

How it works

  • Acts as a third party that connects consumers with their services

  • Deferring the decision on how to connect the classes the more options you have

  • They are not reused

  • All classes should use constructor injection (possibly others for good reasons)

    • this allows CR to decide how to compose the objects

  • Should be only place that knows the structure of the object graphs

    • centralises the knowledge

    • avoids application code passing on dependencies to other threads the run parallel to the current operation

      • as the consumer has no way of knowing if it is safe to do so

    • It is up to CR to create the object graph for each concurrent operation

  • The CR relies on config/setting/property files

    • increases flexibility

    • Should separate the loading of the config files to values, from the methods that does the object composition

Avoid

  • Dont create the object graphs in many different locations

  • This limits you from intercepting the object graphs to modify their behaviour (ie for testing replace a db with map)

  • It is not part of the UI layer, although it is easiest to create composition root here

    • can take out the layer as module, but might be tricky

DI containers

  • software library that can automate many tasks for composing objects and managing lifetimes

  • should only be used to compose object graphs

  • Only be in the composition root

    • CR should have reference to DI container

    • DICon should not be referenced by any other module

  • removal of any coupling between the DI container and app code base

  • For request based applicatoins (websites/services)

    • you configure the container once, but resolve an object graph for each incoming request

Constructor Injection

  • allows a class to statically declare its required dependencies

  • To guarantee a necessary volatile dependency is always available to the class, by requiring all callers to supply this dependency as a parameter to the class's constructor

  • pass the dependencies of a class to its constructor as parameters, thus store the reference for future use

  • The composition root will supply these dependencies, by instantiating them and injecting them

  • Should be default choice for DI

How it works

  • The class that needs the dep, needs to expose a constructor

    • This can also be done via static factory method

  • The dependency passed in to constructor should be assigned to a field

    • This allows the field (dep) to be used anywhere in the class

    • Generally, the field will be of an abstract type (depends on the patterns used)

  • The field holding the dependency should be read only (no setters)

    • Thus after object instantiation, the dependency cannot be modified

  • There should be no other logic in the constructor (see below exception for validation)

  • Dep injected into a class are few, but the depth of the object graphs might be long/deeper, due to the multiple layers of decorators

    • Have large parameter lists for constructors, can have an adverse effect on performance

Benefits

  • Clearly documents what the class requires via it's constructor parameters

  • Makes sure the class cannot create an object if it has not been passed a dependency that it needs to function

    • injection guaranteed

Bad

  • Some frameworks need to break this implementation to work

  • Some frameworks may need you to have a parameterless constructor (hibernate, ant)

    • leading to more work and configuration to setup properly

Multiple constructors

  • There are patterns which allow a class to have multiple constructors (Via telescoping) that allows defaults

    • This can allow a class to have constructors with more or less parameters than previous constructor

    • These can be helpful, especially in refactoring a class for testing

    • This can lead to ambiguity in terms of which constructor should be called or DI Con should use

  • Alternatively, can have one constructor, and have multiple explicitly named static factories which call the constructor

  • For application constructors having one constructor is preference, while in libraries having multiple is preferred

Optional dependencies

  • This can be a consequence of having multiple constructors

  • They can complicate checks for null arguments

  • Give greater flexibility in constructing a specific object

  • Best to use the builder or factory patterns

  • Can maintain a single constructor, but the composition root can inject a null object which has no behaviour

    • but code that uses this dependency must be careful of it's usage if it relies on a return type (output)

Validating constructor arguments

  • This is doing guard clauses to make sure that args are correct before constructing the object

    • So that when the dep is used elsewhere in the class no issues with null pointer

  • Two camps, one saying constructors should just be purely for assigning the reference for the dependecy to the class, and others saying should assign validation

  • I believe they should be pure with no validation, and create static factories that will do the validation before instantiating the object

    • Not all classes should need validation, only those at the edge of the system, or can be constructed at runtime (without the coders knowledge - but for to reduce failure might better to have default dependency instead of throwing an exception)

    • This might lead to issues with DI Con frameworks from not easily using it, with extra work to create (ie @bean and @configuration in Sprin)

Setter/Method Injection

  • Enables you to provide a dependency to a consumer when either the dependency or the consumer might change for each operation

    • Dependency can vary with each method call

    • Consumer of such dependency can vary on each call

  • dependencies are instantiated after the class is created.

  • Dep supplied with method parameter

  • Done outside of composition root

Good

  • Allows caller to provide operation specific context or service

  • Allows inject dep into data centric objects that are not created inside composition root

Bad

  • limited applicability

  • causes the dep to become part of the public api of a class or it's abstraction

When

  • Creating entities (DDD)

    • due to entities possibly containing lots of methods, these will require only certain dependencies

    • yes, can inject all dep via constructor, but not all will be used within each method

      • which complicates testing

Avoid

  • Dont store the dependnecy when passed in via method args

    • leads to temporal coupling, captive dependencies, hidden side effects

    • just use it or pass it on

Field/Property Injection

  • By exposing a writable property/field, that lets the caller supply a dep, if they want to override the default (one created in constructor)

  • having a public non final field, where you can inject the dependencies

  • dependencies are instantiated after the class is created

Good

  • Allow for class to be extensible

Issues

  • NPE

    • object created may have null fields, as they dont need to be assigned when object is instantiated

    • CURE: Have a default at constructor time

  • Causes temporal coupling

    • lead to inconsistent behaviour

  • Hard to impl in robust manner

  • https://www.vojtechruzicka.com/field-dependency-injection-considered-harmful/

when

  • when you have a good local default but want to extend by allowing users to provide diff impl

  • only applicable to resuable libraries

    • allows components to define sensible defaults

  • Dont use for applications

Issues with injecting too many dependencies

  • Some times having too many dependencies injected, can make the code more complex (esp for testing)

  • Things like crosscutting concerns (logging, date etc) can pollute the class.

    • Instead try using a factory, which can pass a supplier to a static factory method, and when the method of dependency is called, can call the supplier and instantiate it. Then the supplier is passed in at composition root, or a stub/fake is passed in for tests

  • https://enterprisecraftsmanship.com/posts/singleton-vs-dependency-injection/

DI anti patterns

Service locator

Control freak

Ambient Context

Constrained Constructor

Code smells

constructor over injection

abstract factories abuse

cyclic dependencies

Object lifetime

Singleton

Transient

Scoped

interception

Decorator

cross cutting

Last updated