3
Writing loosely coupled code

In this chapter

  • Redesigning Mary’s e-commerce application to become loosely coupled
  • Analyzing that loosely coupled application
  • Evaluating that loosely coupled application

When it comes to grilling steak, an important practice is to let the meat rest before you cut it into slices. When resting, the juices redistribute, and the results get juicier. If, on the other hand, you cut it too soon, all the juice runs out, and your meat gets drier and less tasty. It’d be a terrible shame to let this happen, because you’d like to give your guests the best tasting experience you can deliver. Although it’s important to know the best practices for any profession, it’s just as important to know the bad practices and to understand why those lead to unsatisfactory results.

Knowing the difference between good and bad practices is essential to learning. This is why the previous chapter was completely devoted to an example and analysis of tightly coupled code: the analysis provided you with the why.

To summarize, loose coupling provides a number of benefits — late binding, extensibility, maintainability, Testability, and parallel development. With tight coupling, you lose those benefits. Although not all tight coupling is undesirable, you should strive to avoid tight coupling to Volatile Dependencies. Moreover, you can use Dependency Injection (DI) to solve the issues that were discovered during that analysis. Because DI is a radical departure from the way Mary created her application, we’re not going to modify her existing code. Rather, we’re going to re-create it from scratch.

Let’s start with a short recap of Mary’s application. We’ll also discuss how we’ll approach the rewrite and what the desired result will look like when we’ve finished.

3.1 Rebuilding the e-commerce application

The analysis of Mary’s application in chapter 2 concluded that Volatile Dependencies were tightly coupled across the different layers. As the dependency graph of Mary’s application in figure 3.1 shows, both the domain layer and the UI layer depend on the data access layer.

03-01.eps

Figure 3.1 The dependency graph for Mary’s application shows how the modules depend on each other.

What we’ll aim to achieve in this chapter is to invert the dependency between the domain layer and the data access layer. This means that instead of the domain layer depending on the data access layer, the data access layer will depend on the domain layer, as shown in figure 3.2.

03-02.eps

Figure 3.2 Dependency graph of the desired inversion for Mary’s application

By creating this inversion, we allow the data access layer to be replaced without having to completely rewrite the application. (This is a radical departure from the way Mary developed her application.) We’ll also apply several patterns along the way. Then we’ll apply Constructor Injection, which we discussed in chapter 1. And finally, we’ll also use Method Injection and Composition Root, which we’ll discuss as we go.

This approach will lead to quite a few more classes as we focus on separating the application concerns. Where Mary defined four classes, we’ll define nine classes and three interfaces. Figure 3.3 drills a little deeper into the application and shows the classes and interfaces we’ll create throughout this chapter.

03-03.eps

Figure 3.3 The classes and interfaces that we’ll have at the end of this chapter. Interfaces are marked with dashed lines.

Figure 3.4 shows how the main classes in the application will interact. At the end of this chapter, we’ll take a look at a slightly more detailed version of this diagram again.

03-04.eps

Figure 3.4 Sequence diagram showing the interaction between elements involved in DI in the e-commerce application that we build in this chapter

When we write software, we prefer to start in the most significant place — the part that has most visibility to our stakeholders. As in Mary’s e-commerce application, this is often the UI. From there, we work our way in, adding more functionality until one feature is done; then we move on to the next. This outside-in technique helps us to focus on the requested functionality without overengineering the solution.

In chapter 2, Mary used the opposite approach. She started with the data access layer and worked her way out, working inside-out. It would be harsh for us to say that working inside-out is bad, but as you’ll see later, the outside-in approach gives you quicker feedback on what you’re building. We’ll therefore build the application in the opposite order, starting with the UI layer, continuing with the domain layer, and then building the data access layer last.

Because we practice Test-Driven Development (TDD), we start by writing unit tests as soon as our outside-in approach prompts us to create a new class. Although we wrote unit tests to create this example, TDD isn’t required to implement and use DI, so we’re not going to show these tests in the book. If you’re interested, the source code that accompanies this book includes the tests. Let’s dive right into our project and begin with the UI.

3.1.1 Building a more maintainable UI

Mary’s specification for the list of featured products was to write an application that extracts those items from the database and displays them in a list (shown again in figure 3.5). Because we know that the project stakeholders will mainly be interested in the visual result, the UI seems like a good place to start.

