9
Interception

In this chapter

  • Intercepting calls between two collaborating objects
  • Understanding the Decorator design pattern
  • Applying Cross-Cutting Concerns using Decorators

One of the most interesting things about cooking is the way you can combine many ingredients, some of them not particularly savory in themselves, into a whole that’s greater than the sum of its parts. Often, you start with a simple ingredient that provides the basis for the meal, and then modify and embellish it until the end result is a delicious dish.

Consider a veal cutlet. If you were desperate, you could eat it raw, but in most cases you’d prefer to fry it. But if you slap it on a hot pan, the result will be less than stellar. Apart from the burned flavor, it won’t taste like much. Fortunately, there are lots of steps you can take to enhance the experience:

  • Frying the cutlet in butter prevents burning the meat, but the taste is likely to remain bland.
  • Adding salt enhances the taste of the meat.
  • Adding other spices, such as pepper, makes the taste more complex.
  • Breading it with a mixture that includes salt and spices not only adds to the taste, but also envelops the original ingredient in a new texture. At this point, you’re getting close to having a cotoletta.1 
  • Slitting open a pocket in the cutlet and adding ham, cheese, and garlic into the pocket before breading it takes us over the top. Now you have veal cordon bleu, a most excellent dish.

The difference between a burned veal cutlet and veal cordon bleu is significant, but the basic ingredient is the same. The variation is caused by the things you add to it. Given a veal cutlet, you can embellish it without changing the main ingredient to create a different dish.

With loose coupling, you can perform a similar feat when developing software. When you program to an interface, you can transform or enhance a core implementation by wrapping it in other implementations of that interface. You already saw a bit of this technique in action in listing 8.19, where we used this technique to modify an expensive Dependency’s lifetime by wrapping it in a Virtual Proxy.2 

This approach can be generalized, providing you with the ability to Intercept a call from a consumer to a service. This is what we’ll cover in this chapter.

Like the veal cutlet, we start out with a basic ingredient and add more ingredients to make the first ingredient better, but without changing the core of what it was originally. Interception is one of the most powerful abilities that you gain from loose coupling. It enables you to apply the Single Responsibility Principle and Separation of Concerns with ease.

In the previous chapters, we expended a lot of energy maneuvering code into a position where it’s truly loosely coupled. In this chapter, we’ll start harvesting the benefits of that investment. The overall structure of this chapter is pretty linear. We’ll start with an introduction to Interception, including an example. From there, we’ll move on to talk about Cross-Cutting Concerns. This chapter is light on theory and heavy on examples, so if you’re already familiar with this subject, you can consider moving directly to chapter 10, which discusses Aspect-Oriented Programming.

When you’re done with this chapter, you should be able to use Interception to develop loosely coupled code using the Decorator design pattern. You should gain the ability to successfully observe Separation of Concerns and apply Cross-Cutting Concerns, all while keeping your code in good condition.

This chapter starts with a basic, introductory example, building toward increasingly complex notions and examples. The final, and most advanced, concept can be quickly explained in the abstract. But, because it’ll probably only make sense with a solid example, the chapter culminates with a comprehensive, multipage demonstration of how it works. Before we get to that point, however, we must start at the beginning, which is to introduce Interception.

9.1 Introducing Interception

The concept of Interception is simple: we want to be able to intercept the call between a consumer and a service, and to execute some code before or after the service is invoked. And we want to do so in such a way that neither the consumer nor the service has to change.

For example, imagine you want to add security checks to a SqlProductRepository class. Although you could do this by changing SqlProductRepository itself or by changing a consumer’s code, with Interception, you apply security checks by intercepting calls to SqlProductRepository using some intermediary piece of code. In figure 9.1, a normal call from a consumer to a service is intercepted by an intermediary that can execute its own code before or after passing the call to the real service.

09-01.eps

Figure 9.1 Interception in a nutshell

In this section, you’re going to get acquainted with Interception and learn how, at its core, it’s an application of the Decorator design pattern. Don’t worry if your knowledge of the Decorator pattern is a bit rusty; we’ll start with a description of this pattern as part of the discussion. When we’re done, you should have a good understanding of how Decorators work. We’ll begin by looking at a simple example that showcases the pattern, and follow up with a discussion of how Interception relates to the Decorator pattern.

9.1.1 Decorator design pattern

As is the case with many other patterns, the Decorator pattern is an old and well-described design pattern that predates DI by a decade. It’s such a fundamental part of Interception that it warrants a refresher.

