Chapter 8. Object Lifetime

Menu

  • Managing DEPENDENCY LIFETIME
  • Disposable DEPENDENCIES
  • SINGLETON
  • TRANSIENT
  • PER GRAPH
  • WEB REQUEST CONTEXT
  • POOLED

The passing of time has a profound effect on most foods and drinks, but the consequences vary. Personally, I find 12-month-old Gruyère much more interesting than 6-month-old Gruyère, but I prefer my asparagus much fresher than either of those. In many cases, it’s easy to assess the proper age of an item; but in certain cases, doing so becomes very complex. This is most notable when it comes to wine (see figure 8.1).

Figure 8.1. Wine, cheese, and asparagus. Although the combination may be a bit off, their age greatly affects their overall qualities.

Wines tend to get better with age—until they suddenly become too old and lose most of their flavor. This depends on a lot of factors, including the origin and vintage of the wine. Although wines interest me, I don’t expect to ever be able to predict when a wine will peak. For that, I rely on experts: books at home and sommeliers at restaurants. They understand wines better than I do because it’s their specialty, so whenever I trust them I happily let them take control.

Unless you dove straight into this chapter without reading any of the previous ones, you know that letting go of control is a key concept in DI. This is the INVERSION OF CONTROL aspect, but it implies more than just letting someone else pick an implementation of a required ABSTRACTION. When we accept letting a COMPOSER supply a DEPENDENCY, we must also accept that we can’t control its lifetime.

Just as the sommelier intimately knows the contents of the restaurant’s wine cellar and can make a far more informed decision than we can, so should we trust the COMPOSER to be able to control the lifetime of DEPENDENCIES more efficiently than the consumer. Composing and managing components is its single responsibility.

 

Definition

COMPOSER—As I use it here, COMPOSER is a unifying term to refer to any object or method that composes DEPENDENCIES. This is often a DI CONTAINER, but may also be any method used in POOR MAN’S DI, such as the Main method of a console application.

 

In this chapter, we’ll explore DEPENDENCY LIFETIME MANAGEMENT. Understanding this topic is important because just as you can have a subpar experience if you drink a wine at the wrong age,[1] you may experience degraded performance from configuring DEPENDENCY LIFETIME incorrectly. Even worse, you may get the LIFETIME MANAGEMENT equivalent of spoiled food: resource leaks. Understanding the principles of correctly managing the life cycle scopes of components should enable you to make informed decisions and configure your applications correctly.

1 Both your own and the wine’s!

 

Note

Throughout this chapter, I use the terms lifestyle type, life cycle strategy, life cycle scope, and other unlikely combinations interchangeably.

 

As figure 8.2 illustrates, we’ll start with a general introduction to the concept, followed by a discussion about disposable DEPENDENCIES. This first part of the chapter is meant to provide all the background information and guiding principles you need to make knowledgeable decisions about your own applications’ life cycle scope configuration.

Figure 8.2. The overall structure of this chapter. We’ll start with a general discussion about managing DEPENDENCY Lifetime, including a particular discussion of dealing with disposable objects. We need that foundation to efficiently discuss the common patterns of the small lifestyle catalog that follows. We’ll start by looking at some common and highly useful patterns and finish with a brief glance at some more exotic lifestyle scopes to give you an impression of the breadth of the topic.

After that, we’ll use the rest of the chapter to look at different lifetime strategies. This part of the chapter takes the form of a catalog of available lifestyles. In most cases, one of these stock lifestyle patterns will provide a good match for a given challenge, so understanding them in advance equips you to deal with many difficult situations. When we’re finished, you should have a good grasp of LIFETIME MANAGEMENT and common lifetimes.

First, let’s look at OBJECT LIFETIME and how it relates to DI in general.

8.1. Managing Dependency Lifetime

So far, we’ve mostly discussed how DI enables us to compose DEPENDENCIES. The previous chapter explored this subject in great detail, but as I alluded to in section 1.4, OBJECT COMPOSITION is just one aspect of DI. Managing OBJECT LIFETIME is another.

 

Note

In .NET, an object’s life cycle is simple: the object is created, used, and garbage-collected. The presence of IDisposable complicates things a bit, but the life cycle isn’t more complicated than that. When we discuss OBJECT LIFETIME, we talk about how we manage objects’ life cycles.

 

The first time I was introduced to the idea that the scope of DI includes LIFETIME MANAGEMENT I failed to understand the deep connection between OBJECT COMPOSITION and OBJECT LIFETIME. I finally got it, and it’s simple, so let’s take a look!

In this section, I’ll introduce LIFETIME MANAGEMENT and how it applies to DEPENDENCIES. We’ll start by looking at the general case of composing objects and how it has implications for the lifetimes of DEPENDENCIES. From there, we’ll move on to study how DI CONTAINERS can manage DEPENDENCY LIFETIME. Although most of the examples are specialized code that deals with particular configurations, we’ll also make a brief detour around a sample DI CONTAINER to get a glimpse at what lifetime configuration might look like.

First, we’ll investigate why OBJECT COMPOSITION implies LIFETIME MANAGEMENT.

8.1.1. Introducing Lifetime Management

When we accept that we should let go of our psychological need for control over DEPENDENCIES and rather request them through CONSTRUCTOR INJECTION or one of the other DI patterns, we must let go completely. To understand why, we’ll examine the issue progressively. Let’s begin by reviewing what the standard .NET object life cycle means for DEPENDENCIES. You should already know this, but bear with me for the next half page while I establish the context.

Simple Dependency life cycle

You know that DI means we let a third party (often a DI CONTAINER) serve us the DEPENDENCIES we need. This also means we must let it manage the DEPENDENCIES’ lifetimes. This is easiest to understand when it comes to object creation. Here is a code fragment from the sample Commerce application’s COMPOSITION ROOT (you can see the complete example in listing 7.3):

var discountRepository =
    new SqlDiscountRepository(connectionString);
var discountPolicy =
    new RepositoryBasketDiscountPolicy(discountRepository);

I hope that it’s evident that the RepositoryBasketDiscountPolicy class doesn’t control when discountRepository is created. In this case, it’s likely to happen within the same millisecond; but as a thought experiment, we could insert a call to Thread.Sleep between these two lines of code to demonstrate that we can arbitrarily separate them over time. That would be a pretty weird thing to do, but you get the point.

Consumers don’t control creation of their DEPENDENCIES, but what about destruction? As a general rule, we don’t control when objects are destroyed in .NET. The garbage collector collects unused objects, but unless we’re dealing with disposable objects, we can’t explicitly destroy an object.

 

Note

I use the term disposable object as shorthand for referring to object instances of types that implement the IDisposable interface.

 

Objects are garbage-collected when they go out of scope. Conversely, they last as long as someone else holds a reference to them. Although a consumer can’t explicitly destroy an object, it can keep the object alive by holding on to the reference. This is what we do when we use CONSTRUCTOR INJECTION, because we save the DEPENDENCY in a private field. But as figure 8.3 illustrates, when the consumer goes out of scope, so may the DEPENDENCY.

Figure 8.3. Whoever injects the DEPENDENCY into a consumer decides when it’s created, but the consumer can keep the DEPENDENCY alive by holding a reference to it. When the consumer goes out of scope, the DEPENDENCY may be eligible for garbage collection.

Even when a consumer goes out of scope, the DEPENDENCY may live on if other objects hold a reference to it. Otherwise, it will be garbage-collected. Because you’re an experienced .NET developer, this is old news to you, but now the discussion should begin to get more interesting.

Adding complexity to the Dependency life cycle

Until now our analysis of the DEPENDENCY life cycle has been mundane, but we can add some complexity. What happens when more than one consumer requires the same DEPENDENCY? One option is to supply each consumer their own instance, as shown in the following listing.

Listing 8.1. Composing with multiple instances of the same DEPENDENCY

In this example, two consumers both require a DiscountRepository instance, so you wire up two separate instances with the same connection string. You can now pass repositoryForPolicy to a new RepositoryBasketDiscountPolicy instance and repositoryForCampaign to a new DiscountCampaign instance .

When it comes to the life cycles of each repository in listing 8.1, nothing has changed compared to the previous example. Each goes out of scope and is garbage-collected when the consumers go out of scope. This may happen at different times, but the situation is only marginally different than before.

It would be a somewhat different situation if both consumers were to share the same DEPENDENCY, as shown in this example:

var repository =
    new SqlDiscountRepository(connectionString);

var discountPolicy =
    new RepositoryBasketDiscountPolicy(repository);