03-05.tif

Figure 3.5 Screen capture of the e-commerce web application

The first thing you do after opening Visual Studio is add a new ASP.NET Core MVC application to the solution. Because the list of featured products needs to go on the front page, you start by modifying the Index.cshtml file to include the markup shown in the following listing.2 

Listing 3.1 Index.cshtml view markup

@model FeaturedProductsViewModel

<h2>Featured Products</h2>
<div>
    @foreach (ProductViewModel product in this.Model.Products)
    {
        <div>@product.SummaryText</div>
    }
</div>

Notice how much cleaner listing 3.1 is compared to Mary’s original markup.

Listing 3.2 Mary’s original Index view markup from chapter 2

<h2>Featured Products</h2>
<div>
@{
    var products = (IEnumerable<Product>)this.ViewData["Products"];

    foreach (Product product in products)
    {
        <div>@product.Name (@product.UnitPrice.ToString("C"))</div>
    }
}
</div>

The first improvement is that you no longer cast a dictionary item to a sequence of products before iteration is possible. You accomplished this easily by using MVC’s special @model directive. This means that the Model property of the page is of the FeaturedProductsViewModel type. Using the @model directive, MVC will ensure that the value returned from the controller will be cast to the FeaturedProductsViewModel type. Secondly, the entire product display string is pulled directly from the SummaryText property of ProductViewModel.

Both improvements are related to the introduction of view-specific models that encapsulate the behavior of the view. These models are Plain Old CLR Objects (POCO).3  The following listing provides an outline of their structure.

Listing 3.3 FeaturedProductsViewModel and ProductViewModel classes

public class FeaturedProductsViewModel
{
    public FeaturedProductsViewModel(
        IEnumerable<ProductViewModel> products)
    {
        this.Products = products;
    }

    public IEnumerable<ProductViewModel> Products    ①  
        { get; }
}

public class ProductViewModel
{
    private static CultureInfo PriceCulture = new CultureInfo("en-US");

    public ProductViewModel(string name, decimal unitPrice)
    {
        this.SummaryText = string.Format(PriceCulture,
            "{0} ({1:C})", name, unitPrice);
    }

    public string SummaryText { get; }    ②  
}

The use of view models simplifies the view, which is good because views are harder to test. It also makes it easier for a UI designer to work on the application.

HomeController must return a view with an instance of FeaturedProductsViewModel for the code in listing 3.1 to work. As a first step, this can be implemented inside HomeController like this:

public ViewResult Index()
{
    var vm = new FeaturedProductsViewModel(new[]    ①  
    {    ①  
        new ProductViewModel("Chocolate", 34.95m),    ①  
        new ProductViewModel("Asparagus", 39.80m)    ①  
    });    ①  

    return this.View(vm);    ②  
}

We hard-coded the list of discounted products inside the Index method. This isn’t the desired end result, but it enables the web application to execute without error and allows us to show the stakeholders an incomplete, but running example of the application (a stub) for them to comment on.

03-06.tif

Figure 3.6 Screen capture of the stubbed e-commerce web application. Here the product list is hard-coded.

At this stage, only a stub of the UI layer has been implemented; a full implementation of the domain layer and data access layer still remains. One advantage of starting with the UI is that we already have software we can run and test. Contrast this with Mary’s progress at a comparable stage. Only at a much later stage does Mary arrive at a point where she can run the application. Figure 3.6 shows the stubbed web application.

For our HomeController to fulfill its obligations, and to do anything of interest, it requests a list of featured products from the domain layer. These products need to have discounts applied. In chapter 2, Mary wrapped this logic in her ProductService class, and we’ll do that too.

The Index method on HomeController should use the ProductService instance to retrieve the list of featured products, convert those to ProductViewModel instances, and then add those to FeaturedProductsViewModel. From the perspective of HomeController, however, ProductService is a Volatile Dependency because it’s a Dependency that doesn’t yet exist and is still in development. If we want to test HomeController in isolation, develop ProductService in parallel, or replace or Intercept it in the future, we need to introduce a Seam.