The Decorator pattern was first described in the book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma et al. (Addison-Wesley, 1994). The pattern’s intent is to “attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.”3 

As figure 9.2 shows, a Decorator works by wrapping one implementation of an Abstraction in another implementation of the same Abstraction. This wrapper delegates operations to the contained implementation, while adding behavior before and/or after invoking the wrapped object.

09-02.eps

Figure 9.2 General structure of the Decorator pattern

The ability to attach responsibilities dynamically means that you can make the decision to apply a Decorator at runtime rather than having this relationship baked into the program at compile time, which is what you’d do with subclassing.

A Decorator can wrap another Decorator, which wraps another Decorator, and so on, providing a “pipeline” of interception. Figure 9.3 shows how this works. At the core, there must be a self-contained implementation that performs the desired work.

09-03.eps

Figure 9.3 Like a set of Russian nesting dolls, a Decorator wraps another Decorator that wraps a self-contained component.4 

Let’s say, for instance, that you have an Abstraction called IGreeter that contains a Greet method:

public interface IGreeter
{
    string Greet(string name);
}

For this Abstraction, you can create a simple implementation that creates a formal greeting:

public class FormalGreeter : IGreeter
{
    public string Greet(string name)
    {
        return "Hello, " + name + ".";
    }
}

The simplest Decorator implementation is one that delegates the call to the decorated object without doing anything at all:

public class SimpleDecorator : IGreeter    ①  
{
    private readonly IGreeter decoratee;    ①  

    public SimpleDecorator(IGreeter decoratee)
    {
        this.decoratee = decoratee;
    }

    public string Greet(string name)
    {
        return this.decoratee.Greet(name);    ②  
    }
}

Figure 9.4 shows the relationship between IGreeter, FormalGreeter, and SimpleDecorator. Because SimpleDecorator doesn’t do anything except forward the call, it’s pretty useless. Instead, a Decorator can choose to modify the input before delegating the call.

09-04.eps

Figure 9.4 Both SimpleDecorator and FormalGreeter implement IGreeter, while SimpleDecorator wraps an IGreeter and forwards any calls from its Greet method to the Greet method of the decoratee.

Let’s take a look at the Greet method of a TitledGreeterDecorator class:

public string Greet(string name)
{
    string titledName = "Mr. " + name;
    return this.decoratee.Greet(titledName);
}

In a similar move, the Decorator may decide to modify the return value before returning it when you create a NiceToMeetYouGreeterDecorator:

public string Greet(string name)
{
    string greet = this.decoratee.Greet(name);
    return greet + " Nice to meet you.";
}

Given the two previous examples, you can wrap the latter around the former to compose a combination that modifies both input and output:

IGreeter greeter =
    new NiceToMeetYouGreeterDecorator(
        new TitledGreeterDecorator(
            new FormalGreeter()));

string greet = greeter.Greet("Samuel L. Jackson");
Console.WriteLine(greet);

This produces the following output:

09-09_hedgehog.eps

A Decorator may also decide not to invoke the underlying implementation:

public string Greet(string name)
{
    if (name == null)    ①  
    {
        return "Hello world!";
    }

    return this.decoratee.Greet(name);
}

Not invoking the underlying implementation is more consequential than delegating the call. Although there’s nothing inherently wrong with skipping the decoratee, the Decorator now replaces, rather than enriches, the original behavior.5  A more common scenario is to stop execution by throwing an exception, as we’ll discuss in section 9.2.3.

What differentiates a Decorator from any class containing Dependencies is that the wrapped object implements the same Abstraction as the Decorator. This enables a Composer to replace the original component with a Decorator without changing the consumer. The wrapped object is often injected into the Decorator declared as the abstract type — it wraps the interface, not a specific, concrete implementation. In that case, the Decorator must adhere to the Liskov Substitution Principle and treat all decorated objects equally.

That’s it. There isn’t much more to the Decorator pattern than this. You’ve already seen Decorators in action several places in this book. The SecureMessageWriter example in section 1.2.2, for instance, is a Decorator. Now let’s look at a concrete example of how we can use a Decorator to implement a Cross-Cutting Concern.

9.1.2 Example: Implementing auditing using a Decorator

In this example, we’ll implement auditing for the IUserRepository again. As you might recall, we discussed auditing in section 6.3, where we used it as an example when explaining how to fix Dependency cycles. With auditing, you record all of the important actions users make in a system for later analysis.