var campaign = new DiscountCampaign(repository);

Instead of creating two different SqlDiscountRepository instances, you create a single instance that you inject into both consumers. Both save the reference for later use.

 

Note

The consumers are blissfully unaware that the DEPENDENCY is shared. Because they both accept whichever version of the DEPENDENCY they’re given, no modification of the source code is necessary to accommodate this change in DEPENDENCY configuration. This is a result of the LISKOV SUBSTITUTION PRINCIPLE.

 

 

Liskov Substitution Principle

As originally stated, the LISKOV SUBSTITUTION PRINCIPLE is an academic and abstract concept. But in object-oriented design, we can paraphrase it as follows: Methods that consume ABSTRACTIONS must be able to use any derived class without knowing it. In other words, we must be able to substitute the ABSTRACTION for an arbitrary implementation without changing the correctness of the system.

 

The life cycle situation for the repository DEPENDENCY has changed distinctly compared to the previous example. Both consumers must go out of scope before repository may be eligible for garbage collection, and they may do so at different times. The situation becomes less predictable when the DEPENDENCY reaches the end of its lifetime, and this trait is only reinforced when the number of consumers increases.

Given enough consumers, it’s likely that there will always be one around to keep the DEPENDENCY alive. This may sound like a problem, but it rarely is: instead of a multitude of similar instances, we have only one, which saves memory. This is such a desirable quality that we formalize it in a lifestyle pattern called SINGLETON LIFESTYLE. Don’t confuse this with the Singleton[2] design pattern, although there are similarities. We’ll go into greater detail about this subject in section 8.3.1.

2 Erich Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software (New York: Addison-Wesley, 1994), 127.

The key point to appreciate is that the COMPOSER has a greater degree of influence over the lifetime of DEPENDENCIES than any single consumer. The COMPOSER decides when instances are created, and by its choice of whether to share instances, it determines whether a DEPENDENCY goes out of scope with a single consumer, or whether all consumers must go out of scope before the DEPENDENCY can be released.

This is comparable to visiting a restaurant with a good sommelier. The sommelier spends a large proportion of the day managing and evolving the wine cellar, buying new wines, sampling the available bottles to track how they develop, and working with the chefs to identify optimal matches to the food being served. When we’re presented with the wine list, it includes only what the sommelier deems fit to offer for sale. We’re free to select a wine according to our personal taste, but we don’t presume to know more about the restaurant’s selection of wines and how they go with the food than the sommelier does.

The sommelier will often decide to keep lots of bottles in stock for years; and as you’ll see in the next section, a COMPOSER may decide to keep instances alive by holding on to their references.

8.1.2. Managing lifetime with a container

The previous section explained how we can vary the composition of DEPENDENCIES to influence their lifetimes. In this section, we’ll look at how a DI CONTAINER can address these variations.

 

Note

This section discusses the principles behind managing lifetimes with a DI CONTAINER, so I won’t go into great detail about particular containers. As is the case throughout part 3 of this book, I use POOR MAN’S DI to illustrate the concepts.

 

We’ll start by examining how to control the life cycle of DEPENDENCIES using custom containers, and then turn to a quick example of specifying lifestyles in a real DI CONTAINER.

Managing lifestyles using a specialized container

In chapter 7, we created specialized containers to compose applications. One of these was CommerceServiceContainer. Listing 7.9 shows the implementation of its ResolveProductManagementService method; and as figure 8.4 shows, this method is about the only code in the class.

Figure 8.4. All of the CommerceServiceContainer class’s implementation currently resides in the ResolveProductManagementService method. The Release method does absolutely nothing, and there are no fields or properties on the class. If you’re wondering why we have the Release method, we’ll get to it in section 8.2.2.

As you may recall from listing 7.9, the Resolve method creates the entire dependency graph on the fly each time it’s invoked. In other words, each DEPENDENCY is private to the issued IProductManagementService, and there is no sharing. When the IProductManagementService instance goes out of scope (which it does every time the service has replied to a request), all the DEPENDENCIES go out of scope as well. This is often called a TRANSIENT lifestyle, but we’ll talk more about that in section 8.3.2.

Let’s analyze the object graph created by CommerceServiceContainer and shown in figure 8.5 to see if there is room for improvement.

Figure 8.5. Object graph as created by CommerceServiceContainer. Each ProductManagementService instance created contains its own ContractMapper and its own SqlProductRepository, which in turn contains its own connection string. The DEPENDENCIES on the right are immutable.

The ContractMapper class is a completely stateless service, so there is no reason to create a new instance every time we need to service a request. The connection string is also unlikely to change, so we may also decide to reuse it across requests.

The SqlProductRepository class, on the other hand, relies on an Entity Framework Object Context, and it’s considered a best practice to use a new instance per request.

Given this particular configuration, a better implementation of CommerceServiceContainer would reuse the same instances of both ContractMapper and the connection string while creating new instances of SqlProductRepository. In short, you should configure ContractMapper and the connection string to use SINGLETON LIFESTYLE and SqlProductRepository as TRANSIENT. The following listing shows how to implement this change.

Listing 8.2. Managing lifetime with a container

Because you want to reuse the connection string and the ContractMapper across all requests, you save them in private fields and initialize them in the constructor . The readonly keyword provides an extra guarantee that once assigned, these SINGLETON instances are permanent and can’t be replaced, but apart from that extra guarantee, readonly is in no way required when implementing the SINGLETON LIFESTYLE.

Each time the container is asked to create a new instance, it creates a TRANSIENT instance of SqlProductRepository using the SINGLETON connection string . It finally uses this TRANSIENT repository together with the SINGLETON mapper to compose and return an instance of ProductManagementService.

 

Note

The code in listing 8.2 is functionally equivalent to the code in listing 7.9—it’s just slightly more efficient.

 

By holding on to the DEPENDENCIES it creates, a container can keep them alive for as long as it wants. In the previous example, it creates both SINGLETON DEPENDENCIES as soon as it’s initialized, but it could also have used lazy initialization.

This example should give you an idea of how DI CONTAINERS manage life cycles. Because a DI CONTAINER is a reusable library, we can’t modify its source code every time we want to reconfigure a DEPENDENCY’s lifestyle. In the next section, we’ll take a quick look at how to configure lifestyles for a sample container.

Managing lifestyle using Autofac

Occasionally throughout this book, I take a break from pure POOR MAN’S DI to provide an example of how we can achieve a result using a sample DI CONTAINER. Each DI CONTAINER has its own specific API to express many different features; but although the details differ, the principles remain the same. This is also true for LIFETIME MANAGEMENT.

 

Note

Even the term LIFETIME MANAGEMENT isn’t ubiquitous. For instance, Autofac[3] calls it Instance Scope.

3http://code.google.com/p/autofac/

 

In this section, we’ll take a brief glance at configuring lifetimes with Autofac.

 

Note

There is no particular reason I chose Autofac over other DI CONTAINERS for this example. I could just as well have chosen a different one.

 

The next listing shows how to configure Autofac with pure TRANSIENT DEPENDENCIES, equivalent to the example from listing 7.9.

Listing 8.3. Configuring Autofac with TRANSIENT DEPENDENCIES
var builder = new ContainerBuilder();
builder.RegisterType<ContractMapper>()
    .As<IContractMapper>();
builder.Register((c, p) =>
    new SqlProductRepository(
        ConfigurationManager
        .ConnectionStrings["CommerceObjectContext"]
        .ConnectionString))
    .As<ProductRepository>();
builder.RegisterType<ProductManagementService>()
    .As<IProductManagementService>();
var container = builder.Build();

One peculiarity of Autofac is that you don’t configure the container itself, but rather configure a ContainerBuilder and use it to create the container when the configuration is completed.

The simplest form of registration is when you only need to define a map between an ABSTRACTION and a concrete type, such as the map from IContractMapper to ContractMapper. Notice that the concrete type is specified before the ABSTRACTION, which is the reverse order of that used by most other DI CONTAINERS.

Although Autofac supports AUTO-WIRING as well as any other DI CONTAINER, injecting primitive types such as strings always represents a special case because there could potentially be many different strings in play. In this case, you have only a single connection string, but you still need to supply it to the SqlProductRepository you now register. You can do that by using a lambda expression[4] that will be executed when the ProductRepository type is requested.

4 Technically, it isn’t a lambda expression but rather a code block. Most .NET developers know such code constructs as lambda expressions, so I chose the more well-known term over the more correct one.

