Dependency Injection in Net

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 8

1

Dependency Injection in .NET


By Mark Seemann

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.

You may also be interested in…

Dealing with constructor over-injection


Unless you have special requirements, Constructor Injection should be your preferred injection pattern. However,
some people get uncomfortable when the number of Dependencies grows. They do not like a constructor with too
many parameters.
In this article, we will look at the apparent problem of a growing number of constructor parameters, and why
this is actually a good thing rather than a bad thing. As we will see, it does not mean that we should accept long
parameter lists in constructors, so we will also review what we can do about too many constructor arguments. An
example rounds off the article.

Recognizing and addressing constructor over-injection


When a constructor’s parameter list grows too large we call the phenomenon constructor over-injection 1 and
consider it a code smell. It is a general code smell unrelated to, but magnified by, DI.
Although our initial reaction might be that we do not like Constructor Injection because of constructor over-
injection, we should really be thankful that a general design issue is revealed to us.
We will first take a moment to appreciate how constructor over-injection makes our lives a little bit easier and,
secondly, consider appropriate reactions.

Constructor over-injection as signals


Although Constructor Injection is easy to implement and use, it makes people uncomfortable when their
constructors start looking like this:

public MyClass(IUnitOfWorkFactory uowFactory,


CurrencyProvider currencyProvider,
IFooPolicy fooPolicy,
IBarService barService,
ICoffeeMaker coffeeMaker,
IKitchenSink kitchenSink)

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.

Refactoring to aggregate services


There are many ways we can design and implement collaborators so that they do not violate the Single
Responsibility Principle. The Decorator 3 design pattern can help us stack Cross-Cutting Concerns instead of
injecting them into consumers as services. This can take away a lot of constructor arguments.
Still, there are scenarios where a single entry point needs to orchestrate a lot of different Dependencies. One
example is a web service operation that triggers a complex interaction of many different services. The entry point
of a scheduled batch job may face the same issue.
Figure 1 shows how we can refactor key relationships into 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

Dependency Dependency Dependency Dependency Dependency


A B C D E

Consumer

Aggregate 1 Aggregate 2

Dependency Dependency Dependency Dependency Dependency


A B C D E

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.

Example: refactoring order reception


The sample commerce application we’ll feature here needs to be able to receive orders. This is often best done by a
separate application or subsystem because, at that point, the semantics of the transaction change.
As long as we are looking at a shopping basket, we can dynamically calculate unit prices, exchange rates, and
discounts but, when a customer places an order, all those values must be captured and frozen as they were
presented when they approved the order. Table 1 provides an overview of the order reception process.

Table 1 When the order subsystem receives a new order, it must perform a number of different actions.

Action Required Dependencies

Save the order OrderRepository

Send a receipt email to the customer IMessageService

Notify the accounting system about the invoice amount IBillingSystem

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.

Too many fine-grained Dependencies


If we let OrderService directly consume all five Dependencies, the structure is as shown in figure 2.

OrderService

OrderRepository IBillingSystem ILocationService

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.

Refactoring to aggregate services


The first thing we need to do is look after natural clusters of interaction to identify potential aggregate services.
The interaction between ILocationService and IInventoryManagement immediately draws our attention
because we use them to find the closest warehouses that can fulfill the order. This could potentially be a complex
algorithm but, once we have selected the warehouses, we just need to notify them about the order.
If we think about this a little further, ILocationService is just an implementation detail of notifying the
appropriate warehouses about the order. The entire interaction can be hidden behind an IOrderFulfillment
interface as shown in figure 3. Interestingly, order fulfillment sounds a lot like a domain concept in its own right;
chances are that we just managed to discover an implicit domain concept and make it explicit.

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:

public interface INotificationService


{
void OrderAdded(Order order);
}

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

IMessageService IBillingSystem IOrderFulfillment

MessageNotification BillingNotification OrderFulfillmentNotification

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:

The Art of Unit Testing


With examples in .NET
Roy Osherove

Brownfield Application Development in .NET


Kyle Baley and Donald Belcham

Continuous Integration in .NET


Marcin Kawalerowicz and Craig Berntson

ASP.NET MVC in Action


Jeffrey Palermo, Ben Scheirman, and Jimmy Bogard

C# in Depth, Second Edition


Jon Skeet

Last updated: March 29, 2011

For Source Code, Sample Chapters, the Author Forum and other resources, go to
http://www.manning.com/seemann/

You might also like