The 7 deadly sins of Dependency Injection
Stephan Hochdörfer // 21.02.2014
About me
Stephan Hochdörfer
Head of IT , bitExpert AG (Mannheim,
Germany)
S.Hochdoerfer@bitExpert.de
@shochdoerfer
#PHP, #DevOps, #Automation
I am biased: "Doing" DI since 2005
T-Shirt anyone?
If you buy five items you'll have a 100% chance of getting into heaven! (Restrictions may apply)
The 7 deadly sins
Lust
Greed
Gluttony
Sloth
Wrath
Envy
Pride
Lust
was originally a general term for desire. Therefore lust could involve the intense desire of money, food, fame, or power as well.
Or in our case dealing with too many dependencies.
A simple controller...
class MyController
{
public function myAction ()
{
return new Response ( 'Ok!' );
}
}
...evolving over time
class MyController
{
protected $service ;
public function __construct ( MyService $service )
{
$this -> service = $service ;
}
public function myAction ()
{
$this -> service-> doSomething ();
return new Response ( 'Ok!' );
}
}
...evolving over time
class MyController
{
protected $service ;
protected $logger ;
public function __construct ( MyService $service , MyLogger $logger )
{
$this -> service = $service ;
$this -> logger = $logger ;
}
public function myAction ()
{
$this -> logger-> debug ( 'myAction running' );
$this -> service-> doSomething ();
return new Response ( 'Ok!' );
}
}
What if we need to add another dependency like MyMailer? Too many dependencies. How to solve that?
Can a ServiceLocator help?
class MyController
{
protected $service ;
protected $logger ;
public function __construct ( Container $container )
{
$this -> service = $container -> get ( 'service' );
$this -> logger = $container -> get ( 'logger' );
}
public function myAction ()
{
$this -> logger-> debug ( 'myAction running' );
$this -> service-> doSomething ();
return new Response ( 'Ok!' );
}
}
This is wrong on so many levels:
- Violates the SoC principle. The class should not be responsible for instanciating it`s dependencies. Should concentrate on the business logic!
- You added a dependency on your framework which should be avoided. Your application should not need to know about your framework!
- Looks like just one dependency, but it is not. The "dependencies" are hardcoded in strings which is really hard to search for. Try a search and replace for "service" or "logger"
- How can we be sure that we get the type we need? In the previous example we had the type hint in the constructor acting as a filter
...or some *aware magic?
class MyController implements ContainerAware
{
protected $service ;
protected $logger ;
public function setContainer ( Container $container )
{
$this -> service = $container -> get ( 'service' );
$this -> logger = $container -> get ( 'logger' );
}
public function myAction ()
{
$this -> logger-> debug ( 'myAction running' );
$this -> service-> doSomething ();
return new Response ( 'Ok!' );
}
}
Same problems as before but there`s more: When looking at the class from the outside I have no idea that it is important to call the setContainer() method. If I don`t do that I will a fatal error and I do not know why (if I do not look at the source code).
Avoid "ContainerAware"
Not needed in your application layer. Only allowed in two places: In your framework to get the controller instance and the view instance.
Btw: The container is indeed a singleton, otherwise it would not be possible to manage the instances globally. But in your application logic you should not care about the container.
Establish boundaries
Let your code be as portable as possible. Try not to depend on framework classes.
Greed
is an inordinate desire to acquire or possess more than one needs, especially with respect to material wealth.
Greedy controllers...
class UserController
{
protected $auth ;
protected $user ;
public function __construct ( AuthService $auth , UserService $user )
{
$this -> auth = $auth ;
$this -> user = $user ;
}
public function authAction ( Request $r )
{
return $this -> auth-> login ( $r -> get ( 'user' ), $r -> get ( 'pass' ));
}
public function listUserAction ()
{
return $this -> user-> readAll ();
}
}
Fat controller: You learned to put all the things related to a user into a UserController
If the only reason for you to use a SL is to reduce the amount of dependencies then you are doing it wrong.
...should be restructured
class AuthController
{
// [...]
public function authAction ( Request $r )
{
return $this -> auth-> login ( $r -> get ( 'user' ), $r -> get ( 'pass' ));
}
}
class UserController
{
// [...]
public function listUserAction ()
{
return $this -> user-> readAll ();
}
}
Rethink your architecture and avoid fat controllers!
Do not be afraid of too many classes!
Architecture is tool agnostic
Architecture and structure are agnostic of tools
If your framework forces you into a fixed structure get rid of it. As soon as possible
Don`t blame DI for amount of dependencies you have to deal with.
ServiceLocator will not fix it
A ServiceLocator will not fix your problem. It will help you to hide it.
Depend on what you need...
Greedyness also applies to the dependencies themselves. Depend on what you really need.
Depend on what you need...
class LoginController
{
protected $auth ;
public function __construct ( AuthService $auth )
{
$this -> auth = $auth ;
}
public function authAction ( Request $r )
{
return $this -> auth-> login ( $r -> get ( 'user' ), $r -> get ( 'pass' ));
}
}
Depend on abstractions not concrete implementations,
Dependency Inversion
« High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions. » - Robert C. Martin
"The Dependency Inversion Principle", Robert C. Martin
Depend on abstractions
class LoginController
{
protected $auth ;
public function __construct ( AuthInterface $auth )
{
$this -> auth = $auth ;
}
public function authAction ( Request $r )
{
return $this -> auth-> login ( $r -> get ( 'user' ), $r -> get ( 'pass' ));
}
}
Controller does not need to know how the authentication works or what backend (database, ldap, webservice, ...) is used to authenticate the user
Gluttony
is the over-indulgence and over-consumption of anything to the point of waste. Gluttony can be interpreted as selfishness; essentially placing concern with one's own interests above the well-being or interests of others.
Too many implementations
19 search result pages -> 285 packages
10 / 15 years ago devs wrote CMS to proof their skills. Today they write either another NoSQL Store or a DI container.
What`s the problem?
No common standard!
Every framework and every library does it differently. No common ground. So many missunderstandings, so many interpretations.
I have a dream
Are we there yet?
Standardization for the win!
Project started by David Négrier and Matthieu Napoli
Sloth
has also been defined as a failure to do things that one should do. By this definition, evil exists when good men fail to act.
And don`t be lazy. Do not take shortcuts, solve the problems the right way.
(Public) Property injection
class UserController
{
/**
* @var Logger
* @Inject
*/
public $logger ;
public function authAction ( Request $r )
{
if ( null !== $this -> logger) {
$this -> logger-> debug ( 'Auth action running...' );
}
}
}
public properties are a bad idea! No type check possible. Anyone can set the property to any value.
instanceof check would be better in this case!
(Protected) Property injection
class UserController
{
/**
* @var Logger
* @Inject
*/
protected $logger ;
public function authAction ( Request $r )
{
if ( null !== $this -> logger) {
$this -> logger-> debug ( 'Auth action running...' );
}
}
}
Protected and private properties can only be set via Reflection API which is rather expensive.
« NEIN! NEIN! NEIN! » - David Zülke
When looking at the class documentation I do not see that I need to set the property and in worst case I probably do not even see which type I should set it to.
Some say it is a shortcut, but in reality it is simply bad practise!
No clear communication what to inject and when!
Constructor injection
class UserController
{
protected $auth ;
public function __construct ( AuthInterface $auth )
{
$this -> auth = $auth ;
}
}
Why should I ever want to change the AuthInterface during the lifetime of the UserController?
Pass your dependencies as constructor args!
Use constructor injection whenever possible.
If your constructor has too many arguments rethink your application structure!
Setter injection
class UserController
{
protected $logger ;
public function setLogger ( Logger $logger )
{
$this -> logger = $logger ;
}
public function authAction ( Request $r )
{
if ( null !== $this -> logger) {
$this -> logger-> debug ( 'Auth action running...' );
}
}
}
Setter injection
class UserController
{
protected $logger ;
public function setLogger ( Logger $logger )
{
$this -> logger = $logger ;
}
public function authAction ( Request $r )
{
if ( $this -> logger instanceof Logger) {
$this -> logger-> debug ( 'Auth action running...' );
}
}
}
Some say Setter injection is useless. But that is not true. Can be useful for optional dependencies, e.g. things that change for different installs or the logger which can be turned on and off.
Null checks everywhere!
Setter injection means null checks everywhere!
Circular dependency hell
class UserService
{
protected $auth ;
public function __construct ( AuthInterface $auth )
{
$this -> auth = $auth ;
}
}
class SimpleAuthService implements AuthInterface
{
protected $userService ;
public function __construct ( UserService $userService )
{
$this -> userService = $userService ;
}
}
Circular dependency hell
class UserService
{
protected $auth ;
public function __construct ( AuthInterface $auth )
{
$this -> auth = $auth ;
}
}
class SimpleAuthService implements AuthInterface
{
protected $userService ;
public function setUserService ( UserService $userService )
{
$this -> userService = $userService ;
}
}
Avoid circular dependencies!
Interface injection
interface LoggerAware
{
public function setLogger ( Logger $logger );
}
class UserController implements LoggerAware
{
protected $logger ;
public function setLogger ( Logger $logger )
{
$this -> logger = $logger ;
}
public function authAction ( Request $r )
{
if ( null !== $this -> logger) {
$this -> logger-> debug ( 'Auth action running...' );
}
}
}
Looks quite similar to setter injection because we use setter methods to inject the dependencies. But in fact it is slightly different.
Implicit wiring
No configuration needed -> leads to less configuration code.
On the other hand you need to be aware what happens in the dark
Interface injection is "forced" setter injection.
Default dependencies?
class AuthService implements AuthInterface
{
protected $provider ;
public function __construct ( AuthProviderInterface $provider )
{
$this -> provider = $provider ;
}
}
Imaging you want to share your lib with others. How do they know what to inject? Better provide a default implementation...
Default dependencies?
class AuthService implements AuthInterface
{
protected $provider ;
public function __construct ( AuthProviderInterface $provider )
{
$this -> provider = $provider ;
}
public static function getInstance ()
{
return new AuthService ( new DatabaseAuthProvider ());
}
}
Separating the concerns
class AuthService implements AuthInterface
{
protected $provider ;
public function __construct ( AuthProviderInterface $provider )
{
$this -> provider = $provider ;
}
}
class AuthServiceFactory
{
public static function getInstance ()
{
return new AuthService ( new DatabaseAuthProvider ());
}
}
Wrath
also known as "rage", may be described as inordinate and uncontrolled feelings of hatred and anger. Wrath is the only sin not necessarily associated with selfishness or self-interest, although one can of course be wrathful for selfish reasons, such as jealousy.
DIC vs. SL
What is the difference?
My "favourite" topic ;)
Endless discussions...
ServiceLocator
interface MyServiceLocator
{
/**
* Returns an entry of the container by its $id.
*
* @param string $id
* @throws NotFoundException
* @return mixed
*/
public function get ( $id );
/**
* Returns true if the container can return an entry
* for the given identifier. Returns false otherwise.
*
* @param string $id
* @return bool
*/
public function has ( $id );
}
DIC
interface MyDependencyContainer
{
/**
* Returns an entry of the container by its $id.
*
* @param string $id
* @throws NotFoundException
* @return mixed
*/
public function get ( $id );
/**
* Returns true if the container can return an entry
* for the given identifier. Returns false otherwise.
*
* @param string $id
* @return bool
*/
public function has ( $id );
}
Interface Segregation Principle claims: "Clients should not be forced to depend upon interfaces that they do not use."
DIC vs. SL - The difference?
Push vs. Pull
From a consumer`s point of view:
SL: Pulling services from
DI: Will push the dependencies into the class
Using a ServiceLocator
class MyController
{
protected $service ;
protected $logger ;
public function __construct ( MyServiceLocator $locator )
{
$this -> service = $locator -> get ( 'service' );
$this -> logger = $locator -> get ( 'logger' );
}
}
Downside: You make your code dependant of the ServiceLocator. Thight coupling. No idea what to get back. Could be any type of object or scalar value!
Using a DIC
class MyController
{
protected $service ;
protected $logger ;
public function __construct ( MyService $service , MyLogger $logger )
{
$this -> service = $service ;
$this -> logger = $logger ;
}
}
No framework dependency. Clean code. Maximum reuse!
Envy
is characterized by an insatiable desire. Envy is similar to jealousy in that they both feel discontent towards someone's traits, status, abilities, or rewards. The difference is the envious also desire the entity and covet it.
Types of configuration
internal Configuration
vs.
external Configuration
Some say that only internal configuration is needed while other`s agree that both make sense and you cannot live without either of them.
Internal Configuration
class AuthService implements AuthInterface
{
protected $provider ;
/**
* @Inject
*/
public function __construct ( AuthProviderInterface $provider )
{
$this -> provider = $provider ;
}
}
In most cases this is easy as we just have one implementation of the AuthProviderInterface. But what happens if we have more of them?
Internal Configuration
class AuthService implements AuthInterface
{
protected $provider ;
/**
* @Inject
* @Named('RESTAuthProvider')
*/
public function __construct ( AuthProviderInterface $provider )
{
$this -> provider = $provider ;
}
}
That way we bind explictly the AuthService to the RESTAuthProvider. Any time I create an instance of AuthService the RESTAuthProvider will be injected. I cannot do anything about it. And since the AuthService is immutable I cannot change it later on.
Internal Configuration
/**
* @ImplementedBy(RESTAuthProvider)
*/
interface AuthProviderInterface
{
/**
* Tries to authenticate the user.
*
* @return bool
*/
public function authenticate ( $username , $password );
}
Other idea: Bind default implementation to interface. Anywhere the interface is type-hinted, the default implementation (RESTAuthProvider) will be injected.
External Configuration
services:
authprovider:
class: RESTAuthProvider
authservice:
class: AuthService
arguments: ["@authprovider"]
External Configuration
services:
provider_rest:
class: RESTAuthProvider
provider_soap:
class: SoapAuthProvider
authservice_rest:
class: AuthService
arguments: ["@provider_rest"]
authservice_soap:
class: AuthService
arguments: ["@provider_soap"]
One class, multiple configurations. Getting rid of code by using configuration code.
Both ways make sense...
A mix of internal and external configuration can make sense.
Depends on what you want to achieve.
...pick what makes sense!
$dep = new \Di\Definition\Builder\PhpClass ();
$dep -> setName ( 'RESTAuthProvider' );
$class = new \Di\Definition\Builder\PhpClass ();
$class -> setName ( 'AuthService' );
$im = new \Di\Definition\Builder\InjectionMethod ();
$im -> setName ( '__construct' );
$im -> addParameter ( 'provider' , 'RESTAuthProvider' );
$class -> addInjectionMethod ( $im );
$builder = new \Di\Definition\BuilderDefinition ();
$builder -> addClass ( $dep );
$builder -> addClass ( $class );
$defList = new \Di\DefinitionList ( $builder );
$di = new \Di\DiContainer ( $defList );
new AuthService ( new RESTAuthProvider ());
Configuration is too complex?
If your Container configuration is more complex than manually wiring the deps, you are using the wrong container!
DI is not slow!
DI is not slow. Use a PHP based configuration!
No calls to the Reflection API!
Pride
is considered the original and most serious of the seven deadly sins, and the source of the others. It is identified as believing that one is essentially better than others.
Choose wisely
I am not saying that SL is bad or that tightly coupled components are bad. In some situations it is enough to get things done and this is fine. No over-engeering.
But on the other hand understand why DI is good why it makes sense to use and use it that way.
Learn to love it
DI is a kind of lifestyle.
Famous final words
« Dependency Injection is a key element of agile architecture » - Ward Cunningham
DI forces you to use interfaces which helps you to exchange implementations later on pretty easily.