The use of lambdas is one of Autofac’s claims to fame. Although most DI CONTAINERS now have a similar feature, Autofac was among the first to introduce it. You can use the lambda to specify how the SqlProductRepository class is created; and, more specifically, you pull the connectionString constructor parameter from the application configuration.

The advantage of using a lambda is that it’s type-safe, so you get compile-time verification of the construction of the SqlProductRepository. The disadvantage is that you don’t get AUTO-WIRING, so unless you explicitly need to specify a constructor parameter, the simpler map using the RegisterType method is preferable. This is how you map IProductManagementService to ProductManagementService so it can take advantage of AUTO-WIRING.

You can now use the container instance to create new instances of IProductManagementService like this:

var service = container.Resolve<IProductManagementService>();

But wait, what about LIFETIME MANAGEMENT? Most DI CONTAINERS have a default lifestyle. In the case of Autofac, the default is called Per Dependency, which is the same as the TRANSIENT lifestyle. Because it’s the default, you didn’t need to specify it, but you could have done it like this if you wanted to:

builder.RegisterType<ContractMapper>()
    .As<IContractMapper>()
    .InstancePerDependency();

Notice that you use the fluent registration interface to specify the Instance Scope (Autofac’s term for lifestyle) with the InstancePerDependency method.

There is also a Single Instance Scope that corresponds to the SINGLETON lifestyle. Armed with that knowledge, you can write the Autofac equivalent of listing 8.2:

builder.RegisterType<ContractMapper>()
    .As<IContractMapper>()
    .SingleInstance();
builder.Register((c, p) =>
    new SqlProductRepository(connectionString))
    .As<ProductRepository>();
builder.RegisterType<ProductManagementService>()
    .As<IProductManagementService>();

You want ContractMapper to have the SINGLETON lifestyle, so you define this by invoking the SingleInstance method. When it comes to the SqlProductRepository, things become a little more difficult because the SqlProductRepository instance should be TRANSIENT, but the injected connection string should be a SINGLETON. You can achieve this by extracting the connectionString from the application configuration (not shown, but similar to before) and using this outer variable from within the closure you use to specify the constructor. Because connectionString is an outer variable, it remains the same across many invocations of the constructor. Notice how you implicitly scope both SqlProductRepository and ProductManagementService as TRANSIENTS by not specifying a lifestyle.

Although this example describes how to specify lifestyles with Autofac, other DI CONTAINERS have vaguely similar APIs for the same purpose.

The ability to fine-tune each DEPENDENCY’s lifestyle is important for performance reasons but can also be important for functionality. For instance, the Mediator[5] design pattern relies on a shared director through which several components communicate. This only works when the Mediator is shared among the involved collaborators.

5 Gamma, Design Patterns, 273.

So far, we’ve discussed how INVERSION OF CONTROL implies that consumers can’t manage the lifetimes of their DEPENDENCIES because they obviously don’t control creation of objects, and because .NET uses garbage collection consumers can’t explicitly destroy objects either.

This leaves a question unanswered: what about disposable DEPENDENCIES? We’ll now turn our attention to that delicate question.

8.2. Working with disposable Dependencies

Although .NET is a managed platform with a garbage collector, it can still interact with unmanaged code. When this happens, .NET code interacts with unmanaged memory that isn’t garbage-collected. To prevent memory leaks, we must have a mechanism with which to deterministically release unmanaged memory. This is the key purpose of the IDisposable interface.[6]

6 In my opinion, the definitive article about IDisposable is Shawn Farkas’s “CLR Inside Out: Digging into IDisposable” (MSDN Magazine, July 2007), http://msdn.microsoft.com/en-us/magazine/cc163392.aspx

It’s likely that some DEPENDENCY implementations will contain unmanaged resources. As an example, ADO.NET connections are disposable because they tend to use unmanaged memory, so database-related implementations such as repositories backed by databases are likely to be disposable themselves.

How should we model disposable DEPENDENCIES? Should we also let ABSTRACTIONS be disposable? That might look like this:

public interface IMyDependency : IDisposable { }

This is technically possible but not a particularly good idea, because it’s a design smell that indicates a LEAKY ABSTRACTION:

[An] interface [...] generally shouldn’t be disposable. There’s no way for the one defining an interface to foresee all possible implementations of it - you can always come up with a disposable implementation of practically any interface.

Nicholas Blumhardt on the Common Context Adapters discussion forum[7]

7http://cca.codeplex.com/discussions/82987?ProjectName=cca

If you feel the urge to add IDisposable to your interface, it’s probably because you have a particular implementation in mind. But you must not let that knowledge leak through to the interface design. Doing so would make it more difficult for other classes to implement the interface and would introduce vagueness into the ABSTRACTION. Who is responsible for disposing of a disposable DEPENDENCY? Could it be the consumer?

8.2.1. Consuming disposable Dependencies

For the sake of argument, imagine that we have a disposable ABSTRACTION like this abstract OrderRepository class:

public abstract class OrderRepository : IDisposable

How should an OrderService class deal with such a DEPENDENCY? Most design guidelines (including FxCop and Visual Studio’s built-in Code Analysis) would insist that if a class holds a disposable resource as a member, it should itself implement IDisposable and dispose of the resource like this:

protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        this.repository.Dispose();
    }
}

But this turns out to be a spectacularly bad idea because the repository member was originally injected, and it may be shared by other consumers as shown in figure 8.6.

Figure 8.6. A single instance of SqlOrderRepository is injected into both OrderService and SupplierReorderPolicy. These two instances share the same DEPENDENCY. If OrderService disposes its injected OrderRepository, it destroys SupplierReorderPolicy’s DEPENDENCY, and exceptions will be thrown when SupplierReorderPolicy tries to use it.

It would be less dangerous not to dispose of the injected repository, but this means we’re essentially ignoring the fact that the ABSTRACTION is disposable. In other words, declaring an ABSTRACTION as deriving from IDisposable provides no benefit.

Then again, there can be scenarios where we need to signal the beginning and end of a short-lived scope, and IDisposable is sometimes used for that purpose. Before we examine how a COMPOSER can manage the lifetime of a disposable DEPENDENCY, we should consider how to deal with such ephemeral disposables.

Creating ephemeral disposables

Many APIs in the .NET Base Class Library (BCL) use IDisposable to signal that a particular scope has ended. One of the more prominent examples is WCF proxies.

 

WCF proxies and IDisposable

All auto-generated WCF proxies implement IDisposable, and it’s important to remember to invoke the Dispose (or Close) method on a proxy as soon as possible. Many bindings automatically create a session on the service when they submit the first request, and this session lingers in the service until it times out or is explicitly disposed.

If we forget to dispose of our WCF proxies after use, the number of sessions will increase until we hit the limit for concurrent connections from the same source. When we reach the limit, exceptions are thrown. Too many sessions also place an undue burden on the service, so disposing of WCF proxies as soon as possible is important.

To be entirely technically correct, we don’t have to invoke the Dispose method on a WCF proxy. Using the Close method achieves the same result.

 

It’s important to remember that the use of IDisposable for such purposes need not indicate a LEAKY ABSTRACTION, because these types aren’t always ABSTRACTIONS in the first place. On the other hand, some of them are; and when that is the case, how do we deal with them?

Fortunately, after an object is disposed of, we can’t reuse it. This means if we want to invoke the same API again, we must create a new instance. As an example, this fits well with how we use WCF proxies or ADO.NET commands: we create the proxy, invoke its operations, and dispose of it as soon as we’re finished. How can we reconcile this with DI if we consider disposable ABSTRACTIONS to be LEAKY ABSTRACTIONS?

As always, hiding the messy details behind an interface can be helpful. If we return to the WPF application from section 7.4, we hid the WCF proxy behind an IProductManagementAgent interface.

 

Note

The IProductManagementAgent interface is most notable in listing 7.10, but apart from that, we didn’t look at this interface in detail. In essence, such an agent occupies the same spot as a Repository, but many years ago I picked up the habit of naming the data access components of Smart Clients agent instead of repository.

 

From the MainViewModel’s perspective, this is how you delete a product:

this.agent.DeleteProduct(productId);

You ask the injected agent to delete the product. MainViewModel can safely hold a reference to the agent because the IProductManagementAgent interface doesn’t derive from IDisposable.

Another picture forms when we look at the WCF implementation of that interface. Here is the implementation of the DeleteProduct method:

public void DeleteProduct(int productId)
{
    using (var channel = this.factory.CreateChannel())
    {
        channel.DeleteProduct(productId);
    }
}