Auditing is a common example of a Cross-Cutting Concern: it may be required, but the core functionality of reading and editing users shouldn’t be affected by auditing. This is exactly what we did in section 6.3. Because we injected the IAuditTrailAppender interface into the SqlUserRepository itself, we forced it to know about and to implement auditing. This is a Single Responsibility Principle violation. The Single Responsibility Principle suggests that we shouldn’t let SqlUserRepository implement auditing; given this, using a Decorator is a better alternative.

Implementing an auditing Decorator for the user repository

You can implement auditing with a Decorator by introducing a new AuditingUserRepositoryDecorator class that wraps another IUserRepository and implements auditing. Figure 9.5 illustrates how the types relate to each other.

09-05.eps

Figure 9.5 AuditingUserRepositoryDecorator adds auditing to any IUserRepository implementation.

In addition to a decorated IUserRepository, AuditingUserRepositoryDecorator also needs a service that implements auditing. For this, you can use IAuditTrailAppender from section 6.3. The following listing shows this implementation.

Listing 9.1 Declaring an AuditingUserRepositoryDecorator

public class AuditingUserRepositoryDecorator
    : IUserRepository    ①  
{
    private readonly IAuditTrailAppender appender;
    private readonly IUserRepository decoratee;

    public AuditingProductRepository(
        IAuditTrailAppender appender,
        IUserRepository decoratee)    ①  
    {
        this.appender = appender;
        this.decoratee = decoratee;
    }

    ...
}

AuditingUserRepositoryDecorator implements the same Abstraction that it decorates. It uses standard Constructor Injection to request an IUserRepository that it can wrap and to which it can delegate its core implementation. In addition to the decorated Repository, it also requests an IAuditTrailAppender it can use to audit the operations implemented by the decorated Repository. The following listing shows sample implementations of two methods on AuditingUserRepositoryDecorator.

Listing 9.2 Implementing AuditingUserRepositoryDecorator

public User GetById(Guid id)    ①  
{    ①  
    return this.decoratee.GetById(id);    ①  
}    ①  

public void Update(User user)    ②  
{    ②  
    this.decoratee.Update(user);    ②  
    this.appender.Append(user);    ②  
}    ②  

Not all operations need auditing. A common requirement is to audit all create, update, and delete operations, while ignoring read operations. Because the GetById method is a pure read operation, you delegate the call to the decorated Repository and immediately return the result. The Update method, on the other hand, must be audited. You still delegate the implementation to the decorated Repository, but after the delegated method returns successfully, you use the injected IAuditTrailAppender to audit the operation.

A Decorator, like AuditingUserRepositoryDecorator, is similar to the breading around the veal cutlet: it embellishes the basic ingredient without modifying it. The breading itself isn’t an empty shell, but comes with its own list of ingredients. Real breading is made from breadcrumbs and spices; similarly, AuditingUserRepositoryDecorator contains an IAuditTrailAppender.

Note that the injected IAuditTrailAppender is itself an Abstraction, which means that you can vary the implementation independently of AuditingUserRepositoryDecorator. All the AuditingUserRepositoryDecorator class does is coordinate the actions of the decorated IUserRepository and IAuditTrailAppender. You can write any implementation of IAuditTrailAppender you like, but in listing 6.24, we chose to build one based on the Entity Framework. Let’s see how you can wire up all relevant Dependencies to make this work.

Composing AuditingUserRepositoryDecorator

In chapter 8, you saw several examples of how to compose a HomeController instance. Listing 8.11 provided a simple implementation concerning instances with a Transient Lifestyle. The following listing shows how you can compose this HomeController using a decorated SqlUserRepository.

Listing 9.3 Composing a Decorator

private HomeController CreateHomeController()
{
    var context = new CommerceContext();

    IAuditTrailAppender appender =
        new SqlAuditTrailAppender(
            this.userContext,
            context);

    IUserRepository userRepository =    ①  
        new AuditingUserRepositoryDecorator(    ①  
            appender,    ①  
            new SqlUserRepository(context));    ①  

    IProductService productService =
        new ProductService(
            new SqlProductRepository(context),
            this.userContext,
            userRepository);    ②  

    return new HomeController(productService);
}