Recall from the analysis of Mary’s implementation that depending on Volatile Dependencies is a cardinal sin. As soon as you do that, you’re tightly coupled with the type just used. To avoid this tight coupling, we’ll introduce an interface and use a technique called Constructor Injection; how the instance is created, and by whom, is of no concern to HomeController.

Listing 3.4 HomeController class

public class HomeController : Controller
{
    private readonly IProductService productService;

    public HomeController(
        IProductService productService)    ①  
    {
        if (productService == null)    ②  
            throw new ArgumentNullException(
                "productService");

        this.productService = productService;    ③  
    }

    public ViewResult Index()
    {
        IEnumerable<DiscountedProduct> products =
            this.productService.GetFeaturedProducts();    ④  

        var vm = new FeaturedProductsViewModel(    ⑤  
            from product in products
            select new ProductViewModel(product));    ⑥  

        return this.View(vm);
    }
}

As we stated in chapter 1, Constructor Injection is the act of statically defining the list of required Dependencies by specifying them as parameters to the class’s constructor. This is exactly what HomeController does. In its public constructor, it defines what Dependencies it requires for it to function correctly.

The first time we heard about Constructor Injection, we had a hard time understanding the real benefit. Doesn’t it push the burden of controlling the Dependency onto some other class? Yes, it does — and that’s the whole point. In an n-layer application, you can push that burden all the way to the top of the application into a Composition Root.

Because we added a constructor with an argument to HomeController, it’ll be impossible to create a HomeController without that Dependency, and that’s exactly why we did that. But that does mean that the application’s home screen is broken, because MVC has no idea how our HomeController must be created — unless you instruct MVC otherwise.

In fact, the creation of HomeController isn’t a concern of the UI layer; it’s the responsibility of the Composition Root.5  Because of this, we consider the UI layer completed, and we’ll come back to the creation of HomeController later on. Figure 3.7 shows the current state of implementing the architecture envisioned in figure 3.2.

03-07.eps

Figure 3.7 At this stage, only the UI layer has been implemented; the domain and data access layers have yet to be addressed.

This leads us to the next stage in the re-creation of our e-commerce application, the domain model.

3.1.2 Building an independent domain model

The domain model is a plain, vanilla C# library that we add to the solution. This library will contain POCOs and interfaces. The POCOs will model the domain while the interfaces provide Abstractions that will serve as our main external entry points into the domain model. They’ll provide the contract through which the domain model interacts with the forthcoming data access layer.

The HomeController delivered in the previous section doesn’t compile yet because we haven’t defined the IProductService Abstraction. In this section, we’ll add a new domain layer project to the e-commerce application and a reference to the domain layer project from the MVC project, like Mary did. That will turn out OK, but we’ll postpone doing a dependency graph analysis until section 3.2 so that we can provide you with the full picture. The following listing shows the IProductService Abstraction.

Listing 3.5 IProductService interface

public interface IProductService
{
    IEnumerable<DiscountedProduct> GetFeaturedProducts();
}

IProductService represents the heart of our current domain layer in that it bridges the UI layer with the data access layer. It’s the glue that binds our initial application together.

The sole member of the IProductService Abstraction is the GetFeaturedProducts method. It returns a collection of DiscountedProduct instances. Each DiscountedProduct contains a Name and a UnitPrice. It’s a simple POCO class, as can be seen in the next listing, and this definition gives us enough to compile our Visual Studio solution.

Listing 3.6 DiscountedProduct POCO class

public class DiscountedProduct
{
    public DiscountedProduct(string name, decimal unitPrice)
    {
        if (name == null) throw new ArgumentNullException("name");

        this.Name = name;
        this.UnitPrice = unitPrice;
    }

    public string Name { get; }
    public decimal UnitPrice { get; }
}

The principle of programming to interfaces instead of concrete classes is a cornerstone of DI. It’s this principle that lets you replace one concrete implementation with another. Before continuing, we should take a quick moment to recognize the role of interfaces in this discussion.

Next we’ll write our ProductService implementation. The GetFeaturedProducts method of this ProductService class should use an IProductRepository instance to retrieve the list of featured products, apply any discounts, and return a list of DiscountedProduct instances.

A common abstraction over data access is provided by the Repository pattern, so we’ll define an IProductRepository abstraction in the domain model library.6 

