A comprehensive example
2.3 Expanding the sample application
2.3.2 Basket feature
The list of featured products only presents us with a limited level of complexity: there’s only a single Repository involved in a read-only scenario.
Humble Object
It isn’t only for educational purposes that I split up the application into a User Inter- face Layer and a Presentation Model layer; I routinely do this for all applications I write if they have a user interface at all.
This split provides clear separation of concerns between presentation logic (how user interfaces behave) and rendering (how user interfaces look). It puts all logic into a layer where it can be unit tested, and puts all markup in a layer where a graphic designer can work without fear of breaking things too easily.
The goal is to have as little imperative code as possible in the User Interface Layer, because I’m not going to write any unit tests for this layer.
An application root that contains only the bare minimum of code to bootstrap itself, after which it delegates all other work to TESTABLE modules, is called a Humble Object.12
In this case, it contains only Views and bootstrap code: the COMPOSITION ROOT.
12Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code (New York: Addison-Wesley, 2007), 695-708.
Figure 2.22 A Presentation Model layer is inserted into the sample application to separate the presentation logic from the application root.
55
Expanding the sample application
The logical next step is to introduce a shopping basket feature. Figure 2.23 shows a screenshot of the shopping basket in use.
To support a shopping basket for each user, I need a Basket, a BasketRepository, and a host of supporting classes. If you’re like me, you want to see the Basket class first: figure 2.24 shows the basket and its list of items.
From a DI perspective, the Basket and Extent classes aren’t particularly interesting: they’re both Plain Old CLR Object (POCO) classes with no DEPEN-
DENCIES. Of much more interest is the BasketService
and supporting classes, shown in figure 2.25.
A BasketService can be used to retrieve a user’s Basket and apply discounts. It uses the abstract BasketRepository to get the contents of the Basket, and the abstract BasketDiscountPolicy to apply discounts. Both of these ABSTRACTIONS are
injected into the BasketService via CONSTRUCTOR INJECTION:
Figure 2.23 The spectacularly feature-poor shopping basket in the refactored commerce sample application.
Figure 2.24 Basket and its Contents, which is a list of Extent<Evaluated- Product>. An Extent represents a quantity of a given product.
Figure 2.25 BasketService and supporting classes. A BasketService can retrieve and evaluate a Basket for a given user. It uses a BasketRepository to retrieve the Basket and a
public BasketService(BasketRepository repository, BasketDiscountPolicy discountPolicy)
A BasketDiscountPolicy can be a simple implementation with a hard-coded policy, such as giving preferred customers a five percent discount, as we saw earlier in this chap- ter. This policy is implemented by the DefaultProductDiscountPolicy, while a more complex, data-driven implementation is provided by RepositoryBasketDiscountPolicy, that itself uses the abstract DiscountRepository to get a list of discounted products. ThisABSTRACTION is once again injected into the RepositoryBasketDiscountPolicy via
CONSTRUCTOR INJECTION:
public RepositoryBasketDiscountPolicy(DiscountRepository repository)
To manage all this, I can use the BasketService to orchestrate the operations on the Basket: adding items, as well as displaying and emptying the Basket. To do this, it needs both a BasketRepository and a BasketDiscountPolicy that (you guessed it) is supplied to it via its constructor:
public BasketService(BasketRepository repository, BasketDiscountPolicy discountPolicy)
To further complicate matters, I need an ASP.NET MVC controller called Basket- Controller that wraps around the IBasketService interface that I again inject into it via its constructor:
public BasketController(IBasketService basketService)
As figure 2.25 shows, the BasketService class implements IBasketService, so that’s the implementation we use.
The BasketController is ultimately created by a custom IControllerFactory, so it will need these ABSTRACTIONS as well.
If you lost track along the way, figure 2.26 shows a diagram that illustrates how the
DEPENDENCIES are to be composed in the final application.
Figure 2.26 Composition of the sample commerce application with the added Basket feature, as well as the original list of featured products on the front page. Each class encapsulates its contents, and only the COMPOSITION ROOT has knowledge of all DEPENDENCIES.
57
Summary
The custom IControllerFactory creates instances of BasketController and Home- Controller by providing them with their respective DEPENDENCIES. The Basket-
Service, for instance, uses the supplied BasketDiscountPolicy instance to apply a discount policy to the basket:
var discountedBasket = this.discountPolicy.Apply(b);
It has no inkling that in this case, the supplied BasketDiscountPolicy is an instance of RepositoryBasketDiscountPolicy that itself is a container of a DiscountRepository. This expanded sample application serves as the basis for many of the code samples in the rest of the book.
2.4
Summary
It’s surprisingly easy to write tightly coupled code. Even when Mary set out with the express intent of writing a three-layer application, it turned into a largely monolithic piece of Spaghetti Code13 (when we’re talking about layering, we call this Lasagna).
One of the many reasons that it’s so easy to write tightly coupled code is that both the language features and our tools already pull us in that direction. If we need a new instance of an object, we can use the new keyword, and if we don’t have a reference to the required assembly, Visual Studio makes it easy to add it.
However, every time we use the new keyword, we introduce a tight coupling. The best way to minimize the use of new is to use the CONSTRUCTOR INJECTION design
pattern whenever we need an instance of a DEPENDENCY. The second example in the
chapter demonstrated how to re-implement Mary’s application by programming to
ABSTRACTIONS instead of concrete classes.
CONSTRUCTOR INJECTION is an example of INVERSION OF CONTROL because we invert
the control over DEPENDENCIES. Instead of creating instances with the new keyword, we
delegate that responsibility to a third party. As we shall see in the next chapter, we call this place the COMPOSITION ROOT. This is where we compose all the loosely coupled
classes into an application.
13William J. Brown et al., AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis (New York: Wiley
58
DI Containers
When I was a kid, my mother and I would occasionally make ice cream. This didn’t happen too often, because it required a lot of work and it was hard to get right. In case you’ve never tried making ice cream, figure 3.1 illustrates the process.
Real ice cream is based on a crème anglaise, which is a light custard made from sugar, egg yolks, and milk or cream. If heated too much, this mixture will curdle. Even if you manage to avoid this, the next phase presents more problems. Left alone in the freezer, the cream mixture will crystallize, so you have to stir it at regu- lar intervals until it becomes so stiff that this is no longer possible. Only then will you have a good, homemade ice cream.
Although this is a slow and labor-intensive process, if you want to and you have the necessary ingredients and equipment, you can use the technique I’ve outlined to make ice cream.
Menu
■ XML configuration ■ Code as configuration ■ AUTO-REGISTRATION
■ COMPOSITION ROOT
59
DI Containers
Today, some 30 years later, my mother-in-law makes ice cream with a frequency unmatched by my mother and me at much younger ages—not because she loves mak-
ing ice cream, but because she uses technology to help her. The technique is still the
same, but instead of regularly taking out the ice cream from the freezer and stirring it, she uses an electric ice cream maker to do the work for her (see figure 3.2).
DI is first and foremost a technique, but you can use technology to make things easier. In part 3, I’ll describe DI as a technique. Then, in part 4, we’ll take a look at the technology that can be used to support the DI technique. We call this technology DI CONTAINERS.
In this chapter, we’ll look at DI CONTAINERS as a concept: how they fit into the over-
all topic of DI, some patterns and practices concerning their usage, and some history about .NETDI CONTAINERS. We’ll also look at some examples along the way.
The general outline of the chapter is illustrated by figure 3.3. It begins with a general introduction to DI CONTAINERS, including a description of a concept called
AUTO-WIRING, followed by a section on various configuration options. You can read Figure 3.1 Making ice cream is an arduous process, with plenty of opportunities for error.
about each of these configuration options in isolation, but I think it would be beneficial to at least read about CODEAS CONFIGURATION before you read about AUTO-REGISTRATION.
The central section in the chapter is a mini-catalog of design patterns related to DI CONTAINERS. Although it follows the catalog format, the REGISTER RESOLVE RELEASE
(RRR) pattern description relies on the COMPOSITION ROOT pattern, so it makes sense to
read both in sequence. You can skip the section about configuration options to go directly to the patterns, but those sections are best read in order.
The last section is different. It’s much less technical and focuses on how DI CON-
TAINERS fit into the .NET ecosystem. You can skip reading this section if you don’t care about that aspect.
The purpose of the chapter is to give you a good understanding of what a DI CON-
TAINER is and how it fits in with all the rest of the patterns and principles in this book;
in a sense, you can view this chapter as an introduction to part 4 of the book. Here, we’ll talk about DI CONTAINERS in general, whereas in part 4, we’ll talk about specific
containers and their APIs.
It may seem a bit strange that we talk about DI CONTAINERS here in chapter 3 and
then more or less forget about them again in the next six chapters, but there’s a
Figure 3.2 My mother-in-law’s Italian ice cream maker.
61
Introducing DI Containers
reason for that. In this part of the book, I want to draw the big picture of DI, and it’s essential that you understand how DI CONTAINERS fit into the scheme of things. In
parts 2 and 3, I’ll occasionally show you some examples that involve a DI CONTAINER,
but for the most part I’ll keep the discussion general. The principles and patterns described in the middle of the book can be applied to all DI CONTAINERS.