The WcfProductManagementAgent class has no mutable state but does have an injected Abstract Factory[8] you can use to create a channel. Channel is just another word for a WCF proxy, and it’s the auto-generated client interface you get for free when you create a service reference with Visual Studio or svcutil.exe. Because this interface derives from IDisposable, you can wrap it in a using statement.

8 Gamma, Design Patterns, 87.

You use the channel to delete the product. When you exit the using scope, the channel is disposed of.

But wait! Didn’t I claim that a disposable ABSTRACTION is a LEAKY ABSTRACTION? Yes, I did, but I have to balance pragmatic concerns against principles. In this case, WcfProductManagementAgent, the Abstract Factory IProductChannelFactory, and IProductManagementServiceChannel are all defined in the same, WCF-specific library outlined in figure 8.7.

Figure 8.7. Among other types, the ProductWcfAgent library contains the implementation of IProductManagementAgent and its supporting types. WcfProductManagementAgent uses an IProductChannelFactory to create instances of IProductManagementServiceChannel, which is disposable. Although this can be considered a LEAKY ABSTRACTION, it doesn’t leak very far because all consumers and implementers are contained in the same assembly.

Every time you invoke a method on the WcfProductManagementAgent class, it quickly opens a new channel and disposes it after use. Its lifetime is extremely short, which is why I call such a disposable ABSTRACTION an ephemeral disposable.

Notice that the ephemeral disposable is never injected into the consumer. Instead, an Abstract Factory is injected, and you use that factory to control the lifetime of the ephemeral disposable.

In summary, disposable ABSTRACTIONS are LEAKY ABSTRACTIONS. Sometimes we must accept such a leak to avoid bugs (such as refused WCF connections); but when we do that, we can do our best to contain that leak so it doesn’t propagate throughout an entire application.

We’ve now examined how to consume disposable DEPENDENCIES. Let’s turn our attention to how we can serve and manage them for consumers.

8.2.2. Managing disposable Dependencies

Because I so adamantly insist that disposable ABSTRACTIONS are LEAKY ABSTRACTIONS, the consequence is that ABSTRACTIONS shouldn’t be disposable. On the other hand, sometimes implementations are disposable, and if we don’t properly dispose of them, we’ll have resource leaks in our applications. Someone must dispose of them.

 

Tip

Try hard to implement services so they don’t hold references to disposables, but rather create and dispose of them on demand as illustrated in figure 6.3. This makes memory management much simpler, because the service can be garbage-collected like other objects.

 

As always, this responsibility falls on the COMPOSER (such as a DI CONTAINER). It, better than anyone else, knows when it creates a disposable instance, so it also knows that the instance needs to be disposed of. It’s easy for the COMPOSER to keep a reference to the disposable instance and invoke its Dispose method at an appropriate time.

The challenge lies in identifying when the time to dispose is appropriate. How do we know when all consumers have gone out of scope?

Unless someone tells us when that happens, we don’t know, but often our code lives inside some sort of context with a well-defined lifetime and events that tell us when a specific scope completes. Table 8.1 shows the scopes for the technologies we looked at in chapter 7.

Table 8.1. Entry and exit points for various .NET Frameworks

Technology

Entry point

Exit point

Console applications Main[*] Main[*]
ASP.NET MVC IControllerFactory.CreateController IControllerFactory.ReleaseController
WCF IInstanceProvider.GetInstance IInstanceProvider.ReleaseInstance
WPF Application.OnStartup Application.OnExit
ASP.NET Constructors[**], Page_Load IDisposable.Dispose[**], Page_Unload
PowerShell Constructors[**] IDisposable.Dispose[**]

* The Main method is both the entry and exit point because the application starts when it enters Main and ends when it exits. Use the beginning of Main to resolve DEPENDENCIES and the end to release them.

** We can resolve DEPENDENCIES in constructors, and both ASP.NET and PowerShell at least have the decency to invoke Dispose if we implement IDisposable.

We can use the various exit points to tell the COMPOSER that it should release all DEPENDENCIES for a given object. It’s then up to the COMPOSER to keep track of those DEPENDENCIES and their lifestyles and to decide whether anything must be disposed.

Releasing Dependencies

Releasing an object graph isn’t the same as disposing of it. It’s a signal to the COMPOSER that the root of the graph goes out of scope, so if the root itself implements IDisposable, it should be disposed of. But the root’s DEPENDENCIES may be shared with other roots, so the COMPOSER may decide to keep some of them around because it knows other objects still rely on them. Figure 8.8 illustrates the sequence of events.

Figure 8.8. When a COMPOSER is asked to resolve an object, it gathers all of the requested object’s DEPENDENCIES. In this case, the requested object has three DEPENDENCIES, and two of them are disposable. One of these disposable DEPENDENCIES is shared with other consumers, so it’s reused, whereas the other DEPENDENCIES are instantiated on the spot. When the request to release the object comes in, the COMPOSER disposes of the private disposable DEPENDENCY and lets the non-disposable DEPENDENCY and the object itself go out of scope. The only interaction with the shared DEPENDENCY is that it’s injected into the requested object; but because it’s shared, it isn’t disposed of (yet).

To release DEPENDENCIES, a COMPOSER must track all disposable DEPENDENCIES it has ever served and to which consumers it served them, so it can dispose of them when the last consumer is released.

 

Tip

If you’ve ever worked with reference counts (or experienced bugs because of bad implementations), you’ll appreciate how complex it can be to keep tabs on all DEPENDENCIES and their consumers. This is an area where a DI CONTAINER shines because it takes care of all that for you. Use a DI CONTAINER instead of developing your own lifetime-tracking code. A DI CONTAINER’s implementation of LIFETIME MANAGEMENT is guaranteed to be more thoroughly tested than anything you can produce within a reasonable timeframe.

 

Let’s go back to the WCF service example from section 8.1.2. As it turns out, there is a bug in listing 8.2 because, as figure 8.9 shows, SqlProductRepository implements IDisposable.

Figure 8.9. SqlProductRepository implements IDisposable because it encapsulates a disposable resource. It also derives from the abstract ProductRepository class that doesn’t implement IDisposable.

The code in listing 8.2 creates new instances of SqlProductRepository, but it never releases those instances. This will cause resource leaks, so let’s fix that bug with a new version of the specialized container.

First, keep in mind that the container must be able to service many concurrent requests, so it has to associate each SqlProductRepository instance with the IProductManagementService it creates. The container uses a Dictionary<IProductManagementService, SqlProductRepository> called repositories to keep track of those associations. The following listing shows how the container resolves requests for IProductManagementService instances.

Listing 8.4. Associating disposable DEPENDENCIES with a resolved root
public IProductManagementService ResolveProductManagementService()
{
    var repository = new SqlProductRepository(this.connectionString);
    var srvc = new ProductManagementService(repository, this.mapper);

    lock (this.syncRoot)
    {
        this.repositories.Add(srvc, repository);
    }

    return srvc;
}

The method starts by resolving all the DEPENDENCIES. This is similar to the implementation in listing 8.2. But before returning the resolved service, the container must remember the association between the service and the repository.

There is only one instance of the container in the WCF application, and because it’s likely that it will receive concurrent requests, you need to lock the dictionary before you add the repository to it. Adding items to a dictionary isn’t a thread-safe operation, so you need the lock to ensure that all repositories are saved for later even from within concurrent calls.

If you refer back to listing 7.7, you’ll notice that the IInstanceProvider implementation already calls the Release method on the container. So far, you haven’t implemented this method, relying on the garbage collector to do the job; but with disposable DEPENDENCIES it’s essential that you grasp this opportunity to clean up. Here’s the implementation.

Listing 8.5. Releasing disposable DEPENDENCIES

Because the Release method accepts any type of object, you first need a Guard Clause to make sure that instance is an IProductManagementService.

Concurrent threads may invoke the Release method simultaneously, so once more you must serialize access to the repositories dictionary to ensure that concurrent threads don’t corrupt its state. It could lead to memory leaks if repositories aren’t removed from the dictionary.

The srvc variable acts as a key to the dictionary, so you can use it to find the disposable DEPENDENCY. When you have it, you can dispose of it and remove it from the dictionary to ensure that the container doesn’t keep it alive for no good reason.

The examples shown in listings 8.4 and 8.5 are specialized to deal with one particular disposable DEPENDENCY: SqlProductRepository. It would be trivial to expand the code to be able to deal with any sort of disposable DEPENDENCY, but after that it becomes more difficult. Imagine having to deal with multiple disposable DEPENDENCIES for the same object, or nested disposable DEPENDENCIES, where some of them should be SINGLETONS and some of them TRANSIENT—and we haven’t even begun to discuss more advanced lifestyles!

 