Listing 3.7 IProductRepository

public interface IProductRepository
{
    IEnumerable<Product> GetFeaturedProducts();
}

IProductRepository is the interface to the data access layer, returning “raw” Entities from the persistence store. By contrast, IProductService applies business logic, such as the discount in this case, and converts the Entities to a narrower-focused object. A full-blown Repository would have more methods to find and modify products, but, following the outside-in principle, we only define the classes and members needed for the task at hand. It’s easier to add functionality to code than it is to remove anything.

Because our goal is to invert the dependency between the domain layer and the data access layer, IProductRepository is defined in the domain layer. In the next section, we’ll create an implementation of IProductRepository as part of the data access layer. This allows our dependency to point at the domain layer.

The Product class is also implemented with the bare minimum of members, as shown in the following listing.

Listing 3.8 ProductEntity

public class Product
{
    public string Name { get; set; }    ①  
    public decimal UnitPrice { get; set; }    ①  
    public bool IsFeatured { get; set; }    ①  

    public DiscountedProduct ApplyDiscountFor(
        IUserContext user)    ②  
    {
        bool preferred =    ③  
            user.IsInRole(Role.PreferredCustomer);    ③  
    ③  
        decimal discount = preferred ? .95m : 1.00m;    ③  
    ③  
        return new DiscountedProduct(    ③  
            name: this.Name,    ③  
            unitPrice: this.UnitPrice * discount);    ③  
    }
}

Figure 3.8 illustrates the relationship between ProductService and its Dependencies.

03-08.eps

Figure 3.8 ProductService and its Dependencies

The GetFeaturedProducts method of the ProductService class should use an IProductRepository instance to retrieve the list of featured products, apply any discounts, and return a list of DiscountedProduct instances. The ProductService class corresponds to Mary’s class of the same name, but is now a pure domain model class because it doesn’t have a hard-coded reference to the data access layer. As with our HomeController, we’re again going to relinquish control of its Volatile Dependencies using Constructor Injection, as shown next.

Listing 3.9 ProductService with Constructor Injection

public class ProductService : IProductService
{
    private readonly IProductRepository repository;
    private readonly IUserContext userContext;

    public ProductService(
        IProductRepository repository,    ①  
        IUserContext userContext)    ①  
    {
        if (repository == null)
            throw new ArgumentNullException("repository");
        if (userContext == null)
            throw new ArgumentNullException("userContext");

        this.repository = repository;
        this.userContext = userContext;
    }

    public IEnumerable<DiscountedProduct> GetFeaturedProducts()
    {
        return
            from product in this.repository    ②  
                .GetFeaturedProducts()    ②  
            select product    ②  
                .ApplyDiscountFor(this.userContext);    ③  
    }
}

Besides an IProductRepository, the ProductService constructor requires an instance of IUserContext:

public interface IUserContext
{
    bool IsInRole(Role role);
}

public enum Role { PreferredCustomer }

This is another departure from Mary’s implementation, which only took a boolean value as argument to the GetFeaturedProducts method, indicating whether the user is a preferred customer. Because deciding whether a user is a preferred customer is a piece of the domain layer, it’s more correct to explicitly model this as a Dependency. Besides that, information about the user on whose behalf the request is running is contextual. We don’t want every controller to be responsible for gathering this information. That would be repetitive and error prone, and might lead to accidental security bugs.

Instead of letting the UI layer provide this information to the domain layer, we allow the retrieval of this information to become an implementation detail of ProductService. The IUserContext interface allows ProductService to retrieve information about the current user without HomeController needing to provide this. HomeController doesn’t need to know which role(s) are authorized for a discount price, nor is it easy for HomeController to inadvertently enable the discount by passing, for example, true instead of false. This reduces the overall complexity of the UI layer.

Although the .NET Base Class Library (BCL) includes an IPrincipal interface, which represents a standard way of modeling application users, that interface is generic in nature and isn’t tailored for our application’s special needs. Instead, we let the application define the Abstraction.

