DI impact on OO
This throws away a bigger part of the benefits
Since we inject stateless objects only, these objects (services) do not comply with the concept of an object in object-oriented programming. This is not really an issue if the dependency injection is restricted to a few central services which implement e.g. a thin layer of stateless activity classes which orchestrate the functionality implemented in the domain model, implement transaction handling and maybe cross cutting functionality beyond the domain model.
But remember: we use dependency injection mainly because we want to be able to exchange implementations without having to adapt anything else. If we use dependency injection just for a few central services, then is it worth it? How do we exchange the implementations in our rich domain model? In order to be able to exchange all implementations we will have to move the behavior from the domain model to stateless services.
This means we separate data from behavior and leave the domain model as an (almost) pure data structure, an anemic domain model, which does not comply with the concepts of object-oriented programming. As a natural consequence, these stateless services will have to operate directly on the data of the domain model and therefore cannot avoid to chain getter calls and to nest iterations over and over. In object-oriented design this is a violation of the law of demeter at least.
And this is exactly what happens in so many business applications: an (almost) pure data structure aka anemic domain model on one side and stateless services with procedural code on the other side. This throws away a bigger part of the benefits provided by an object-oriented language, but runs at the cost of the overhead of that object-oriented language. Implementing procedural code is probably easier and faster with a procedural language.
While this may happen for a couple of reasons (including but not limited to the challenge to get used to the way of thinking a "new" paradigm requires) dependency injection plays its role here because it does not allow for an object-oriented design if used throughout the entire application.
Thus, for the purpose of exchanging implementations in a rich domain model another pattern must be choosen.
And again: If we use dependency injection just for a few central services, then is it worth it?
Of course I'm not the first one and certainly not last one writing about this topic, although it seems hard to find publications in this direction. Also, most people blame the dependency injection frameworks, but not the pattern itself. While writing this article I found just very few publications about this topic. (Please note, that I do not fully agree with everything written in the following publications.)
An interesting introduction to the topic can be found in the Blog of Adam Warski:
Dependency injection discourages object-oriented programming?
More to the point and more entertaining to read is an article from David Green:
Your DI framework is killing your code
See also this thread on stackexchange.com:
Does Inversion of Control promote Anemic Domain Model?
Knowing that dependency injection destroys object-oriented design, which are the alternatives?
Well, choosing the right creational pattern should depend on the applications functional and nonfunctional requirements, already given application frameworks etc.
There are some older patterns like abstract factory or service locator. These patterns had been subject to some solid criticism as well, what might be one reason why dependency injection became more famous. This article is not going to warm-up these discussions. Instead let's have a look at newer alternatives:
A matured project which still uses dependency injection could probably migrate quite easily to the factory injection pattern. For the factory injection pattern it is not required to abandon the dependency injection framework in use, the project can indirectly still exchange implementations by inversion of control on a higher level, but the client classes get back control over the dependencies itself. The latter means that factory injection is not dependency injection. To be able to manage rich domain model objects by the container, the actual parameters must be passed to the factory for the object creation. If the framework does not support this, then you cannot create a container managed object that requires parameters for its construction without violating the encapsulation (e.g. by setting the initial state inside the factory). This is not a restriction of the pattern but the framework. With that restriction the container could be used for a thin layer of activities which orchestrate the functionality implemented in the domain model and implement transaction handling and holders which hold the domain model. The domain model objects itself are then created directly by the factories (e.g. like in the abstract factory pattern) and not pulled from the container, but derive their lifecycle from the holders. The domain model could use stateless objects (without construction arguments) from the container which implement cross-cutting functionality. This way the factory injection pattern enables to integrate container managed objects with unmanaged objects allowing to exchange the implementations of both managed and unmanaged ones.
A new or younger project could choose an even better pattern: the creator pattern.
In the creator pattern universe there is no issue with object-oriented design. All kinds of objects are created in a very natural way under the full control by the client classes, but allowing for exchanging implementations, even dynamically by using contexts made of dynamic values or compositions. This makes it a very powerful creational pattern! Creator classes and factories are implemented or generated with full compiler support instead of writing proprietary configurations in a DSL. There's also a creator framework available for this pattern.
About the author
Pages |