Tip

Do yourself a favor and use a DI CONTAINER instead of trying to address all those issues in custom code. The only reason I show this custom code is to explain the principles of LIFETIME MANAGEMENT.

 

DI CONTAINERS can deal with complex combinations of lifestyles, and they offer opportunities (such as a Release method) to explicitly release components when we’re finished with them. We must remember to use these methods to avoid memory leaks, particularly when one or more of the configured DEPENDENCIES are disposable.

We’ve now discussed LIFETIME MANAGEMENT in some detail. As a consumer, we can’t manage the lifetime of injected DEPENDENCIES; that responsibility falls on the COMPOSER, who may decide to share a single instance among many consumers or give each consumer its own private instance. These SINGLETON and TRANSIENT lifestyles are only the most common members of a larger set of lifestyles, and we’ll use the rest of the chapter to work our way through a catalog of life cycle strategies.

8.3. Lifestyle catalog

Now that we’ve covered the principles behind LIFETIME MANAGEMENT in the previous sections, we’ll spend the rest of this chapter looking at common lifestyle patterns.

 

Note

I’ll use comparable examples throughout this section. But to allow us to focus on the essentials, I’ll compose shallow hierarchies, and I’ll sometimes ignore the issue with disposable DEPENDENCIES to avoid that added complexity.

 

Because you’ve already encountered both SINGLETON and TRANSIENT, we’ll begin with them and then expand to other types. As we progress through those lifestyles, we’ll move from commonplace lifestyles to more exotic ones, as described in table 8.2.

Table 8.2. Lifestyle patterns covered in this section

Name

Description

SINGLETON A single instance is perpetually reused.
TRANSIENT New instances are always served.
PER GRAPH A single instance is reused within each object graph.
WEB REQUEST CONTEXT At most one instance of each type is served per web request.
POOLED Instances are served from a pool of ready objects.
Lazy An expensive DEPENDENCY is lazily created and served.
Future A DEPENDENCY becomes available in the future.

Although you may rarely use lifestyles such as POOLED, it’s good to know about them, and this list should give you a good indication of the wide range of lifestyles available. Compared to advanced lifestyles, SINGLETON may seem mundane, but it’s nevertheless a common and appropriate life cycle strategy.

8.3.1. Singleton

In this book, we have implicitly used the SINGLETON lifetime style from time to time. The name is both clear and somewhat confusing at the same time. It makes a lot of sense because the resulting behavior is similar to the Singleton design pattern,[9] but the structure is different.

9 Ibid., 127.

 

Warning

Don’t confuse the SINGLETON lifestyle with the Singleton design pattern.

 

Within the scope of a single COMPOSER, a component with SINGLETON lifestyle behaves much like a SINGLETON. Each and every time a consumer requests the component, the same instance is served.

But the similarity ends there. A consumer can’t access a SINGLETON-scoped DEPENDENCY through a static member, and if we ask two different COMPOSERS to serve us an instance, we’ll get two different instances.

 

Tip

Use the SINGLETON lifestyle whenever it’s possible.

 

Because only a single instance is in use, the SINGLETON lifestyle generally consumes a minimal amount of memory. The only time this isn’t the case is when the instance is used rarely but consumes inordinate amounts of memory. In such cases, a Lazy lifestyle backed by a TRANSIENT instance may be a better configuration (but I have a hard time coming up with a reasonable example for this).

When to use it

Use the SINGLETON lifestyle when possible. The main issue preventing you from using SINGLETON is when a component isn’t thread-safe. Because the SINGLETON instance is shared among potentially many consumers, it must be able to handle concurrent access.

All stateless services are by definition thread-safe, as are immutable types and obviously classes specifically designed to be thread-safe. In these cases, there is no reason not to configure them as SINGLETONS.

In addition to the argument for efficiency, some DEPENDENCIES may work as intended only if they’re shared. For example, this is the case for implementations of the Circuit Breaker[10] design pattern, as well as in-memory caches. In these cases, it’s essential that the implementations are thread-safe.

10 Nygard, Release It, 104.

Let’s take a closer look at an in-memory repository.

Example: using a thread-safe in-memory repository

Let’s once more turn our attention to implementing an ICommerceServiceContainer like those from sections 7.3.2, 8.1.2, and 8.2.2. Instead of using a SQL Server–based ProductRepository, we could decide to use a thread-safe in-memory implementation. For an in-memory data store to make any sense, it must be shared among all requests, so it has to be thread-safe as illustrated in figure 8.10.

Figure 8.10. When multiple ProductManagementService instances running on separate threads access a shared resource such as an in-memory ProductRepository, we must ensure that the shared resource is thread-safe.

Instead of explicitly implementing such a repository as a SINGLETON, we can use a concrete class and scope it appropriately. The only requirement is that it must be thread-safe.

Listing 8.6 shows how a container can return new instances every time it’s asked to resolve an IProductManagementService, while the ProductRepository is shared among all instances.

Listing 8.6. Managing SINGLETONS

The SINGLETON lifetime is pretty easy to implement: you keep a reference to each DEPENDENCY for the duration of the container’s lifetime. Notice that you use the readonly keyword to ensure that you can’t accidentally change the references at a later date. This isn’t strictly necessary to implement the SINGLETON lifestyle but provides a bit of extra safety at the cost of writing eight letters.

Every time the container is asked to resolve an IProductManagementService instance, it creates a TRANSIENT instance with the SINGLETONS injected into it . In this example, both repository and mapper are SINGLETONS, but you can mix lifestyles if you wish.

The SINGLETON lifestyle is one of the easiest lifestyles to implement. All it requires is that you keep a reference to the object and serve the same object every time it’s requested. The instance doesn’t go out of scope until the COMPOSER goes out of scope. When that happens, the COMPOSER should dispose of the object if it’s a disposable type.

Another lifestyle that is trivial to implement is the TRANSIENT lifestyle.

8.3.2. Transient

The TRANSIENT lifestyle involves returning a new instance every time it’s requested. Unless the instance returned implements IDisposable, there is nothing to keep track of. Conversely, when the instance implements IDisposable, the COMPOSER must keep it in mind and explicitly dispose of it when asked to release the applicable object graph.

It’s worth noting that in desktop and similar applications, we tend to resolve the entire object hierarchy only once: at application startup. This means even for TRANSIENT components, only a few instances will be created, and they may be around for a long time. In the degenerate case where there is only one consumer per DEPENDENCY, the end result of resolving a graph of pure TRANSIENT components is equivalent to resolving a graph of pure SINGLETONS, or any mix thereof. This is because the graph is resolved only once, so the difference in behavior never kicks in.

When to use it

The TRANSIENT lifestyle is the safest choice of lifestyles but also one of the least efficient, because it can cause a myriad of instances to be created and garbage-collected even when a single instance would have sufficed. But if you have doubts about the thread-safety of a component, the TRANSIENT lifestyle is safe because each consumer has its own instance of the DEPENDENCY.

In many cases, we can safely exchange the TRANSIENT lifestyle for a context-scoped lifestyle such as WEB REQUEST CONTEXT where access to the DEPENDENCY is also guaranteed to be serialized, but that depends on the runtime environment (WEB REQUEST CONTEXTS make no sense in a desktop application).

Example: resolving multiple repositories

You saw several examples of using the TRANSIENT lifestyle earlier in this chapter. In listing 8.2, the repository is created and injected on the spot in the resolving method, and the container keeps no reference to it. In listings 8.4 and 8.5, you subsequently saw how to deal with a TRANSIENT disposable component.

In these examples, you may have noticed that the mapper stays a SINGLETON throughout. This is a purely stateless service, so there is no reason to create a new instance for every ProductManagementService created. The noteworthy point is that you can mix DEPENDENCIES with different lifestyles.

When multiple components require the same DEPENDENCY, each is given a separate instance. The following listing shows a method resolving an ASP.NET MVC Controller.

Listing 8.7. Resolving TRANSIENT DiscountRepositorys

Both the DiscountCampaign and RepositoryBasketDiscountPolicy classes require a DiscountRepository DEPENDENCY. When the DiscountRepository is TRANSIENT, each consumer gets it own private instance, so DiscountCampaign gets one instance and RepositoryBasketDiscountPolicy gets another .