The ProductService.GetFeaturedProducts method passes the IUserContext Dependency on to the Product.ApplyDiscountFor method. This technique is known as Method Injection. Method Injection is particularly useful in cases where short-lived objects like Entities (such as the Product Entity, in our case) need Dependencies. Although the details vary, the main technique remains the same. We’ll discuss this pattern in more detail in chapter 4. At this stage, the application doesn’t work at all. That’s because three problems remain:

  • There’s no concrete implementation of IProductRepository. This is easily solved. In the next section, we’ll implement a concrete SqlProductRepository that reads the featured products from the database.
  • There’s no concrete implementation ofIUserContext. We’ll take a look at this in the next section too.
  • The MVC framework doesn’t know which concrete type to use. This is because we introduced an abstract parameter of type IProductService to the constructor of HomeController. This issue can be solved in various ways, but our preference is to develop a custom Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator. How this is done is outside the scope of this chapter, but it’s a subject that we’ll discuss in chapter 7. Suffice it to say that this custom factory will create an instance of the concrete ProductService and supply it to the constructor of HomeController.

In the domain layer, we work only with types defined within the domain layer and Stable Dependencies of the .NET BCL. The concepts of the domain layer are implemented as POCOs. At this stage, there’s only a single concept represented, namely, a Product. The domain layer must be able to communicate with the outside world (such as databases). This need is modeled as Abstractions (such as Repositories) that we must replace with concrete implementations before the domain layer becomes useful. Figure 3.9 shows the current state of implementing the architecture envisioned in figure 3.2.

03-09.eps

Figure 3.9 The UI and domain layer are now both in place, whereas the data access layer remains to be implemented.

We succeeded in making our domain model compile. This means that we created a domain model that’s independent of the data access layer, which we still need to create. But before we get to that, there are a few points we’d like to explain in more detail.

Dependency Inversion Principle

Much of what we’re trying to accomplish with DI is related to the Dependency Inversion Principle.8  This principle states that higher-level modules in our applications shouldn’t depend on lower-level modules; instead, modules of both levels should depend on Abstractions.

03-10.eps

Figure 3.10 Instead of ProductService depending on SqlProductRepository, both classes depend on an Abstraction.

This is exactly what we did when we defined our IProductRepository. The ProductService component is part of the higher-level domain layer module, whereas the IProductRepository implementation — let’s call it SqlProductRepository — is part of the lower-level data access module. Instead of letting our ProductService depend on SqlProductRepository, we let both ProductService and SqlProductRepository depend on the IProductRepository Abstraction. SqlProductRepository implements the Abstraction, while ProductService uses it. Figure 3.10 illustrates this.

The relationship between the Dependency Inversion Principle and DI is that the Dependency Inversion Principle prescribes what we would like to accomplish, and DI states how we would like to accomplish it. The principle doesn’t describe how a consumer should get ahold of its Dependencies. Many developers, however, aren’t aware of another interesting part of the Dependency Inversion Principle.

Not only does the principle prescribe loose coupling, it states that Abstractions should be owned by the module using the Abstraction. In this context, “owned” means that the consuming module has control over the shape of the Abstraction, and it’s distributed with that module, rather than with the module that implements it. The consuming module should be able to define the Abstraction in a way that benefits itself the most.

You already saw us do this twice: both IUserContext and IProductRepository are defined this way. They’re designed in a way that works best for the domain layer, even though their implementations are the responsibility of the UI and data access layers, respectively, as shown in figure 3.11.

Letting a higher-level module or layer define its own Abstractions not only prevents it from having to take a dependency on a lower-level module, it allows the higher-level module to be simplified, because the Abstractions are tailored for its specific needs. This brings us back to the BCL’s IPrincipal interface.

As we described, IPrincipal is generic in nature. The Dependency Inversion Principle instead guides us towards defining Abstractions tailored for our application’s special needs. That’s why we define our own IUserContext Abstraction instead of letting the domain layer depend on IPrincipal. This does mean, however, that we have to create an Adapter implementation that allows translating calls from this application-specific IUserContext Abstraction to calls to the application framework.

03-11.eps

Figure 3.11 Both IUserContext and IProductRepository are part of the domain layer, because ProductService “owns” them.

If the Dependency Inversion Principle dictates that Abstractions should be distributed with their owning modules, doesn’t the domain layer IProductService interface violate this principle? After all, IProductService is consumed by the UI layer, but implemented by the domain layer, as figure 3.12 shows. The answer is yes, this does violate the Dependency Inversion Principle.

