Professional Documents
Culture Documents
Dependency Injection in Net
Dependency Injection in Net
Dependency Injection in Net
As .NET developers embrace the practice of dependency injection they’re discovering that
many of the established patterns from the Java world are equally applicable to their work.
In this article from Dependency Injection in .NET, author Mark Seemann explores the
Constructor Injection pattern and tackles the tricky code smell of constructor over-injection.
I can’t say I blame anyone for disliking such a constructor, but do not blame Constructor Injection. While we
can agree that a constructor with six parameters is a code smell, it indicates a violation of the Single Responsibility
Principle rather than a problem related to DI.
TIP
1
Palermo, Jeffrey. Constructor over-injection smell – follow up. 2010. http://jeffreypalermo.com/blog/constructor-over-injection-smell-ndash-
follow-up/
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/
2
Constructor Injection makes it very easy to spot Single Responsibility Principle violations.
Instead of feeling uneasy about constructor over-injection, we should embrace it as a fortunate side-effect of
Constructor Injection. It is a signal that alerts us whenever a class takes on too much responsibility.
My personal threshold lies at four constructor arguments. Whenever I add a third argument, I already start
considering whether I could design things differently, but I can live with four arguments for a few classes. Your
limit may be different but, once we cross it, it is time to refactor.
How you refactor a particular class that has grown too big depends on the particular circumstances; the object
model already in place, the domain, business logic, and so on. Splitting up a budding God Class 2 into smaller, more
focused classes according to well-known design patterns is always a good move.
Still, there are cases where business requirements oblige us to do a lot of different things at the same time.
This is often the case at the boundary of an application. Think about a coarse-grained web service operation that
triggers a lot of business events.
One way to model such operations is by hiding the myriad Dependencies behind aggregate services.
2
Brown, William J, et al. AntiPatterns. Refactoring Software, Architectures, and Projects in Crisis. Wiley Computer Publishing, 1998. p. 73.
3
Gamma, Erich, et al. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994. p. 175.
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/
3
Consumer
Consumer
Aggregate 1 Aggregate 2
Figure 1 In the top diagram, the consumer has five Dependencies, which is a strong indication that it violates the Single
Responsibility Principle. Still, if the role of the consumer is to orchestrate those five Dependencies, we cannot throw any away.
Instead, we can introduce aggregate services that orchestrate parts of the relationship. In the bottom diagram, the consumer has
only two Dependencies, and the aggregates have two and three Dependencies.
Refactoring to aggregate services is more than just a party trick to get rid of too many Dependencies. The key
is to identify natural clusters of interaction. In figure 1, it turns out that Dependencies A through C form a natural
cluster of interaction, and so do D and E.
A beneficial side-effect is that discovering these natural clusters draws previously undiscovered relations and
domain concepts out in the open. In the process, we turn implicit concepts into explicit concepts 4.
Each aggregate then becomes a service that captures this interaction on a higher level, and the consumer’s
single responsibility becomes to orchestrate these high-level services.
NOTE
Aggregate services are simply abstract Facades 5.
Aggregate services are related to parameter objects 6 but, instead of combining and exposing its components, an
aggregate service exposes only the encapsulated behavior while hiding the constituents.
Obviously, we can repeat this refactoring if we have such a complex application that even the consumer ends
up with too many Dependencies on aggregate services. Aggregating aggregate services is a perfectly sensible thing
to do.
4
Evans, Eric. Domain-Driven Design. Tackling Complexity in the Heart of Software. Addison-Wesley, 2004. pp. 206-223.
5
Gamma, Erich, et al. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994. p. 185.
6
Fowler, Martin, et al. Refactoring: Improving the Design of Existing Code. Addison-Wesley, 1999. p. 295.
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/
4
Close to the boundary of our application (for example, the UI or a web service) we can operate with a set of
rather coarse-grained Abstractions. As we examine the implementations of Dependencies, we see that, behind
coarse-grained services are finer-grained services, combined of even finer-grained services. This enables us to
quickly get an overview at the entry level while ensuring that each final implementation adheres to the Single
Responsibility Principle.
Let us look at an example.
Table 1 When the order subsystem receives a new order, it must perform a number of different actions.
Select the best warehouses to pick and ship the order based on the ILocationService,
items in the order and proximity to the shipping address IInventoryManagement
Ask the selected warehouses to pick and ship the whole or parts of the IInventoryManagement
order
Five different Dependencies are required just to receive an order. Imagine which other Dependencies we would
need to handle other order-related operations!
Let us first review how this would look if the consuming OrderService class directly imports all these
Dependencies; subsequently, we will see how we can refactor the functionality by using aggregate services.
OrderService
IMessageService IInventoryManagement
Figure 2 OrderService has five direct Dependencies, indicating that is violates the Single Responsibility Principle.
If we use Constructor Injection for the OrderService class (which we should), we will have a constructor with
five parameters. This is too many and indicates that the OrderService has too many responsibilities. On the
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/
5
other hand, all these Dependencies are required because the OrderService class must implement all of the
desired functionality when it receives a new order.
We can address this issue by redesigning the OrderService.
LocationOrderFulfillment IOrderFulfillment
IInventoryManagement ILocationService
Figure 3 The interaction between IInventoryManagement and ILocationService is implemented in the LocationOrderFulfillment class
that implements the IOrderFulfillment interface. Consumers of the IOrderFulfillment interface would have no idea that the
implementation has two Dependencies.
The default implementation of IOrderFulfillment consumes the two original Dependencies, so it would
have a constructor with two parameters, which is absolutely fine. As a further benefit, we have now encapsulated
the algorithm for finding the best warehouse for a given order into a reusable component.
This refactoring would collapse two Dependencies into one, but still leave us with four Dependencies of the
OrderService class. We need to look for other opportunities to aggregate Dependencies.
The next thing we might notice is that all of the requirements involve notifying other systems about the order.
This suggests that we can define a common Abstraction that models notifications—perhaps something like this:
TIP
The Domain Event design pattern is another good alternative in this scenario.
Each notification to an external system can be implemented using this interface. We could even consider wrapping
OrderRepository in INotificationService but it is likely that the OrderService class will need access to
other methods on OrderRepository to implement other functionality. Figure 4 shows that we implement the other
notifications using INotificationService.
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/
6
INotificationService
Figure 4 Every notification to an external system can be hidden behind the INotificationService - even the new IOrderFulfillment
interface we just introduced.
You may wonder how this helps us since we have just wrapped each Dependency in a new interface. The
number of Dependencies did not decrease, so did we gain anything?
Yes. Since all three notifications implement the same interface, we can wrap them in a Composite 7. This is
another implementation of INotificationService that decorates a collection of INotificationService
instances and invokes the OrderAdded method on them all.
From a conceptual perspective, this also makes much sense since, on the high-level perspective, we do not
really care about the details of how OrderService notifies other systems. However, we do care that it does.
Figure 5 shows the final Dependencies of OrderService.
OrderService
OrderRepository INotificationService
CompositeNotificationService
Figure 5 The final OrderService with refactored Dependencies. We decide to keep OrderRepository as a separate Dependency
because we need its other methods to implement other functionality of OrderService. All the other notifications are hidden behind
the INotificationService interface. At runtime, we use a CompositeNotificationService that contains the other three notifications.
This reduces OrderService to only two Dependencies, a much more reasonable number. Functionality is
unchanged, making this a true refactoring. On the other hand, the conceptual level of OrderService has
changed. Its responsibility now is to receive an order, save it, and notify other systems. The details of which
systems are notified and how this is implemented has been pushed down to a more detailed level.
7
Gamma, Erich, et al. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994. p. 163.
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/
7
Even though we consistently use Constructor Injection throughout, no single class’s constructor ends up
requiring more than two parameters (CompositeNotificationService takes an
IEnumerable<INotificationService> as a single argument).
Summary
Constructor over-injection is not a problem related to DI in general or Constructor Injection specifically. Rather, it
is a signal that the class in question has too many responsibilities. The code smell comes from the class, not
Constructor Injection and, as always, we should regard the smell as an opportunity to improve the code.
There are many ways we can refactor the patterns, but one option is to introduce aggregate services that model
concepts at a higher abstraction level. This addresses the violation of the Single Responsibility Principle and often
draws out previously undiscovered domain concepts in the process.
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/
8
Here are some other Manning titles you might be interested in:
For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/