Notice that you were able to add behavior to IUserRepository without changing the source code of existing classes. You didn’t have to change SqlUserRepository to add auditing. Recall from section 4.4.2 that this is a desirable trait known as the Open/Closed Principle.

Now that you’ve seen an example of intercepting the concrete SqlUserRepository with a decorating AuditingUserRepositoryDecorator, let’s turn our attention to writing clean and maintainable code in the face of inconsistent or changing requirements, and to addressing Cross-Cutting Concerns.

9.2 Implementing Cross-Cutting Concerns

Most applications must address aspects that don’t directly relate to any particular feature, but, rather, address a wider matter. These concerns tend to touch many otherwise unrelated areas of code, even in different modules or layers. Because they cut across a wide area of the code base, we call them Cross-Cutting Concerns. Table 9.1 lists some examples. This table isn’t a comprehensive listing; rather, it’s an illustrative sampling.

Table 9.1 Common examples of Cross-Cutting Concerns
AspectDescription
AuditingAny data-altering operation should leave an audit trail including timestamp, the identity of the user who performed the change, and information about what changed. You saw an example of this in section 9.1.2.
LoggingSlightly different than auditing, logging tends to focus on recording events that reflect the state of the application. This could be events of interest to IT operations staff, but might also be business events.
Performance monitoringSlightly different than logging because this deals more with recording performance than specific events. If you have Service Level Agreements (SLAs) that can’t be monitored via standard infrastructure, you must implement your own performance monitoring. Custom Windows performance counters are a good choice for this, but you must still add some code that captures the data.
ValidationOperations typically need to be called with valid data. This can be either simple user input validation or more complex business rule validation. Although validation itself is always dependent on its context, the invocation of that validation and the processing of the validation results often isn’t and can be considered to be cross-cutting.
SecuritySome operations should only be allowed for certain users, often based on membership in roles or groups, and you must enforce this.
CachingYou can often increase performance by implementing caches, but there’s no reason why a specific data access component should deal with this aspect. You may want the ability to enable or disable caching for different data access implementations.
Error handlingAn application may need to handle certain exceptions and log them, transform them, or show a message to the user. You can use an error-handling Decorator to deal with errors in a proper way.
Fault toleranceOut-of-process resources are guaranteed to be unavailable from time to time. Relational databases need to process transactional operations to prevent data corruption, which can lead to deadlocks. Using a Decorator, you can implement fault tolerance patterns, such as a Circuit Breaker, to address this.
09-06.eps

Figure 9.6 In application architecture diagrams, Cross-Cutting Concerns are typically represented by vertical blocks that span all layers. In this case, security is a Cross-Cutting Concern.

When you draw diagrams of layered application architecture, Cross-Cutting Concerns are often represented as vertical blocks placed beside the layers. This is shown in figure 9.6.

In this section, we’ll look at some examples that illustrate how to use Interception in the form of Decorators to address Cross-Cutting Concerns. From table 9.1, we’ll pick the fault tolerance, error handling, and security aspects to get a feel for implementing aspects. As is the case with many other concepts, Interception can be easy to understand in the abstract, but the devil is in the details. It takes exposure to properly absorb the technique, and that’s why this section shows three examples. When we’re done with these, you should have a clearer picture of what Interception is and how you can apply it. Because you already saw an introductory example in section 9.1.2, we’ll take a look at a more complex example to illustrate how Interception can be used with arbitrarily complex logic.

9.2.1 Intercepting with a Circuit Breaker

Any application that communicates with an out-of-process resource will occasionally find that the resource is unavailable. Network connections go down, databases go offline, and web services get swamped by Distributed Denial of Service (DDOS) attacks. In such cases, the calling application must be able to recover and appropriately deal with the issue.

Most .NET APIs have default timeouts that ensure that an out-of-process call doesn’t block the consuming thread forever. Still, in a situation where you receive a timeout exception, how do you treat the next call to the faulting resource? Do you attempt to call the resource again? Because a timeout often indicates that the other end is either offline or swamped by requests, making a new blocking call may not be a good idea. It would be better to assume the worst and throw an exception immediately. This is the rationale behind the Circuit Breaker pattern.

Circuit Breaker is a stability pattern that adds robustness to an application by failing fast instead of hanging and consuming resources as it hangs. This is a good example of a non-functional requirement and a true Cross-Cutting Concern, because it has little to do with the feature implemented in the out-of-process call.