03-12.eps

Figure 3.12 By making IProductService part of the domain layer, we violate the Dependency Inversion Principle.

If we were keen on fixing this violation, we should move IProductService out of the domain layer. Moving IProductService into the UI layer, however, would make our domain layer dependent on that layer. Because the domain layer is the central part of the application, we don’t want it to depend on anything else. Besides, this dependency would make it impossible to replace the UI later on.

This means that to fix the violation, we need an additional two extra projects in our solution — one for the isolated UI layer without the Composition Root and another for the IProductService Abstraction that the UI layer owns. Out of pragmatism, however, we chose not to pursuit this path for this example and, therefore, leave the violation in place. We hope you can appreciate that we don’t want to overcomplicate things.

Interfaces or abstract classes?

Many guides to object-oriented design focus on interfaces as the main abstraction mechanism, whereas the .NET Framework Design Guidelines endorse abstract classes over interfaces.9  Should you use interfaces or abstract classes? With relation to DI, the reassuring answer is that it doesn’t matter. The important part is that you program against some sort of abstraction.

Choosing between interfaces and abstract classes is important in other contexts, but not here. You’ll notice that we use these words interchangeably; we often use the term Abstraction to encompass both interfaces and abstract classes. This doesn’t mean that we, as authors, don’t have a preference for one over the other. We do, in fact. When it comes to writing applications, we typically prefer interfaces over abstract classes for these reasons:

  • Abstract classes can easily be abused as base classes. Base classes can easily turn into ever-changing, ever-growing God Objects.10  The derivatives are tightly coupled to its base class, which can become a problem when the base class contains Volatile behavior. Interfaces, on the other hand, force us into the “Composition over Inheritance” mantra.11 
  • Concrete classes can implement several interfaces, although in .NET, those can only derive from a single base class. Using interfaces as the vehicle of Abstraction is more flexible.
  • Interface definitions in C# are less clumsy compared to abstract classes. With interfaces, we can omit the abstract and public keywords from their members. This makes an interface a more succinct definition.

When writing reusable libraries, however, the subject is becoming less clear-cut, due to the need to deal with backward compatibility. In that light, an abstract class might make more sense because non-abstract members can be added later, whereas adding members to an interface is a breaking change. That’s why the .NET Framework Design Guidelines prefer abstract classes.

Now let’s move on to the data access layer. We’ll create an implementation for the previously defined IProductRepository interface.

3.1.3 Building a new data access layer

Like Mary, we’d like to implement our data access layer using Entity Framework Core, so we follow the same steps she did in chapter 2 to create the Entity model. The main difference is that CommerceContext is now only an implementation detail of the data access layer, as opposed to being the entirety of the data access layer.

In this model, nothing outside of the data access layer will have any awareness of, or dependency on, Entity Framework. It can be swapped out without any upstream effects. With that in mind, we can create an implementation of IProductRepository.

Listing 3.10 Implementing IProductRepository using Entity Framework Core

public class SqlProductRepository : IProductRepository
{
    private readonly CommerceContext context;

    public SqlProductRepository(CommerceContext context)
    {
        if (context == null) throw new ArgumentNullException("context");

        this.context = context;
    }

    public IEnumerable<Product> GetFeaturedProducts()
    {
        return
            from product in this.context.Products
            where product.IsFeatured
            select product;
    }
}

In Mary’s application, the Product Entity was also used as a domain object, although it was defined in the data access layer. This is no longer the case. The Product class is now defined in our domain layer. Our data access layer reuses the Product class from that layer.

For simplicity, we chose to let the data access layer reuse our domain object instead of defining its own implementation. We were able to do so because Entity Framework Core allows us to write Entities that are persistence ignorant.12  Whether this is a reasonable practice depends a lot on the structure and complexity of your domain objects. If we later conclude that this shared model is enforcing unwanted constraints on our model, we can change our data access layer by introducing internal persistence objects, without touching the rest of the application. In that case, we’d need the data access layer to convert those internal persistence objects into domain objects.

In the previous chapter, we discussed how the implicit dependency of Mary’s CommerceContext on the connection string caused her problems along the way. Our new CommerceContext will make this dependency explicit, which is another deviation from Mary’s implementation. The next listing shows our new CommerceContext.

