I’ve been worried lately about doing dependency injection in python
Having programmed largely in C#, and being used to decouple things via constructor injection, with the aid of an Inversion of Control (IoC) container, I’ve tried to apply the same patterns in the python world, but somehow I’ve never felt this was really pythonic.
The lack of a community accepted IoC container also felt like a warning smell
Continuing with the IoC problem, first thing one does is to STFW, and that means google and stackoverflow. What I’ve found, phrased diversely, in summary is:
you don not need a DI container in python.
Yeah, but I don’t want to loose all the benefits that I was used to get from DI. How do you do it in python?
What is inversion of control and dependency injection and why we need it?
Going back to the classics, I’ll use Fowler’s example with a liberal translation to python.
In Fowler’s example an application obtains a list of movies directed by someone.
It does that with the aid of a service MovieLister. This service is the one that has the business logic.
In the whole example this is reduced to the simplistic minimum that serves to exemplify the necessary architecture, but fits in a blog post. Here, the business logic is just filtering by director.
This class depends on another service MovieFinder from the module finder
which, in turn, depends on the model Movie from the module movies
Again, to simplify, here the MovieFinder obtains a list of movies that is simply hardcoded on the module. In a real world situation, the finder module whould use a database or an external service to fetch the data, here I opted to hardcode some movies from the Robert Zemeckis entry on wikipedia.
This approach works as expected:
so maybe it is time to pause and watch that movie. I strongly recomed you to do it right now!
So, what’s the problem?
Still here? Have you watched the movie? I’ll wait.
Ok, let’s do some testing.
Yeah, I know, I know. Forget for a moment that we should have started by doing the tests in the first place, and concede me this little sin for the shake of the story telling.
We can easily write a test
that breaks
The problem is that code breaks the test even when the implementation of the movies_directed_by method is correct!
The problem is that we can’t test it, because MovieFinder is hard wired onto MovieLister.
We say that MovieListerdepends on MovieFinder.
Monkey patching
In python (and dynamic languages in general) we can temporaly circumvent this and use a patch to mock the dependency during the test.
See:
This monkey patching trickery allows us to write some tests and can be a great option in the case that you’ll have a dependency on some module that you don’t own and that you can’t modify.
But the dependencies are still wrong.
Do dependencies exist when you’re not watching?
The current situation is as follows.
MovieLister depends on MovieFinder, that, in turn, depends on a specific backend (simplified here with hardcoded data, but imagine a database instead).
The application depends on the MovieLister and, transitively, on the MovieFinder and on the database also.
Abstractions
If you look carefully, though, there’s a subtlety here.
MovieListeronly depends on some abstraction that is named find_all. Python being dynamic and lightweight often hides this subtlety, but it exists anyway.
The need to patch this exact dependency on the test to isolate MovieLister and be able to unit test it, supports this point of view.
Actually, the lister module also depends on the Movie class from the movies module.
We can make this a little bit more explicit by adding some typing:
In a language with a type system as Haskell’s, the compiler will infer that abstraction during the static type analysis, without needing type annotations when compiling.
It will infer a typeclass that has a signature (converted to pythonic syntax) like
In languages like C# or Java this will correspond to an interface and on dynamic languages this is usally refered to as duck typing.
In any case, I do think the abstractions exist.
From the design viewpoint, it does not matter much if the compiler is able to detect them or not. For me, the important point is that when we humans detect them, we seem to be able to write much better code. Better in the sense that the code does what we expect the code to do.
So, yes, dependencies exist when we’re not watching. Abstractions exist even when we’re not watching.
Making the dependencies explicit
The dependencies are like those in the picture:
With this insight, we adapt the code to explicitly communicate this.
Modeling abstractions
Python doesn’t have intefaces, but we can use an abstract class with an empty method that represents our abstraction. Following some C#ish style naming conventions we prepend an I to the class name to denote that interface intent (this is not idiomatic python code, is just a convention we found usefull).
The IMovieFinder abstraction gets represented as
Next, we modify MovieFinder to implement this behaviour
Now the code reflects what dependencies it has and who depends on who.
We can summarize the dependencies on the following table
Class
Depends on
MovieLister
IMovieFinder
MovieLister
MovieFinder
MovieFinder
IMovieFinder
According to the dependency inversion principle we’ve got some things wrong.
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.
Clearly, we’ve got the second dependency on the table wrong: the MovieLister module is depending on MovieFinder that is a lower level module.
We can solve this by _inverting the dependency_.
Lower?
When we say that the module MovieLister is a lower level module than MovieFinder we mean that it knows more things about the real implementation.
High level modules and classes express the business use cases in terms of objects, modules and classes in the domain itself, independently of how those are implmented.
MovieLister doesn’t care if you’re using a mock implementation, an implementation that extracts data from a hardcoded array on memory or an implementation that queries a real database or service.
On the contrary, MovieFinder does. So it is lower level: it has more details and is less abstract.
How to invert a dependency
A simple way to invert a dependency is removing the call site where it is constructed.
We can remove the dependency of MovieLister on the MovieFinder module, by requiring the dependency to be passed in from outside instead of instantiating it on the module.
Now the module only depends on the high level abstraction IMovieFinder, but not in MovieFinder anymore, so we can get rid of the import also.
Now the dependencies are right:
Is obvious from the diagram that the MovieFinder implementation doesn’t need to know anything about the domain except IMovieFinder (and Movie).
Better still, the domain don’t need to know even that MovieFinder exists.
Testable code
This enables testing automatically. We can simply mock the dependency and inject the mock. There’s no need to patch anything anymore:
Guards
We can even assert at runtime that the dependency is fullfilled. First we add the edge cases tests:
and we can narrow the interface as much as we want via guards or assertions on the initializer:
Push behaviour to the domain
Having broken the dependency enables us to push the behaviour further and put it all inside the domain.
We can create a new object UseCase that knows everything about the abstractions that conform the use case, but nothing about the current implementations.
Only in the application layer the system is assembled with concrete objects that fullfill the abstractions needed by the use cases.
Who will assemble the application layer is what motivated the post in the first place, and now we are just where we’ve started.
In this simple example all the stuff we’ve done is probably overkill, but in a more realistic situacion, where a the application is interacting with some other applications (probably not ours), the hability to test the business logic without needing to have the whole setup is crucial.
We can easily imagine a system that has more dependencies. For instance, just adding a simple database will increase the boilerplate to set up the application:
DI is also beneficial in python
We don’t yet know how to solve the IoC stuff yet, but the benefits of DI can be used in python also. In short:
We managed to invert dependencies (with a little bit of effort and uglyness.