The Circuit Breaker pattern itself is a bit complex and can be intricate to implement, but you only need to make that investment once. You could even implement it in a reusable library if you liked, where you could easily apply it to multiple components by employing the Decorator pattern.

The Circuit Breaker pattern

The Circuit Breaker design pattern takes its name from the electric switch of the same name.6  It’s designed to cut the connection when a fault occurs, preventing the fault from propagating.

In software applications, once a timeout or similar communications error occurs, it can make a bad situation worse if you keep hammering a downed system. If the remote system is swamped, multiple retries can take it over the edge — a pause might give it a chance to recover. On the calling tier, threads blocked waiting for timeouts can make the consuming application unresponsive, forcing a user to wait for an error message. It’s better to detect that communications are down and fail fast for a period of time.

The Circuit Breaker design addresses this by tripping the switch when an error occurs. It usually includes a timeout that makes it retry the connection later; this way, it can automatically recover when the remote system comes back up. Figure 9.7 illustrates a simplified view of the state transitions in a Circuit Breaker.

09-07.eps

Figure 9.7 Simplified state transition diagram of the Circuit Breaker pattern

You may want to make a Circuit Breaker more complex than described in figure 9.7. First, you may not want to trip the breaker every time a sporadic error occurs but, rather, use a threshold. Second, you should only trip the breaker on certain types of errors. Timeouts and communication exceptions are fine, but a NullReferenceException is likely to indicate a bug instead of an intermittent error.

Let’s look at an example that shows how the Decorator pattern can be used to add Circuit Breaker behavior to an existing out-of-process component. In this example, we’ll focus on applying the reusable Circuit Breaker, but not on how it’s implemented.

Example: creating a Circuit Breaker for IProductRepository

In section 7.2, we created a UWP application that communicates with a backend data source, such as a WCF or Web API service, using the IProductRepository interface. In listing 8.6, we used a WcfProductRepository that implements IProductRepository by invoking the WCF service operations. Because this implementation has no explicit error handling, any communication error will bubble up to the caller.

This is an excellent scenario in which to use a Circuit Breaker. You’d like to fail fast once exceptions start occurring; this way, you won’t block the calling thread and swamp the service. As the next listing shows, you start by declaring a Decorator for IProductRepository and requesting the necessary Dependencies via Constructor Injection.

Listing 9.4 Decorating with a Circuit Breaker

public class CircuitBreakerProductRepositoryDecorator    ①  
    : IProductRepository    ①  
{
    private readonly ICircuitBreaker breaker;
    private readonly IProductRepository decoratee;    ①  

    public CircuitBreakerProductRepositoryDecorator(
        ICircuitBreaker breaker,    ②  
        IProductRepository decoratee)
    {
        this.breaker = breaker;
        this.decoratee = decoratee;
    }

    ...
}

You can now wrap any call to the decorated IProductRepository.

Listing 9.5 Applying a Circuit Breaker to the Insert method

public void Insert(Product product)
{
    this.breaker.Guard();    ①  

    try
    {
        this.decoratee.Insert(product);  ②  
        this.breaker.Succeed();
    }
    catch (Exception ex)    ③  
    {    ③  
        this.breaker.Trip(ex);    ③  
        throw;    ③  
    }
}

The first thing you need to do before you invoke the decorated Repository is check the state of the Circuit Breaker. The Guard method lets you through when the state is either Closed or Half-Open, whereas it throws an exception when the state is Open. This ensures that you fail fast when you have reason to believe that the call isn’t going to succeed. If you make it past the Guard method, you can attempt to invoke the decorated Repository. If the call fails, you trip the breaker. In this example, we’re keeping things simple, but in a proper implementation, you should only catch and trip the breaker from a selection of exception types.

From both the Closed and Half-Open states, tripping the breaker puts you back in the Open state. From the Open state, a timeout determines when you move back to the Half-Open state.

Conversely, you signal the Circuit Breaker if the call succeeds. If you’re already in the Closed state, you stay in the Closed state. If you’re in the Half-Open state, you transition back to Closed. It’s impossible to signal success when the Circuit Breaker is in the Open state, because the Guard method ensures that you never get that far.

All other methods of IProductRepository look similar, with the only difference being the method they invoke on the decoratee and an extra line of code for methods that return a value. You can see this variation inside the try block for the GetAll method:

var products = this.decoratee.GetAll();
this.breaker.Succeed();
return products;