Listing 3.11 A better CommerceContext class

public class CommerceContext : DbContext
{
    private readonly string connectionString;

    public CommerceContext(string connectionString)    ①  
    {
        if (string.IsNullOrWhiteSpace(connectionString))
            throw new ArgumentException(
                "connectionString should not be empty.",
                "connectionString");

        this.connectionString = connectionString;    ②  
    }

    public DbSet<Product> Products { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
        builder.UseSqlServer(this.connectionString);
    }
}

This almost brings us to the end of our re-implementation of the e-commerce application. The only implementation still missing is that of IUserContext.

3.1.4 Implementing an ASP.NET Core–specific IUserContext Adapter

The last concrete implementation missing is that of IUserContext. In web applications, information about a user who issues a request is usually passed on to the server with each request. This information is relayed using cookies or HTTP headers. How we retrieve the identity of the current user is highly dependent on the framework we use. This means that we’ll need a completely different implementation when building an ASP.NET Core application compared with, for instance, a Windows service.

The implementation of our IUserContext is framework specific. We want neither our domain layer nor our data layer to know anything about the application framework. That would make it impossible to use those layers in a different context. We need to implement this elsewhere. The UI layer, therefore, is an ideal place for our IUserContext implementation.

The following listing shows a possible IUserContext implementation for an ASP.NET Core application.

Listing 3.12 IUserContext implementation for ASP.NET Core

public class AspNetUserContextAdapter : IUserContext
{
    private static HttpContextAccessor Accessor = new HttpContextAccessor();

    public bool IsInRole(Role role)
    {
        return Accessor.HttpContext.User.IsInRole(role.ToString());
    }
}

AspNetUserContextAdapter requires an HttpContextAccessor to work. HttpContextAccessor, a component specified by the ASP.NET Core framework, allows access to the HttpContext of the current request, like we were able to in ASP.NET “classic” using HttpContext.Current. We use HttpContext to access the request’s information about the current user.

AspNetUserContextAdapter adapts our application-specific IUserContext Abstraction to the ASP.NET Core API. This class is an implementation of the Adapter design pattern that we discussed in chapter 1.13 

With AspNetUserContextAdapter implemented, our reimplementation of the e-commerce application is finished. This brings us to our Composition Root.

3.1.5 Composing the application in the Composition Root

With ProductService, SqlProductRepository and AspNetUserContextAdapter implemented, we can now set up ASP.NET Core MVC to construct an instance of HomeController, where HomeController is fed by a ProductService instance, which itself is constructed using a SqlProductRepository and an AspNetUserContextAdapter. This eventually results in an object graph that would look as follows.

Listing 3.13 The application’s object graph

new HomeController(
    new ProductService(
        new SqlProductRepository(
            new CommerceContext(connectionString)),
        new AspNetUserContextAdapter()));
03-15.tif

Figure 3.13 Screen capture of the finished application

We’ll discuss how the construction of such an object graph is plugged into the ASP.NET Core framework in greater detail in chapter 7, so we won’t show that here. But now that everything is correctly wired together, we can browse to the application’s homepage and get the page shown in figure 3.13.

3.2 Analyzing the loosely coupled implementation

The previous section contained lots of details, so it’s hardly surprising if you lost sight of the big picture along the way. In this section, we’ll try to explain what happened in broader terms.

3.2.1 Understanding the interaction between components

The classes in each layer interact with each other either directly or in abstract form. They do so across module boundaries, so it can be difficult to follow how they interact. Figure 3.14 shows how the different Dependencies interact, giving a more detailed overview to the original outline described in figure 3.4.

03-16.eps

Figure 3.14 Interaction between elements involved in DI in the e-commerce application

When the application starts, the code in Startup creates a new custom controller activator and looks up the connection string from the application’s configuration file. When a page request comes in, the application invokes Create on the controller activator.

The activator supplies the stored connection string to a new instance of CommerceContext (not shown in the diagram). It injects CommerceContext into a new instance of SqlProductRepository. In turn, the SqlProductRepository instance together with an instance of AspNetUserContextAdapter (not shown in the diagram) are injected into a new instance of ProductService. Similarly, ProductService is injected into a new instance of HomeController, which is then returned from the Create method.