The TRANSIENT lifestyle implies that every consumer receives a private instance of the DEPENDENCY even when multiple consumers in the same object graph have the same DEPENDENCY (as is the case in listing 8.7). If many consumers share the same DEPENDENCY, this approach can be inefficient; but if the implementation isn’t thread-safe, the more efficient SINGLETON lifestyle is inappropriate. In such cases, the PER GRAPH lifestyle may be a better fit.

8.3.3. Per Graph

SINGLETON is the most efficient lifestyle and TRANSIENT is the safest, but can we devise a lifestyle that combines the advantages of both? Although we can’t get the best of both worlds, in some cases it makes sense to share a single instance across a single resolved graph. We can view this as a sort of locally scoped SINGLETON. We can use a shared instance within a single object graph, but we don’t share that instance with other graphs.

Each time we resolve an object graph, we create only a single instance of each DEPENDENCY. If there are multiple consumers of that DEPENDENCY, they share the same instance; but when we resolve a new object graph, we create a new instance.

When to use it

We can use the PER GRAPH lifestyle in most cases where we would otherwise use TRANSIENT. We normally assume that the thread that resolves the object graph is also the only consumer of that object graph. Even when the DEPENDENCY in question isn’t thread-safe, we can use the PER GRAPH lifestyle, because the shared instance is only shared by consumers running on the same thread.

In the rare cases where one or more consumers spin up new threads and consume the DEPENDENCY from those threads, TRANSIENT is still the safest lifestyle, but that should be a rare occurrence. There may be other cases when the DEPENDENCY represents a mutable resource and each consumer needs its own private state. In such a case, TRANSIENT is the correct life cycle because it guarantees that the instances are never shared.

Compared to TRANSIENT, there is no additional overhead from using PER GRAPH, so we can often use it as a replacement for TRANSIENT. Although there is no overhead, we also aren’t guaranteed any benefit. We only gain a boost in efficiency if a single object graph contains multiple consumers of the same DEPENDENCY. In this case, we can share an instance between those consumers; but if there are no shared DEPENDENCIES, there will be nothing to share and so no benefit.

 

Note

PER GRAPH is superior to TRANSIENT in the majority of cases, but not many DI CONTAINERS support it out of the box.

 

In the cases where the implementation is thread-safe, the SINGLETON lifestyle is still a more efficient choice.

Example: sharing a repository within a graph

In listing 8.7, you saw how each consumer received its own private SqlDiscountRepository instance. This class isn’t thread-safe, so you shouldn’t configure it as a SINGLETON. But you don’t expect multiple threads to access individual HomeController instances, so it’s safe to share a SqlDiscountRepository instance between both consumers. The next listing shows how to create a single instance PER GRAPH to the ResolveHomeController method.

Listing 8.8. Resolving a single repository per graph

Instead of creating separate instances for all consumers, you create a single instance that you can share among all consumers . You inject this single instance into both DiscountCampaign and RepositoryBasketDiscountPolicy . Notice that compared to SINGLETONS, where the shared instance is a private member of the container, the repository instance is local to the ResolveHomeController method; the next time the method is invoked, a new instance is created and shared among the two consumers.

The PER GRAPH lifestyle is a good alternative to TRANSIENT when the only reason not to use SINGLETON is because the implementation isn’t thread-safe. Although PER GRAPH offers a generally usable solution to sharing DEPENDENCIES within a well-defined scope, there are other, more specialized alternatives.

8.3.4. Web Request Context

As users of a web application, we would like a response from the application as quickly as possible, even when other users use it at the same time. We don’t want our request to be put on a queue together with all the other users’ requests. We might have to wait an inordinately long time for a response if there were many requests ahead of ours.

To address this issue, web applications handle requests concurrently. The .NET infrastructure shields us from this by letting each request execute in its own context and with its own instance of Controllers (if you use ASP.NET MVC) or Pages (if you use ASP.NET Web Forms).

Because of concurrency, DEPENDENCIES that aren’t thread-safe can’t be used as SINGLETONS. On the other hand, using them as TRANSIENTS may be inefficient or even downright problematic if we need to share a DEPENDENCY between different consumers within the same request.

Although the ASP.NET engine doesn’t guarantee that a single request executes entirely on a single thread, it does guarantee that code is executed in a serialized manner. This means that if we can share a DEPENDENCY only within a single request, thread-safety isn’t an issue.

Figure 8.11 demonstrates how the WEB REQUEST CONTEXT lifestyle works. DEPENDENCIES behave like SINGLETONS within a single request but aren’t shared across requests. Each request has its own set of associated DEPENDENCIES.

Figure 8.11. The WEB REQUEST CONTEXT lifestyle indicates that we create at most one instance per web request. The DiscountRepository instance is shared between BasketDiscountPolicy and DiscountCampaign, but only within Request 1. Request 2 uses the same configuration, but instances are constrained to that request.

Any disposable components should be disposed of when the request ends.

When to use it

The WEB REQUEST CONTEXT lifestyle obviously makes sense only in a web application. Even within a web application, it can only be used in requests. Although requests tend to constitute the vast majority of a web application, it’s worth noting that if we spin up a background thread for asynchronous processing, this lifestyle doesn’t apply because the background thread won’t be synchronized with a web request.

The WEB REQUEST CONTEXT lifestyle is preferable to TRANSIENT, but the SINGLETON lifestyle is still more efficient. Use WEB REQUEST CONTEXT only in situations where SINGLETON won’t work.

 

Note

If you follow the general advice of only resolving a single object graph per web request, the WEB REQUEST CONTEXT and PER GRAPH lifestyles are functionally equivalent.

 

 

Tip

If you ever need to compose an Entity Framework ObjectContext in a web request, the WEB REQUEST CONTEXT is an excellent lifestyle. ObjectContext instances aren’t thread-safe, but there should be only one ObjectContext per web request.

 

Not all DI CONTAINERS support this lifestyle, so obviously we can only use it if it’s available.

 

Tip

Some DI CONTAINERS allow you to write your own lifestyle extension, so this may be an option if your container of choice doesn’t support the WEB REQUEST CONTEXT lifestyle out of the box. Still, this may not be a trivial undertaking.

 

As with other lifestyles, we can mix lifestyles so that, for example, some are configured as SINGLETONS and others are shared per web request.

Example: composing a HomeController with a request-shared repository

In this example, you’ll see how to compose an ASP.NET MVC HomeController instance with DEPENDENCIES that both require a DiscountRepository. This situation is outlined in figure 8.11: the HomeController requires a BasketDiscountPolicy and a DiscountCampaign, and both of these require a DiscountRepository.

 

Note

The example code in this section is more complex than warranted by a one-off solution. I would never expect you to write custom WEB REQUEST CONTEXT life cycle code like this, but I want to show you how it works. Use a DI CONTAINER that supports this lifestyle instead.

 

You want to use a shared SqlDiscountRepository, but because this class isn’t thread-safe you can’t share it as a SINGLETON. Instead, you’ll share it within each web request. The specialized container composes HomeController instances as shown in the following listing.

Listing 8.9. Composing HomeController

By now, most of the mechanics of this method should be familiar to you. The only noteworthy item is that you delegate resolution of the DiscountRepository to a separate method. This method ensures that at most one instance is resolved per web request.

When asked to resolve a DiscountRepository, the container must check if there is already an instance associated with the web request. If this is the case, that instance is returned; otherwise the instance is created and associated with the web request before it’s returned. As the next listing shows, in ASP.NET (both MVC and Web Forms) you can use the current HttpContext to maintain this association.

Listing 8.10. Resolving a web request context-scoped DEPENDENCY

The point of the WEB REQUEST CONTEXT lifestyle is to reuse instances already associated with the current request, so the first thing to do is check whether the desired instance already exists . If this is the case, you can return it. If the instance isn’t found, you must create it and associate it with the current web request before returning it.

The first time you invoke the ResolveDiscountRepository method, it creates the repository and associates it with the request so that every subsequent call reuses the same instance.

When the request ends, you may have left a disposable DEPENDENCY in the web request, which could lead to memory leaks, so you should also ensure that all DEPENDENCIES are released when the request ends. One way to do this is to register a custom IHttpModule that subscribes to the EndRequest event to properly dispose of all disposable DEPENDENCIES. The following listing shows a sample implementation.

Listing 8.11. Releasing disposable WEB REQUEST CONTEXT–scoped DEPENDENCIES

When a web request ends, you attempt to look up the repository in the request context . If you find it, you can dispose of it if applicable . Whether it’s disposable or not, you must remember to remove it from the request context.