Because you must indicate success to the Circuit Breaker, you have to hold the return value of the decorated repository before returning it. That’s the only difference between methods that return a value and methods that don’t.

At this point, you’ve left the implementation of ICircuitBreaker open, but the real implementation is a completely reusable complex of classes that employ the State design pattern.7  Although we aren’t going to dive deeper into the implementation of CircuitBreaker here, the important message is that you can Intercept with arbitrarily complex code.

Composing the application using the Circuit Breaker implementation

To compose an IProductRepository with Circuit Breaker functionality added, you can wrap the Decorator around the real implementation:

var channelFactory = new ChannelFactory<IProductManagementService>("*");

var timeout = TimeSpan.FromMinutes(1);

ICircuitBreaker breaker = new CircuitBreaker(timeout);

IProductRepository repository =
    new CircuitBreakerProductRepositoryDecorator(    ①  
        breaker,
        new WcfProductRepository(channelFactory));

In listing 7.6, we composed a UWP application from several Dependencies, including a WcfProductRepository instance in listing 8.6. You can decorate this WcfProductRepository by injecting it into a CircuitBreakerProductRepositoryDecorator instance, because it implements the same interface. In this example, you create a new instance of the CircuitBreaker class every time you resolve Dependencies. That corresponds to the Transient Lifestyle.

In a UWP application, where you only resolve the Dependencies once, using a Transient Circuit Breaker isn’t an issue but, in general, this isn’t the optimal lifestyle for such functionality. There’ll only be a single web service at the other end. If this service becomes unavailable, the Circuit Breaker should disconnect all attempts to connect to it. If several instances of CircuitBreakerProductRepositoryDecorator are in use, this should happen for all of them.

There’s an obvious case for setting up CircuitBreaker with the Singleton lifetime, but that also means that it must be thread-safe. Due to its nature, CircuitBreaker maintains state; thread-safety must be explicitly implemented. This makes the implementation even more complex.

Despite its complexity, you can easily Intercept an IProductRepository instance with a Circuit Breaker. Although the first Interception example in section 9.1.2 was fairly simple, the Circuit Breaker example demonstrates that you can intercept a class with a Cross-Cutting Concern. The Cross-Cutting Concern can easily be more complex than the original implementation.

The Circuit Breaker pattern ensures that an application fails fast instead of tying up precious resources. Ideally, the application wouldn’t crash at all. To address this issue, you can implement some kinds of error handling with Interception.

9.2.2 Reporting exceptions using the Decorator pattern

Dependencies are likely to throw exceptions from time to time. Even the best-written code will (and should) throw exceptions if it encounters situations it can’t deal with. Clients that consume out-of-process resources fall into that category. A class like WcfProductRepository from the sample UWP application is one example. When the web service is unavailable, the Repository will start throwing exceptions. A Circuit Breaker doesn’t change this fundamental trait. Although it Intercepts the WCF client, it still throws exceptions — it does so quicker.

You can use Interception to add error handling. You don’t want to burden a Dependency with error handling. Because a Dependency should be viewed as a reusable component that can be consumed in many different scenarios, it wouldn’t be possible to add an exception-handling strategy to the Dependency that would fit all scenarios. It would also be a violation of the Single Responsibility Principle if you did.

09-08.tif

Figure 9.8 The product-management application handles communication exceptions by showing a message to the user. Notice that in this case, the error message originates from the Circuit Breaker instead of the underlying communication failure.

By using Interception to deal with exceptions, you follow the Open/Closed Principle. It allows you to implement the best error-handling strategy for any given situation. Let’s look at an example.

In the previous example, we wrapped WcfProductRepository in a Circuit Breaker for use with the product-management client application, which was originally introduced in section 7.2.2. A Circuit Breaker only deals with errors by making certain that the client fails fast, but it still throws exceptions. If left unhandled, they’ll cause the application to crash, so you should implement a Decorator that knows how to handle some of those errors.

Instead of a crashing application, you might prefer a message box that tells the user that the operation didn’t succeed and that they should try again later. In this example, when an exception is thrown, it should pop up a message as shown in figure 9.8.

Implementing this behavior is easy. The same way you did in section 9.2.1, you add a new ErrorHandlingProductRepositoryDecorator class that decorates the IProductRepository interface. Listing 9.6 shows a sample of one of the methods of that interface, but they’re all similar.

Listing 9.6 Handling exceptions with ErrorHandlingProductRepositoryDecorator