The ASP.NET Core MVC framework then invokes the Index method on the HomeController instance, causing it to invoke the GetFeaturedProducts method on the ProductRepository instance. This in turn calls the GetFeaturedProducts method on the SqlProductRepository instance. Finally, the ViewResult with the populated FeaturedProductsViewModel is returned, and MVC finds and renders the correct view.

3.2.2 Analyzing the new dependency graph

In section 2.2, you saw how a dependency graph can help you analyze and understand the degree of flexibility provided by the architectural implementation. Has DI changed the dependency graph for the application?

Figure 3.15 shows that the dependency graph has indeed changed. The domain model no longer has any dependencies and can act as a standalone module. On the other hand, the data access layer now has a dependency; in Mary’s application, it had none.

03-17.eps

Figure 3.15 Dependency graph showing the sample e-commerce application with DI applied. All classes and interfaces are shown, as well as their relationships to one another.

The most important thing to note in figure 3.15 is that the domain layer no longer has any dependencies. This should raise our hopes that we can answer the original questions about composability (see section 2.2) more favorably this time:

  • Can we replace the web-based UI with a WPF-based UI? That was possible before and is still possible with the new design. Neither the domain model library nor the data access library depends on the web-based UI, so we can easily put something else in its place.
  • Can we replace the relational data access layer with one that works with the Azure Table Service? In a later chapter, we’ll describe how the application locates and instantiates the correct IProductRepository, so, for now, take the following at face value: the data access layer is being loaded by late binding, and the type name is defined as an application setting in the application’s configuration file. It’s possible to throw the current data access layer away and inject a new one, as long as it also provides an implementation of IProductRepository.

The sample e-commerce application described in this chapter only presents us with a limited level of complexity: there’s only a single Repository involved in a read-only scenario. Until now, we’ve kept the application as simple and small as possible to gently introduce some core concepts and principles. Because one of the main purposes of DI is to manage complexity, we need a complex application to fully appreciate its power. During the course of the book, we’ll expand the sample e-commerce application to fully demonstrate different aspects of DI.

This chapter concludes the first part of the book. The purpose of part 1 was to put DI on the map and to introduce DI in general. In this chapter, you’ve seen examples of Constructor Injection. We also introduced Method Injection and Composition Root as patterns related to DI. In the next chapter, we’ll dive deeper into these and other design patterns.

Summary

  • Refactoring existing applications towards a more maintainable, loosely coupled design is hard. Big rewrites, on the other hand, are often riskier and expensive.
  • The use of view models can simplify the view, because the incoming data is shaped specifically for the view.
  • Because views are harder to test, the dumber the view, the better. It also simplifies the work of a UI designer who might work on the view.
  • When you limit the amount of Volatile Dependencies within the domain layer, you get a higher degree of decoupling, reuse, and Testability.
  • When building applications, the outside-in approach facilitates more rapid prototyping, which can shorten the feedback cycle.
  • When you want a high degree of modularity in your application, you need to apply the Constructor Injection pattern and build object graphs in the Composition Root, which is located close to the application’s entry point.
  • Programming to interfaces is a cornerstone of DI. It allows you to replace, mock, and Intercept a Dependency, without having to make changes to its consumers. When implementation and Abstraction are placed in different assemblies, it enables whole libraries to be replaced.
  • Programming to interfaces doesn’t mean that all classes should implement an interface. Short-lived objects, such as Entities, view models, and DTOs, typically contain no behavior that requires mocking, Interception, decoration, or replacement.
  • With respect to DI, it doesn’t matter whether you use interfaces or purely abstract classes. From a general development perspective, as authors, we typically prefer interfaces over abstract classes.
  • A reusable library is a library that has clients that aren’t known at compile time. Reusable libraries are typically shipped via NuGet. Libraries that only have callers within the same (Visual Studio) solution aren’t considered to be reusable libraries.
  • DI is closely related to the Dependency Inversion Principle. This principle implies that you should program against interfaces, and that a layer must be in control over the interfaces it uses.
  • The use of a DI Container can help in making the application’s Composition Root more maintainable, but it won’t magically make tightly coupled code loosely coupled. For an application to become maintainable, it must be designed with DI patterns and techniques in mind.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.181