The WEB REQUEST CONTEXT lifestyle associates a DEPENDENCY with the current request by saving and retrieving it via HttpContext.Current. This example demonstrated a specialized solution, but the technique can be generalized so that an arbitrary number of DEPENDENCIES of many different types can be associated with the request context. This is the realm of a proper DI CONTAINER.

Variation: Session Request Context

A rarer variation of the WEB REQUEST CONTEXT lifestyle is one where the scope of a DEPENDENCY’S lifetime is associated not with a particular web request but rather with a session. This is a much more exotic lifestyle, and you should exercise extreme caution if you decide to use it.

Technically, it may seem similar to the WEB REQUEST CONTEXT, but the most important distinction is that, whereas an HTTP request has a well-defined lifetime, sessions don’t. A session rarely ends explicitly but rather expires after a time of inactivity. This means all DEPENDENCIES registered this way are likely to be around for a long time where they aren’t being used. All that time they take up memory, which can severely impact an application’s capacity.

 

Warning

Only use the Session Request Context lifestyle if you really need it. It’s likely to degrade your system’s capacity.[11]

11 For an in-depth treatment of why session-scoped objects are problematic in general, see Michael T. Nygard, Release It! Design and Deploy Production-Ready Software (Raleigh, NC: Pragmatic Bookshelf, 2007), 175.

 

 

Tip

If you need to link certain DEPENDENCIES to a session, you’re better off configuring it with a WEB REQUEST CONTEXT and using a factory that wires up each instance based on the appropriate session key. This approach lets you more explicitly manage the lifetime of the DEPENDENCY while still linking it with a session.

 

Another issue we face is that session state may be saved in an out-of-process store, such as a separate session server or SQL Server session state. In these configurations, all session data must be serializable, and so must the affected DEPENDENCIES. Making a type serializable can be as simple as decorating it with the [Serializable] attribute, but it’s still something we must remember to do.

Overall, I find Session Request Context unattractive, and I can’t recall ever seeing it in use.

Variation: Thread Context

Another, more applicable, variation is to associate a DEPENDENCY with a particular thread. The concept is the same: the DEPENDENCY is managed as a SINGLETON on each thread, but there is an instance per thread.

This approach is mostly useful in scenarios where we spin up multiple equivalent worker threads and use the start of each thread as a COMPOSITION ROOT. This is the situation illustrated in figure 8.12.

Figure 8.12. When an application immediately spins up a number of parallel tasks and resolves DEPENDENCIES from within each thread, we can use the Thread Context lifestyle to ensure that any DEPENDENCIES that aren’t thread-safe can be shared among any number of consumers on the same thread. Each thread has its own instances.

To implement the Thread Context lifestyle, we can look after a requested DEPENDENCY in Thread Local Storage (TLS).[12] If we find the instance, we reuse it; otherwise we create it and store it in TLS.

12 We also used TLS in section 4.4.1.

Whereas Session Request Context may be downright dangerous and Thread Context a bit exotic, the WEB REQUEST CONTEXT lifestyle is useful. It enables us to share DEPENDENCIES within a web request without having to worry about whether they’re thread-safe. It provides a good middle ground between SINGLETON and TRANSIENT.

It provides a more efficient alternative to the TRANSIENT lifestyle, but we can only use it in web applications. If we have expensive DEPENDENCIES to manage in other types of applications, we can turn to other optimization techniques.

8.3.5. Pooled

Sometimes, components are expensive to create. A common solution is to have a pool of already-created components available for easy access. A well-known example is database connections, which are almost always pooled. We automatically use database-connection pooling, and we can use the same technique if we have custom components that are expensive to create.

Although the overall concept of pooled objects should be familiar to you, table 8.3 lists some variation in implementation.

Table 8.3. Options for implementing object pools

Option

Description

Pool preparation How do we prepare the pool? Do we create all the objects in the pool well in advance, or do we fill it gradually as requests arrive? Filling the pool in advance requires that we know at least the starting size of the pool. It may also be an expensive operation, because the purpose of the pool is to make expensive objects readily available. But the advantage of doing this is that the objects are available for fast access. Perhaps it’s even possible to prefill the pool from a background thread so it can begin serving objects while it’s filling. Alternatively, we can start with an empty pool and gradually fill it as required. This causes access times to be slower in the beginning but may help keep the pool at just the right size.
Minimum size We can address the issue of pool preparation by introducing a configurable minimum size. If we set the minimum size to anything other than zero, the pool must first fill itself to this point before it can start serving objects. At a minimum size of zero, on the other hand, it can begin serving objects immediately while filling the pool.
Maximum size What is the maximum size of the pool?
Boundary behavior What happens when we hit the maximum size of the pool? Do we allow the pool to grow? If so, we run the risk of running out of memory. If not, how do we treat additional requests for objects? One option is to block the call until an object becomes available. But if we do that, we should at least provide the caller with an opportunity to specify a timeout.[13] Another option is to immediately throw an exception.
Pool cleanup Do we keep the pool filled until the application shuts down, or do we begin to drain it if we notice that it has excess capacity?

13 Read more about the Blocked Threads anti-pattern and the Timeout pattern in Nygard, Release It, 70 and 100.

These are all relevant concerns regarding object pools. But as is the case with the WEB REQUEST CONTEXT lifestyle, we shouldn’t be custom-developing our own object pools, but rather should use those provided by DI CONTAINERS.

 

Note

Not all DI CONTAINERS provide the POOLED lifestyle, so we can obviously choose this lifestyle only if it’s supported by our DI CONTAINER.

 

When using the POOLED lifestyle provided by a DI CONTAINER, all the options described in table 8.3 may not be available. We have to go with what is available.

When to use it

The POOLED life cycle comes into play when we have specific components that are often used but expensive to create. Even if the component is expensive to create, we should still prefer the SINGLETON lifestyle if possible because that allows us to get by with a single instance and only pay the tax of creating the object once.

From this, it follows that pooling is applicable only when the component in question must not be shared, which is often the case when it isn’t thread-safe. If we’re running in a web application, the WEB REQUEST CONTEXT lifestyle may be a reasonable alternative; we should mostly expect to see the POOLED lifestyle used outside web applications.

Note that it’s a requirement that the component in question can be reused. If it has a natural life cycle that precludes reuse, we can’t pool it. One example is WCF’s ICommunicationObject interface, which has a clearly defined life cycle. When an ICommunicationObject is either Closed or Faulted, it can by definition never leave that state. Such a type of object isn’t eligible for pooling. We must be able to return the object back to the pool in pristine state.

Example: reusing expensive repositories

I once was involved in a project that required us to communicate with a mainframe from .NET code. Earlier consultants had created an unmanaged COM library that could talk to some endpoint on the mainframe, and we decided to wrap that library in a managed assembly.

The COM library communicated with the mainframe via a proprietary protocol over network sockets. To use it, we had to open the connection and go through a handshake. When the connection was open, we could transfer messages at a reasonable speed, but opening the connection took time.

Let’s see how to create a pool of ProductRepository instances that can communicate via such a protocol. In the project I was involved with, we called the COM library for Xfer (very generic), so let’s create a pool of XferProductRepository instances.

 

Note

As was the case with the WEB REQUEST CONTEXT lifestyle example, I don’t expect you to write custom object-pooling lifetime managers. Although you should use an appropriate DI CONTAINER to manage object pools, I want to show you a simplified example to give you an idea of how it works.

 

 

Warning

The following example isn’t thread-safe. I left out the synchronization code to keep the example at a reasonable level of complexity, and I leave a thread-safe implementation as an exercise to the reader (I have always wanted to write this).

 

This example is yet another variation of the ICommerceServiceContainer, of which you’ve seen several variations in this chapter. The following listing shows the foundation of the container.

Listing 8.12. Laying out a foundation for a pooling container
public partial class PooledContainer : ICommerceServiceContainer
{
    private readonly IContractMapper mapper;
    private readonly List<XferProductRepository> free;
    private readonly List<XferProductRepository> used;
    public PooledContainer()
    {
        this.mapper = new ContractMapper();
        this.free = new List<XferProductRepository>();
        this.used = new List<XferProductRepository>();
    }

    public int MaxSize { get; set; }

    public bool HasExcessCapacity
    {
        get
        {
            return this.free.Count + this.used.Count < this.MaxSize;
        }
    }
}

Although you plan to pool instances of XferProductRepository, you still configure ContractMapper as a SINGLETON because it’s a stateless service.

To keep track of the pool, you use two collections: one that holds available repositories and one that contains the repositories that are currently in use. When you create and release components, you’ll move repositories between these two collections.