public void Insert(Product product)
{
    try
    {
        this.decoratee.Insert(product);    ①  
    }
    catch (CommunicationException ex)
    {
        this.AlertUser(ex.Message);    ②  
    }
    catch (InvalidOperationException ex)
    {
        this.AlertUser(ex.Message);    ②  
    }
}

The Insert method is representative of the entire implementation of the ErrorHandlingProductRepositoryDecorator class. You attempt to invoke the decoratee and alert the user with the error message if an exception is thrown. Notice that you only handle a particular set of known exceptions, because it can be dangerous to suppress all exceptions. Alerting the user involves formatting a string and showing it to the user using the MessageBox.Show method. This is done inside the AlertUser method.

Once again, you added functionality to the original implementation (WcfProductRepository) by implementing the Decorator pattern. You’re following both the Single Responsibility Principle and the Open/Closed Principle by continually adding new types instead of modifying existing code. By now, you should be seeing a pattern that suggests a more general arrangement than a Decorator. Let’s briefly glance at a final example, implementing security.

9.2.3 Preventing unauthorized access to sensitive functionality using a Decorator

Security is another common Cross-Cutting Concern. We want to secure our applications as much as possible to prevent unauthorized access to sensitive data and functionality.

Similar to how we used Circuit Breaker, we’d like to Intercept a method call and check whether the call should be allowed. If not, instead of allowing the call to be made, an exception should be thrown. The principle is the same; the difference lies in the criterion we use to determine the validity of the call.

A common approach to implementing authorization logic is to employ role-based security by checking the user’s role(s) against a hard-coded value for the operation at hand. If we stick with our IProductRepository, we might start out with a SecureProductRepositoryDecorator. Because, as you’ve seen in the previous sections, all methods look similar, the following listing only shows two method implementations.

Listing 9.7 Explicitly checking authorization with a Decorator

public class SecureProductRepositoryDecorator
    : IProductRepository
{
    private readonly IUserContext userContext;
    private readonly IProductRepository decoratee;

    public SecureProductRepositoryDecorator(
        IUserContext userContext,    ①  
        IProductRepository decoratee)
    {
        this.userContext = userContext;
        this.decoratee = decoratee;
    }

    public void Delete(Guid id)
    {
        this.CheckAuthorization();    ②  
        this.decoratee.Delete(id);
    }

    public IEnumerable<Product> GetAll()    ③  
    {
        return this.decoratee.GetAll();
    }
    ...
    private void CheckAuthorization()    ④  
    {    ④  
        if (!this.userContext.IsInRole(    ④  
            Role.Administrator))    ④  
        {    ④  
            throw new SecurityException(    ④  
                "Access denied.");    ④  
        }    ④  
    }    ④  
}

In our current design, for a given Cross-Cutting Concern, the implementation based on a Decorator tends to be repetitive. Implementing a Circuit Breaker involves applying the same code template to all methods of the IProductRepository interface. Had you wanted to add a Circuit Breaker to another Abstraction, you would’ve had to apply the same code to more methods.

With the security Decorator, it got even worse because we required some of the methods to be extended, whereas others are mere pass-through operations. But the overall problem is identical.

If you need to apply this Cross-Cutting Concern to a different Abstraction, this too will cause code duplication, which can cause major maintainability issues as the system gets bigger. As you might imagine, there are ways to prevent code duplication, bringing us to the important topic of Aspect-Oriented Programming, which we’ll discuss in the next chapter.

Summary

  • Interception is the ability to intercept calls between two collaborating components in such a way that you can enrich or change the behavior of the Dependency without the need to change the two collaborators themselves.
  • Loose coupling is the enabler of Interception. When you program to an interface, you can transform or enhance a core implementation by wrapping it in other implementations of that interface.
  • At its core, Interception is an application of the Decorator design pattern.
  • The Decorator design pattern provides a flexible alternative to subclassing by attaching additional responsibilities to an object dynamically. It works by wrapping one implementation of an Abstraction in another implementation of the same Abstraction. This allows Decorators to be nested like Russian nesting dolls.
  • Cross-Cutting Concerns are non-functional aspects of code that typically cut across a wide area of the code base. Common examples of Cross-Cutting Concerns are auditing, logging, validation, security, and caching.
  • Circuit Breaker is a stability design pattern that adds robustness to a system by cutting connections when a fault occurs in order to prevent the fault from propagating.
..................Content has been hidden....................

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