The MaxSize property lets you define the maximum size of the pool, and the HasExcessCapacity property is essentially an encapsulated calculation you can use in a conditional expression to determine whether you still have excess capacity.

In this pool variation, you’ll fill the pool gradually as requests arrive, until you reach the maximum. As the next listing shows, you throw an exception if you reach capacity and get more requests.

Listing 8.13. Resolving repositories from a pool

To resolve an IProductManagementService instance, you begin by checking whether a reusable repository is available. If this is the case, you pick one from the collection of free repositories and move it to the list of repositories in use . If you succeed in finding a reusable repository, you can return the service immediately.

If you can’t find an available repository in the pool, there are two possible reasons: the pool is full and all repositories are in use, or you have yet to fill the pool. If you can get past the Guard Clause that checks for the first case, you create a new instance of the expensive repository and add it to the collection of repositories in use before you return the composed service.

ResolveProductManagementService only moves repositories from the free to the used collection, so it’s important to release the services after use. The following listing shows how to do this.

Listing 8.14. Returning repositories to the pool

Returning the repository to the pool is easy: you move it from the collection of repositories in use to the collection of available repositories.

Note that even though this example may seem complex, I didn’t address a few issues:

  • The example definitely isn’t thread-safe. A production implementation should allow several threads to resolve and release instances concurrently.
  • Because the XferProductRepository class encapsulates unmanaged code, it implements IDisposable. As long as you keep reusing instances, you need not dispose of them, but you should certainly do so when the container goes out of scope. Thus, the container itself must implement IDisposable and dispose of all repositories from its Dispose method.

Object pooling is a well-known design pattern, but it’s often encapsulated in existing APIs; for example, ADO.NET uses connection pools, but this isn’t something we have to explicitly deal with. Only when we explicitly need to optimize access to expensive resources does the POOLED lifestyle begin to make sense.

The POOLED lifestyle helps address the situation where we need to optimize the use of expensive resources. This is the last of the common DEPENDENCY lifestyle types.

8.3.6. Other lifestyles

The lifestyle types examined in this chapter represent the most common types, but you may have more exotic needs that aren’t satisfactorily addressed. When I find myself in such a situation, my first reaction is to feel immensely proud that I have discovered a rare and precious corner case that requires me to use an exotic item from my programming toolbox.

My next reaction is to realize that my approach is all wrong, and if I change my design a bit, everything will fit nicely into standard patterns. This realization is often a letdown, but it leads to better and more maintainable code. The point is that if you feel a need to implement a custom lifestyle, you should first seriously reconsider your design.

That said, some DI CONTAINERS provide extensibility points that let you develop custom lifestyles. Let’s briefly look at two technically possible, but rather exotic, lifestyles. In both cases, I provide only a brief sketch of how the lifetime would work. I don’t provide full sections because I’m having a hard time coming up with a reasonable scenario in which they should be applied.

Lazy

The Lazy or Delayed lifestyle is a Virtual Proxy[14] of a more expensive DEPENDENCY. The idea is that if we have an expensive DEPENDENCY that we don’t expect to use often, we can defer creation of the expensive DEPENDENCY until it’s needed. Figure 8.13 illustrates how a consumer can be injected with a lightweight stand-in for the actual, more expensive implementation.

14 Gamma, Design Patterns, 208.

Figure 8.13. A consumer requires an IService DEPENDENCY, but if it only uses this DEPENDENCY in a small fraction of the time, it can live for a long time before requiring the services of IService. When it finally invokes IService.SelectItem(), LazyService uses its injected IServiceFactory to create an instance of another IService. It isn’t until this point that the ExpensiveService instance is created. After ExpensiveService is created, all subsequent calls can be delegated to it.

It only makes sense to use such a lifetime style if the consumer only uses the expensive DEPENDENCY in a small fraction of its own lifetime, or if we can realistically expect that it will take a noticeable amount of time before the DEPENDENCY is invoked. If the DEPENDENCY is invoked immediately or often, the Lazy Decorator buys us nothing, but uses extra resources.

If at all possible, an expensive DEPENDENCY should be registered as a SINGLETON so we only need to pay the tax of creating it once. If this isn’t possible for thread-safety reasons, we can often better resolve this conundrum by pooling the expensive component. Even if we can only have a single instance, a pool of one combined with a timeout on access will effectively give us serialized access to the DEPENDENCY.

The Lazy lifetime style is more of a technical curiosity than a practically useful lifecycle strategy; if you’re curious, I refer you to the suggested literature associated with this book.[15]

15 Mark Seemann, “Rebuttal: Constructor over-injection anti-pattern,” 2010, http://blog.ploeh.dk/2010/01/20/RebuttalConstructorOverinjectionAntipattern.aspx

Future

The Future lifestyle is even more exotic. The idea is that we may want to use a DEPENDENCY that isn’t available at the moment but that we’ll use as it becomes available.

The best way to implement such a lifestyle is similar to the Lazy lifestyle: we can use a Decorator that delegates to an initial implementation until the desired DEPENDENCY becomes available. Figure 8.14 illustrates the conceptual interaction between components. The initial implementation used as a stand-in while the Future Decorator waits for the desired DEPENDENCY is often an application of the Null Object[16] design pattern.

16 Robert C. Martin et al., Pattern Languages of Program Design (New York: Addison-Wesley, 1998), 5.

Figure 8.14. A consumer requires an instance of IService, but DesiredService may not yet be available. In this case, we can encapsulate a NullService as a stand-in to be used while we wait for Godot. FutureService is a state machine that polls to see if DesiredService has become available. As long as it isn’t, the FutureService Decorator has no choice but to use the fallback implementation provided by NullService. When DesiredService finally becomes available, all future requests are directed to it.

I must admit that I’m hard pressed to come up with a reasonable example of when a DEPENDENCY may become available after we’ve wired up the entire object graph. This might sound a bit like the case where we rely on an external resource such as a database or web service, but keep in mind that even if the actual resource is unavailable, the programmatic DEPENDENCY still exists; for example, a web service may be down, but the WCF proxy we use to communicate with it is still available.

We can better deal with the issue of unavailable out-of-process resources using the Circuit Breaker pattern that we’ll look at in the next chapter. Until someone presents me with a reasonable scenario, I regard the Future life cycle strategy as a technical curiosity.

We’ve now looked at a wide range of available DEPENDENCY lifestyles, from the commonplace to the truly exotic.

8.4. Summary

When we apply INVERSION OF CONTROL to DEPENDENCIES, we invert control not only over type selection but also over LIFETIME MANAGEMENT. When a consumer no longer creates its own instances of DEPENDENCIES, it can’t decide when the DEPENDENCY was created or whether it’s shared with other consumers.

COMPOSERS may decide to let many consumers share a single instance, or they may decide to let each consumer have their own instance. More advanced strategies may also come into play.

Although COMPOSERS have a great deal of control over when objects are created, the managed memory model of .NET means that in many cases they have little influence over when objects are destroyed. DEPENDENCIES may go out of scope and be reclaimed by the garbage collector. But a special place is reserved for components that also implement IDisposable because we must ensure that all unmanaged resources are cleaned up—otherwise our applications will soon begin to experience memory leaks.

Equivalent to invoking a Resolve method (or whatever its name is), we must always remember to invoke a Release method when the resolved object graph goes out of scope. This gives the COMPOSER a chance to dispose of any disposable components that become unused.

Each dependency graph may have a mix of many different lifestyles, and we also need to keep track of whether the components are disposable. Add thread-safety to this mix, and it becomes complicated to keep track of all these things. This is an area where a full-blown DI CONTAINER shines, and it’s one of the many reasons we should use a DI CONTAINER instead of POOR MAN’S DI.

Each of the many available DI CONTAINERS offers its own mix of available lifestyles. Some support only a few, others support most or all of them, but many also offer extensibility points that allow us to implement our own lifestyles.

The safest lifestyle is TRANSIENT because instances aren’t shared with anyone else. It’s also the most inefficient because many instances of the same type are likely to be in memory.

The most efficient lifestyle is SINGLETON because only a single instance is in memory (per container, that is). But it requires that the component be thread-safe, so it isn’t always possible to use this lifestyle.

The WEB REQUEST CONTEXT and POOLED lifestyles provide good alternatives to SINGLETON and TRANSIENT, but in more limited scenarios.

More exotic lifestyles are possible. The Future lifestyle may at first glance look like a good way to handle unavailable resources, but as you’ll see in the next chapter, we can better address such issues with INTERCEPTION.

..................Content has been hidden....................

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