Chapter 12. Managing external system dependencies

This chapter covers

  • Interacting with external systems effectively
  • Testing the interaction with an external system
  • Reviewing useful patterns for dealing with external systems

No application is an island. That’s true now more than ever. Today’s applications are expected to not only provide their own functionality but also pull data from a supplier’s website, transform it using a third-party library, and republish the results to both your own database as well as that of, say, the HR department.

Indeed, there are entire products, services, frameworks, and companies dedicated solely to the purpose of integrating disparate systems. As companies have grown, shrunk, and merged, their systems often struggle to keep up. Many an “interim” solution has been in production for many years, forming a tenuous bond between two completely separated applications.

In this chapter, we’ll examine common problems that come with managing external dependencies. Then we’ll look at the types of dependencies you’ll encounter and discuss techniques for managing them effectively. We’ll also provide examples of ways to implement these techniques.

As usual, let’s first review the pain points.

12.1. Pain points

These days, working with an external system is almost a given in an application, like having a UI or a database. But like those other aspects, if it’s not done properly, your application can turn brown in a hurry.

Consider an application that uses a document repository to store documents about the oil and gas industry. An administrator adds documents on a regular basis, providing metadata about each one that’s stored in a database.

The documents are indexed using Microsoft Indexing Service. As users search for them, the application converts their queries into a format that the Indexing Service understands. The results are combined with the results for any metadata criteria that are provided and displayed on the screen.

Things are moving along nicely until the company decides to upgrade to Windows Server 2008. The server is configured using the new Windows Search capabilities built into Windows Server 2008. While the Indexing Service is still available, Windows Server 2008 doesn’t allow it and the search functionality to run at the same time. One must be turned off before the other is turned on.

Now arises the dilemma of depending on an external solution. Faced with this problem, you can either: (a) update the code to use the new Windows Search interface, or (b) leave the application running on its own server (the old server) that uses the Indexing Service. Depending on the size of the company, option b isn’t always feasible.

Unfortunately, the code to search the Indexing Service is hard-wired into the screen. To change it, you need to open the code for the page itself. When updating the mechanism you use to index documents, you now have to touch the code that also searches metadata and displays the results to the user. By this point in the book, you should recognize the inherent risks in such an operation.

Let’s look at another example.

Your search page uses a third-party grid control to display the results. Eventually, a user discovers a bug that occurs when using Google Chrome to view the results. A quick search of the vendor’s website reveals that the latest version of the grid is compatible with all the latest browsers, and you happily go about upgrading.

During the upgrade, you quickly discover that there was a breaking change in the new version that affects your application. An interface changed on one of the methods you use and you can no longer call it in the same way. And as luck would have it, you call it everywhere in your UI, as you can see in figure 12.1.

Figure 12.1. Example of four screens calling the same third-party component. What happens when something changes in the third-party component?

Now you have a no-win decision to make. Here are your choices:

  • Stick with the old grid and postpone support for the new browser.
  • Stick with the old grid and implement HTML and JavaScript hacks to accommodate the inconsistencies in it.
  • Upgrade to the new version and fix every instance of the breaking change.

None of these choices are palatable. The first is more of a marketing faux pas than anything else. The second and third both involve a considerable amount of development and, worse, it will be error-prone development.

These are but two examples of the ways external systems can have an effect on your code. It’s not hard to come up with others:

  • A web service changes the format of the data it returns or changes the signature of a method you call.
  • A web service becomes unstable.
  • A legacy component will no longer be supported and you need to move to a different component.
  • The interface for an external system is cumbersome to use.
  • You wish to expand the functionality of your application but the external system doesn’t support some of the features you need.

We’ve painted a pretty bleak picture. Let’s see what you can do to make it a little brighter. First, we’ll describe the different ways you can integrate with a system.

12.2. Types of external system dependencies

Before looking at how to mitigate your risk, let’s differentiate between two types of external dependencies:

  • Compile-time dependencies
  • Runtime dependencies

12.2.1. Compile-time dependencies

A compile-time dependency is one in which the dependency on the external system is resolved when the application is compiled or built. Essentially, these are the third-party libraries and controls you use to build and run your application.

These systems are usually less risky than runtime dependencies because you need to make a conscious decision to change the dependency; unless you decide to physically remove one version of a library and insert a newer one, your code will not break.


Note

Way back in chapter 2, we recommended that you include all third-party libraries and tools used by the application in your version control system. In addition to reducing the friction of having to track down all the pieces of your application, this approach cuts down on the risk of one developer inadvertently installing the wrong version of a library or control on his or her machine.


Sooner or later, you may need to actually do an upgrade to a newer version of a library or component. This upgrade can often be a small nightmare if there are calls made to it all over the code. Later, in section 12.3, we’ll look at ways to minimize the risk of such an upgrade.

12.2.2. Runtime dependencies

By contrast, a runtime dependency is one where the dependency isn’t resolved until the application is running. Furthermore, it’s often not under your direct control.

A web service is a good example, whether it’s internal or external. If you’re using a supplier’s web service to retrieve purchase order data and the service goes offline, your application may fail (if you haven’t allowed for this contingency; more on this later). Unless it’s a planned outage, you probably won’t even have any warning about your application failure.

Runtime dependencies represent a more volatile risk because you don’t have the same control over them as you do with compile-time dependencies. If something changes in a compile-time system, you have the option of simply ignoring it and sticking with the version currently being used. Not so with runtime dependencies. If they change, you may have to change along with them.


Dynamically loaded libraries

You may be wondering about libraries that are dynamically loaded at runtime. Say you have a process that, when the application is built, retrieves the latest version of certain assemblies and puts them into a plug-in directory.

We consider these compile-time dependencies because you still have control over them at the time the application is built. Although they may not be compiled in with the application, if they’re part of your build process, that means you can write tests around them to ensure they act as expected.


The good news is, regardless of the type of integration you have, compile-time or runtime, the methods are the same for dealing with them effectively. But it’s good to identify what type of integration you have for each external system as the type of integration will have a bearing on decisions you make concerning your system.

Next, we’ll talk about what you can do to manage your external systems.

12.3. Handling external systems

The insurance industry is predicated on a single concept: managing risk. You pay an insurance company a certain amount of money so that they can assume the risk of some catastrophic event that you don’t have control over, such as a fire in your house.

In your software, external systems are a risk. You don’t have the same control over them that you do over your own code. You need to “buy insurance” to reduce the risk of a loss if something should happen to those systems that are beyond your control.

By this point in the book, you may have figured out what that insurance is, so we’ll give away the ending: apply OO principles (chapter 7), layer your application appropriately (chapter 8), and isolate the dependencies you have on the system (chapter 9).

Indeed, you’ve already seen examples of external systems. In chapter 9, we explored third-party inversion of control containers and, in chapter 11, object-relational mappers. These are two examples of external systems over which you have limited control. You’re at the mercy of the interfaces these libraries provide. Although it’s true that a number of frameworks are available for each and you have a choice of which one you choose, you must almost always make compromises.

Chapter 8 introduced the concept of an anticorruption layer. To review, an anticorruption layer is one that you add between two other layers to protect the top layer from changes to the bottom one.

Let’s take another look at one of our pain points from section 12.1. Recall the third-party component used throughout the UI as depicted in figure 12.1 earlier.

As we mentioned in that section, this arrangement can lead to problems when you want to upgrade. Here’s where an anticorruption layer can be used for protection, as shown in figure 12.2.

Figure 12.2. The same drawing with an anticorruption layer added. Now the screens call out to your own service, which passes the calls on to the third-party component. If the component changes, only the anticorruption layer needs to be changed.

Here, we’ve added a layer of code between the screens and the third-party component. The screens no longer interact with the component directly; the component deals with a class of your own design. Behind the scenes, this class will still work with the third-party component, but from the screens’ perspective, a screen no longer has any knowledge of that third-party component.


Note

This discussion on anticorruption layers is a long-winded way of saying “abstraction” from chapter 7.



Tales from the trenches: The “internal” external dependency

When looking at integrating with other systems during one project, we encountered a dependency that was, shall we say, less than wonderful to integrate with. The person who created the public-facing API for the dependency had done a poor job. It wasn’t even a truly external dependency. It had been created, and was being maintained, by our client’s internal programming department.

During discussions on how to deal with the difficulties that the API presented, we decided to draw a clear line in the sand. If the code wasn’t under our full control, we’d treat it as if it had been purchased. This was an important distinction for us to make: if we weren’t involved in the development or the release planning for the dependency’s codebase, then that codebase presented a potential point of change and thus risk.

Once we made this decision, we wrote an anticorruption layer that wrapped the “external” dependency. Instead of our application’s code directly interacting with the public, and potentially changing, API of the dependency, it now worked directly with a single piece of code that was completely in our control. No longer did changes to the “external” dependency’s API affect the code deep in our application. Those changes needed to be dealt with only in one location.

On top of that, the application’s code was able to call this dependency in a way that appeared to seamlessly integrate into the fluency and standards that had been set for our codebase. There was still one location where we had code that did make that direct call to the “external” dependency. The nice thing was that all that nasty code was centralized.

Sure enough, a few months after developing this portion of the application, we were advised that changes to the “external” dependency were coming. When that happened, the impact to our codebase was minimal, the stress of retesting was focused, and the changes were quickly implemented.

Determining whether a component is external isn’t always easy to do, especially if the codebase is controlled by the same department, or even part of the development team you’re working with. In the end, treating an internal library as an external one may be more work, but it can save you from some large refactorings if changes do occur.


An anticorruption layer provides some important benefits:

  • Protection from changes to the interface of the third party
  • Testing of the screens’ interaction with the third-party component without having to explicitly create that component
  • Simplification or alteration the interface to suit our needs
  • Swapping out of different implementations

At this point, it’d be useful to see an example of an anticorruption layer at work.

12.4. Example 1: A sample refactoring

Let’s say you have a Windows Forms application that reads a web service. The service is provided by Twilight Stars from previous chapters, and it provides information on upcoming tours and locations for various artists. Our application (see figure 12.3) is a ticket-purchasing app that reads this data so the business can sell tickets to appropriate shows.

Figure 12.3. Ticket Chooser, a sample application that reads a web service

The tabs list the data in different ways depending on how the user wishes to search for the data. Each tab makes use of a custom control that manipulates the data in various ways. Listing 12.1 shows the OnLoad event for one such control.

Listing 12.1. OnLoad event for control that calls a web service
protected override void OnLoad( EventArgs e )
{
base.OnLoad( e );
TwilightStarsSoapClient client = new TwilightStarsSoapClient( );
DataSet tours = client.GetTours( );
DataTable artistTable = tours.Tables[0];

foreach ( DataRow artistRow in artistTable.Rows )
{
// Add artist row to control
// Then add nested tour and locations
}
}

As you can see in listing 12.1, we’re expecting to get a DataSet back from Twilight-StarsSoapClient, which is the web service.

Now let’s see the code for another control in listing 12.2.

Listing 12.2. OnLoad event for a different control
protected override void OnLoad( EventArgs e )
{
base.OnLoad( e );
TwilightStarsSoapClient client = new TwilightStarsSoapClient( );
DataSet ds = client.GetTours( );
DataTable artistTable = ds.Tables[0];

// Iterate through the dataset and add a row for each tour, then
// nested rows for each date and location
// ...
}

There isn’t much difference between these methods except omission of the nitty-gritty details where most of the work is done. We won’t show the corresponding code from the last tab, but suffice it to say, it calls the same web service.

So there are three controls. All use the same web service and retrieve the same data from it.

Let’s assume that Twilight Stars updates the web service. They have some new developers on board that recognize the blatant evil of DataSets (see the sidebar “Don’t shy away from objects” in chapter 10) and have decided carte blanche to change the web service to return serializable objects instead.

As a consumer of the web service, this decision is a problem for you. Now you need to open each of these controls and modify them. If you’re lucky, Twilight Stars notified you of the impending change before it happened to give you some lead time to make changes. Even then, you need to make the change at the same time Twilight Stars does to minimize downtime, assuming they don’t provide an overlap period where both interfaces are maintained.


What are they thinking?

An argument could be made that Twilight Stars shouldn’t change the public interface to their web service. Regardless of the new developers’ desire to use objects instead of DataSets, there are clients’ needs to consider. Changing a public interface is not to be taken lightly.

That’s an excellent—and moot—argument. The fact is, it’s Twilight Stars’ web service and they can do whatever they want to it. You can work with them to try to convince them otherwise, or you can look for another vendor. Maybe you’ll be successful; maybe you won’t.

The reality is that when you rely on another company’s web service, you’re ultimately at their mercy. It’s the nature of third-party dependencies: you don’t have control over them.


What could you have done to minimize the impact of such a change? That’s easy. The answer is to go back and reread chapter 8.

We’ll save you some time flipping back. To isolate your exposure to a third-party dependency, you need to isolate it behind an anticorruption layer. In this case, that means creating a layer above it that you control.

Let’s start with the quick and dirty solution to this problem. In it, you simply wrap the web service in another class. Let’s call that class TwilightStarsProxy for reasons we’ll explain later. Its implementation is simple, as you can see in listing 12.3.

Listing 12.3. Implementation of TwilightStarsProxy
public class TwilightStarsProxy
{
public DataSet GetTours( )
{
var client = new TwilightStarsSoapClient( );
return client.GetTours( );
}

public DataSet GetTourDates( int tourId )
{
var client = new TwilightStarsSoapClient( );
return client.GetTourDates( tourId );
}
}

Pretty simple. Your TwilightStarsProxy class exposes the same methods that the web service does. Each method creates a reference to the web service and returns the contents of the corresponding method.

The changes to the corresponding code in the controls is equally trivial. We’ll show the same excerpt from listing 12.1, except using our new proxy class (listing 12.4).

Listing 12.4. Updated OnLoad event for our control

The difference between listings 12.4 and listing 12.1 is that you don’t create a web service reference . Instead, you create an instance of your proxy class.

Note that we’re being a little naive in our approach. It may not be a good idea to create a reference to the web service in the proxy in every method. By the look of our OnLoad method, it’s possible that it’s being called more than once. So it might be useful to create a reference to the web service in the proxy’s constructor. Then again, that decision may have ramifications as well. Just bear in mind that there are various options available and the one you choose should match your particular scenario.


Note

In listing 12.3 we’ve implemented an example of the Proxy pattern. Its goal is to provide an interface for some other object so that you can control how it’s used. This pattern is ideally suited for creating an anticorruption layer over third-party dependencies in the way described here.


With the proxy class in place, you can guard against the changes proposed by Twilight Stars. If they change their web service to return objects rather than DataSets, you’re no longer forced to update all your controls to use their object structure. You can update the proxy class so that it retrieves the new objects from the web service and converts them back into the DataSet structure your controls are expecting.

Adding a layer that ties us to DataSets may seem like a dubious benefit given the swipes we’ve taken at DataSets. But because we’re working with a brownfield application, there could be dozens of places where you make a call to this web service. It may not be practical to change each and every one of them, even assuming you’re able to write tests to verify that it’s done correctly. By isolating the calls to the web service, you minimize the impact of future changes to the external web service. Although we’re very much in favor of eventually moving away from DataSets to an object graph that’s easier to maintain, the nature of brownfield applications is such that you may need to deal with them in the short term.

Your anticorruption layer can also play a role in the conversion from DataSets to objects when you do move in that direction. Because you control access to the web service through a proxy class, you can return whatever you want from that proxy. For now, you’re returning DataSets because that’s what was originally returned from the service and the original development team decided not to change it. But if you hide the web service behind a proxy, you could (and in many cases, should) convert what’s returned from the web service into something you’re more familiar with.

Let’s assume you’ve created the TwilightStarsProxy class as outlined in listing 12.3 earlier. You’re returning DataSets from it because that’s what was originally returned from the service. Later, you decide you don’t want to deal with DataSets in your controls. You’d rather work with, say, a Tour object that references an Artist and a list of Tour-Dates, as shown in figure 12.4.

Figure 12.4. A class diagram of a possible structure you’d like to use instead of DataSets

With a proxy class in place, you can take the DataSet returned from the web service and convert it into such a structure. You also still have the benefit of minimizing the risk of changes to the web service. If Twilight Stars decides they want to return objects instead of a DataSet, you can update your proxy class to convert their objects into yours, just like you would if you wanted to continue using DataSets.

Furthermore, let’s revisit the assumption that dozens of controls are accessing the web service. Say you have a proxy class that returns the data from the web service as a DataSet and that each control is referencing the web service through the proxy. Later, you decide you’d like to use objects instead.


The dangers in class naming

The change we describe here, converting a DataSet into an object, is a good example of why it’s a bad practice to name classes based on the pattern used. The goal of the Proxy pattern is to control how an object is accessed. But now we’re also altering the interface for this web service, which is an example of the Adapter pattern. As we mentioned in chapter 7, though it’s useful to recognize patterns and talk about them, it’s usually best to avoid hard-wiring your classes to them if you can help it.

As it is, it’s best not to get too hung up on names. Although you get bragging rights for recognizing when a proxy turns into an adapter, in reality it’s not going to help you finish your work any faster.


You can make this change incrementally: you can introduce a new method on the proxy class that returns a list of Tour objects rather than a DataSet. In this manner, you don’t need to update the controls all at once. You can visit each one at your leisure and have them call the new object-returning method. Once they’ve all been upgraded, you can remove the method that returns the data as a DataSet.

If you decide to go this route, try to avoid an inevitable trap that occurs: you start out with good intentions of migrating from one style to another but along the way you get sidetracked. As the months go by, you don’t get around to converting all the controls and you’re left with a hybrid class where developers aren’t sure which method to call (though you can mitigate this confusion through the use of the [Obsolete] attribute).

Let’s take a bit of a diversion from external dependencies and see what else you can do to improve this sample application. In the next section, you’ll apply some other techniques from previous chapters to the code. While it doesn’t have to do explicitly with external dependencies, think of it as an extended sidebar.

12.4.1. Extending the refactoring—a recap

We’re going to step out of the chapter for a little while. So far in the book, we’ve talked in fragments about how to incrementally alter bits and pieces of code to make it better. We’ve run through the UI and the data access layer, discussed layering and dependency injection, and explored some OO principles. Let’s revisit our example briefly and see what else can be done to make it better, not just from a third-party dependency perspective but as a whole.

This section isn’t meant to be a full-fledged practical application of the book’s principles but we feel it’s worth at least talking about how you might go about applying some of them in practice. The sample code provided with this chapter shows some of the fruits of our labors.

Recall earlier that we referred to our initial change to a proxy class as a quick and dirty solution. We were able to quickly isolate the call to the web service with minimal effort, partially because of the simplistic example. So let’s go a bit further into it based on what little we know about the code so far.

In our previous examples, you could safely assume that the code in the controls wasn’t being tested. In listing 12.1, the web service is directly referenced in the OnLoad event of the control itself. This code doesn’t lend itself to a decoupled and testable design. There’s a good chance this code isn’t even being tested in an integration test, unless the framework is loading up the controls.

Your first step is to create a safety net. Doing so means getting the code in a state where it can be tested. This task will be difficult without changing the code, but if you apply techniques we discussed in previous chapters, you can try to layer the application gently and move code with relative confidence.

First, you need to get as much code as you can out of each control. As you’ll recall from chapter 10, you can refactor this screen to a Model-View-Presenter implementation. Because you’re working with DataSets, a Supervising Controller makes sense. That way, you don’t need to change as much code because you can make use of most of the existing data-binding code.

You can see that the data is retrieved from the Twilight Stars web service. When you migrate your code to a presenter, you should also inject a proxy class into it using the techniques of chapter 9 as well as the anticorruption layer discussed here and in chapter 8.

When you’ve finished, each control should have a presenter that looks something like listing 12.5.

Listing 12.5. Skeleton presenter for our controls

Notice the use of poor man’s dependency injection to allow the creation of a presenter with only a view. The view can then create this presenter and pass itself to it without needing to know where the presenter gets its data. See listing 12.6 for that implementation.

Listing 12.6. Skeleton view for control

Using dependency injection means that the view doesn’t need to know all the dependencies the presenter has when it creates the presenter .

The sample code for this chapter shows this change as well as other refactorings and corresponding tests. We’ve also gone a little further and applied techniques from part 1 of the book by using a more efficient folder structure and adding an automated build.

This brief section was a bit of a sidebar. Although we didn’t go too in-depth, it does show some of the thought process involved when you tackle a brownfield application. Real-life examples will be much less cut-and-dried but the same basic principles apply: break it down into manageable chunks and work iteratively and steadily toward your end goal.

Let’s get back to the topic at hand. We have a couple more examples to work through, but first let’s talk about some limitations of anticorruption layers and discuss how far you should take the practice.

12.4.2. Limits of isolation

Throughout the chapter, we’ve claimed that you can minimize the risk of changes to external systems through the use of an anticorruption layer. The use of the word minimize instead of eliminate is intentional.

You can’t foresee all changes a third-party component could make. It’s possible that the component changes in such a fundamental way that you can’t alter its interface to match what your code is expecting. Perhaps the company that made the component has gone out of business and a suitable replacement doesn’t exist. Or may the web service no longer returns all the data you were previously expecting.

These examples are usually exceptional circumstances. They’re by no means an argument against anticorruption layers. If someone is trying to make the argument that adding a layer over a WCF service is a waste of time because the company could go bankrupt anyway, they are being shortsighted.

An anticorruption layer can save a tremendous amount of work if it’s well designed. If you find you need to update the public interface of your anticorruption layer every time the object it’s hiding changes, that could be a sign that you aren’t hiding the dependency but merely sugarcoating it.

That said, the question will inevitably come up: how far do you take your anticorruption layer? We’ll tackle that question next.

12.4.3. How far do you go in minimizing your risk?

Ah, the age-old question when abstracting a third-party interface: which components do you abstract? How far do you take the abstraction? When do you apply abstraction and when do you leave the code as is?

These aren’t easy questions; the answers will vary from application to application. You need to be able to recognize when you’re coupling yourself to a third-party implementation and to assess when it’s appropriate to create an anticorruption layer around it.

Furthermore, even after you’ve decided to create the layer, you must decide how it should look. If you aren’t careful, you could end up with an interface that’s no better than the third-party component at protecting you from changes.

Obviously, it doesn’t make sense to abstract every aspect of your application. Taking this idea to the extreme, you’d create an interface for even the built-in form controls, like TextBox and Label.

The reason you don’t, of course, is because these controls are stable. They don’t change often, and even when they do, the effort involved to update your application would likely not be as much as it would be to maintain a separate layer over top of the controls.

When considering factors in the decision to abstract, you’ll return to the concept of risk. For a given external system, what’s the risk involved if you decide not to create an anticorruption layer over it?


Assessing the risk

When deciding whether or not to abstract a system, consider the risk involved. Here are some questions that can help guide you:

  • How stable is the system? Does it change often? Is it reliable?
  • What would be the effort involved if it did change? Would you need to examine several dozen classes or is the integration isolated to only a small area?
  • Is it a runtime system or a compile-time system? If it changes, will you be forced to update the application?

More often than not, a gut feeling can be accurate, provided you consider all the risks and discuss them with your team. But if there’s any dissension, it’s best to err on the side of caution and create the anticorruption layer (or, depending on the dissenter, “reorganize” your team).

Consider one example we’ve already discussed: object-relational mappers (ORMs). As a rule of thumb, these are systems that you wouldn’t create anticorruption layers around. Here are some reasons why:

  • The ORM is a compile-time dependency. If a newer version of the library comes out, you can safely ignore it.
  • Most existing ORMs, both open source and commercial, are very stable.
  • The ORM should already be isolated in the data access layer. Any changes made to accommodate a newer version don’t need to be made application wide.

Deciding whether to add an anticorruption layer to a component isn’t always easy. In general, there are no major downsides to adding one except for the extra work involved in both creating and testing it. Adding an anticorruption layer reduces the coupling of the application, and in most cases, makes the application more maintainable.


Anticorruption layer for inversion of control containers

We’ve also mentioned IoC containers as another example of third-party containers. As of this writing, a new project has been created to act as an anticorruption layer over IoC containers. It’s called the Common Service Locator library and can be found at www.codeplex.com/CommonServiceLocator. The idea is that you can code to its interface rather than having a hard dependency on a particular product, which has traditionally been an argument against wider adoption of IoC containers in general.

It’s too early to say whether or not the library has long-term prospects. But in our opinion, if it encourages decoupled code and the use of IoC containers, whether commercial, open source, or hand-rolled, it gets our stamp of approval.


For brownfield applications, it usually doesn’t make sense to add another layer just for the sake of it. As we’ve mentioned throughout the book, refactoring should be done on an as-needed basis. If you’re using a third-party component successfully and don’t have an anticorruption layer around it, there probably isn’t any point adding one as soon as you finish reading this chapter.

Instead, wait until the component needs to be upgraded before adding a layer over it. That’s an ideal time because you’re already making changes to the code, especially if the new version has an updated interface. As we recommended in chapter 4, you should be writing tests for any code you add or modify. So if you’re updating code to work with a new version of the component, you can modify your code to work with an interface of your own design, thus making it more testable and reducing your exposure for future upgrades.

On the other hand, if you are experiencing pain with the current version of the component, there’s no need to wait for an upgrade to add an anticorruption layer. Working from the same chapter 4 recommendation, as you address bugs or add features that relate to the component, you can add the layer incrementally. Using techniques from chapter 9, you can inject the anticorruption layer into your code to reduce the coupling now. But again, only do this if you’re feeling pain.

This example is just one way you can add an anticorruption layer to your application to minimize the risk of an external dependency. There are a couple more scenarios we’d like to walk through for third-party dependencies. We’ll start with the same example as before but this time from the perspective of the web service.

12.5. Example 2: Refactoring a web service

In this example, we return to the web service of the previous section. This example will be much simpler, but it’s still important.

During this refactoring, we’ll pretend we’re Twilight Stars, the company that runs the web service. We’ll assume that the service has already been modified to return objects rather than a DataSet, as shown in listing 12.7.

Listing 12.7. Initial web service returning an array of objects
[WebMethod]
public Artist[] GetArtists()
{
var ds = TwilightDB.GetData("twi_get_artists_and_tours", "Artists");
var artists = new List<Artist>();
foreach (DataRow row in ds.Tables[0].Rows)
{
var artist = new Artist();
artist.Id = (int)row["ArtistId"];
artist.Name = row["ArtistName"].ToString();
artists.Add(artist);
}
return artists.ToArray();
}

[WebMethod]
public Tour[] GetTours()
{
// Code to create a list of Tour objects
}

We’ve omitted the code for the GetTours method, but it’s the same idea as GetArtist. Its full implementation is included with the sample code accompanying this chapter.

Twilight Stars has some clients that would like to use a WCF service rather than an Active Server Methods (ASMX) web service. Perhaps they’re merging with another company and wish to offer a unified service model. Or maybe they want the option of supporting the queuing capabilities of Microsoft Message Queuing (MSMQ). We could make up any reason you like, but the end goal is that Twilight Stars wants to introduce a WCF service to replace the ASMX.

They can’t outright replace the ASMX; for a time, they’ll need a phased approach to give clients time to migrate over to the new service. So for a little while, Twilight Stars will need both an ASMX service and a WCF service offering the same functionality.

The WCF should look like the ASMX service, as shown in listing 12.8.

Listing 12.8. Signatures for WCF service
[ServiceContract]
public interface ITwilightStarsService
{
[OperationContract]
Artist[] GetArtists( );

[OperationContract]
Tour[] GetTours( );
}

For the implementation of this interface, you won’t want to duplicate the existing code. And as you’ve probably guessed, the way to do this is to encapsulate the common functionality into a separate class (see listing 12.9).

Listing 12.9. Encapsulated functionality
public class WebServiceTranslator
{
public Artist[] GetArtists( )
{
var ds = TwilightDB.GetData("twi_get_artists_and_tours",
"Artists");
var artists = new List<Artist>();
foreach (DataRow row in ds.Tables[0].Rows)
{
var artist = new Artist();
artist.Id = (int)row["ArtistId"];
artist.Name = row["ArtistName"].ToString();
artists.Add(artist);
}
return artists.ToArray();
}

public Tour[] GetTours( )
{
// GetTours implementation
}
}

The changes to the ASMX and WCF services are shown in listing 12.10.

Listing 12.10. WCF implementation and updated ASMX
[WebService(Namespace = "http://tempuri.org/")]
public class TwilightStars : WebService
{

[WebMethod]

public Artist[] GetArtists()
{
return new WebServiceTranslator( ).GetArtists( );
}

[WebMethod]
public Tour[] GetTours( )
{
return new WebServiceTranslator( ).GetTours( );
}
}

public class TwilightStarsService : ITwilightStarsService
{
public Artist[] GetArtists()
{
return new WebServiceTranslator().GetArtists();
}

public Tour[] GetTours()
{
return new WebServiceTranslator().GetTours();
}

}

In listing 12.10, the third-party component you want to abstract is the transport mechanism. You have a dependency on how the objects are being publicly exposed. Previously, it was through ASMX web services; in the new example, it is through ASMX web services and WCF.

This example is almost right out of the Object Oriented 101 Handbook under encapsulation. But it’s important to always be on the lookout for opportunities to avoid duplication. As it is, web services are almost as notorious as web pages for having far too many responsibilities. Even without the requirement to support a WCF service, the original implementation of our ASMX service in listing 12.7 wasn’t very maintainable. Web services, in general, should be kept lean, with most of the work being done in other, more testable classes.

This example may seem straightforward but it demonstrates an important concept. When you’re dealing with a hard boundary, such as with a web service, it helps to keep the code in that boundary at a minimum while most of the work is delegated elsewhere.

Our last example is another common scenario: managing a third-party component.

12.6. Example 3: Wrapping a component

Let’s do a slightly different but still musically themed example. Say you have a simple search application (figure 12.5) that people use to enter a search term and the application tries to find corresponding music files in a specified folder.

Figure 12.5. This sample application provides a simple search function against a music library. It uses two different third-party libraries to extract the metadata from each file.

The user selects a folder and loads their metadata (Title, Artist, Album, etc.) into the grid. As the user types in the Search Term text box, the window filters the results in the grid.

Fortunately, there are third-party libraries that handle reading metadata from music files. Unfortunately, the previous team chose two different ones: one to search MP3 files and another to search WMA files. Of course, they don’t do it in the same way.

Listing 12.11 shows how the MP3 files are loaded; listing 12.12 shows the code for loading the WMA files.

Listing 12.11. Loading MP3s using a third-party library
private List<Song> GetMp3s( )
{
var filePath = textBoxFolder.Text;
var files = Directory.GetFiles( filePath, "*.mp3",
SearchOption.AllDirectories );
var songs = new List<Song>( );

foreach ( var file in files )
{
var id3 = new UltraID3( );
id3.Read( file );
var song = new Song
{
Title = id3.Title,
Artist = id3.Artist,
Album = id3.Album,
Genre = id3.Genre,
Filename = Path.GetFileName( file )
};
songs.Add( song );
}
return songs;
}

Listing 12.12. Loading WMAs using a third-party library

Notice in listing 12.12 that the library allows us to retrieve the files only from the root directory . That’s not entirely true. There’s another method, RetrieveRecursiveDirectoryInfo, that retrieves the WMA files from subfolders. The problem is that there’s no single method that retrieves all WMA files from both the root folder and its subfolders. So you need to abstract some of this code into a separate method and call it twice, once for the root and once for the subfolders (this has been done in the sample code accompanying this chapter).

The net result is that the buttonLoad_Click method looks like listing 12.13.

Listing 12.13. Click event for the Load button
private void buttonLoad_Click( object sender, EventArgs e )
{
var songs = GetWmasFromFolder( );
songs.AddRange( GetWmasFromSubfolders( ) );
songs.AddRange( GetMp3s( ) );
dataGridViewSearchResults.DataSource = songs;
}

Listing 12.13 may not be the most coupled code we’ve seen, but it’s certainly a lot more knowledgeable than it needs to be. You can see how we’ve implemented two separate methods to load WMAs, one from the root folder and another from its subfolders.

There’s the usual laundry list of problems with this code; here are two issues:

  • It’s highly coupled to the specific third-party libraries that load the songs.
  • It contains business logic directly in the form, a clear violation of the single responsibility principle.

Fortunately, cleaning this code up is straightforward. You’ve already taken a decent first step by encapsulating the third-party libraries in separate methods. That step will make it easier to move the necessary code out of the form.

One of the major problems with this code, other than coupling to the components within the form, is that the logic to load the songs is more complex than it needs to be. Because of the limitations of each library, the form has to first load the WMAs from the root folder, then load them from the subfolders, and finally load the MP3s.

We need to hide this complexity. Ideally, we’d like the buttonLoad_Click method to say, “Load all the songs from this folder” without having to differentiate the type of file or having to deal with subfolders. In this way, the buttonLoad_Click method’s code has less knowledge about the process required to get the files and thus is less brittle and more resilient to change. Listing 12.14 shows our ideal buttonLoad_Click method.

Listing 12.14. Click event we want to have
private void buttonLoad_Click( object sender, EventArgs e )
{
var folder = textBoxFolder.Text;
var songs = _songRepository.GetAllFrom( folder );
dataGridViewSearchResults.DataSource = songs;
}

The code in listing 12.14 is much cleaner. The form makes use of a new object, _songRepository, to get the list of songs to display. All the complexity is hidden behind a façade for us. Listing 12.15 shows the new interface for this object. We think you’ll agree that this interface, from the perspective of the form, is much nicer to deal with.

Listing 12.15. Interface for the SongRepository object
public interface ISongRepository
{
List<Song> GetAllFrom( string folder );
}

The implementation of the SongRepository object will be familiar to you: it’s the same code cut and pasted from the original form. You can take a look at the sample application to see it in its entirety, but we’ll show the code for the GetAllFrom method in listing 12.16.

Listing 12.16. GetAllFrom method on the SongRepository implementation
public List<Song> GetAllFrom( string folder )
{
var songs = GetWmasFromFolder( folder );
songs.AddRange( GetWmasFromSubfolders( folder ) );
songs.AddRange( GetMp3s( folder ) );
return songs;
}

The code in listing 12.16 is similar to that shown in listing 12.13. But the difference is that it’s encapsulated in a separate class. We can now safely upgrade either third-party component, swap in a different one, or even add one that reads a different type of music file (for example, CDA or AAC files).

You may have noticed a common theme in all three examples. In each of them, we solved our problem by adding another layer between our code and the third-party component. This strategy was deliberate; it’s almost always a surefire way to make your brownfield application more maintainable. Layering your application is an excellent way to reduce your exposure to “risky” components and keeping it decoupled and testable.

Let’s expand on this idea with another “extended sidebar” on adding a layer around the Microsoft Office API.

12.6.1. Made for wrapping: Microsoft Office integration

Although our previous sample focused on a third-party .NET library, where the wrapping technique particularly shines is in wrapping Component Object Model (COM) or COM+ objects. A fantastic example of its power is in managing Microsoft Office integration.

The history of Office development is steeped in Visual Basic for Applications (VBA) and COM. VBA is notorious for being rather loose with its programming conventions. A good many of the methods on Office objects have optional parameters, something VBA can handle with ease. C#, on the other hand, is not so lucky. All parameters must be provided to methods in C# (at least as of this writing with .NET 3.5).

Add to this the long and checkered history of Office development and we are left with methods that can take over a dozen optional parameters.

Consider the Open method on the Excel.Workbooks object shown in listing 12.17.

Listing 12.17. Open method on the Excel.Workbooks object
Function Open( _
Filename As String, _
UpdateLinks As Object, _
ReadOnly As Object, _
Format As Object, _
Password As Object, _
WriteResPassword As Object, _
IgnoreReadOnlyRecommended As Object, _
Origin As Object, _
Delimiter As Object, _
Editable As Object, _
Notify As Object, _
Converter As Object, _
AddToMru As Object, _
Local As Object, _
CorruptLoad As Object _
) As Workbook

Although this code is easy enough to handle in VBA, calling it in C# requires something like this:

object f = false;
object t = true;
object o = System.Reflection.Missing.Value;
Excel.Workbook book = m_application.Workbooks
.Open( spreadsheet, f, f, o, o, o, t, o, o, t, f, o, f, o, o );

In short, listing 12.17 is just screaming out to be wrapped, even after including variables for false, true, and System.Reflection.Missing.Value to make the calling code more readable. And we haven’t even mentioned the joys of dealing with earlier versions of Office, where the signatures are slightly different.

This scenario is where a layer between your code and a third-party component becomes less an academic exercise and more of a way to maintain your sanity. You can add a class, say ExcelFacade, that can expose only the methods you want and with the parameters you need.

For example, if you always open Word documents the same way, you could define a method, OpenDocument, that takes a single parameter, DocumentPath. All the other parameters are hidden behind the class.


Intent-revealing naming

We can’t understate the usefulness of what may seem at first glance to be a simple concept. As you can see in listing 12.17, the Open method is nearly indecipherable. Why are we passing false in the second, third, eleventh, and thirteenth parameters? Someone who isn’t familiar with the Open method (and that includes us even a week after we’ve written this code) will be hard pressed to determine the best way to call it. Some parameters seem to contradict others and require detailed analysis of the documentation. For example, three of the parameters are ReadOnly, IgnoreReadOnlyRecommended, and Editable.

Intent-revealing naming is a concept whereby you name your methods based on the goals of the caller. Our Open method violates this practice because of all the optional parameters and the need to provide Missing.Value for the majority of them. Yes, it’s used to open a document with all the permutations of parameters. It would be more useful to have separate methods for more common scenarios. By creating wrappers around the call, you can provide a more meaningful method name more aligned with your purpose.


Office products are also quintessential examples of how to deal with upgrades. The ubiquitous nature of Microsoft Office can be useful when you’re developing applications that integrate with them. But that ubiquitous quality comes with a price: what version of Office is installed? And just as important, what version of Office could they possibly install in the future?

Word 97 doesn’t have exactly the same methods in its object model as Word 2007. If the Open method takes a new parameter in a newer version of Office, it’s much better for your mental state if you’ve wrapped the Word COM object in its own layer so that you need to change it just in the one place.

That’s the last of our examples. Between the three of them, you should sense a common theme when dealing with third-party dependencies: to create a buffer between you and them. We’ll close this section with a brief discussion of internal dependencies that you have no control over.

12.6.2. The dependencies don’t have to be third-party

Although we’ve focused only on third-party dependencies, everything we’ve talked about applies equally well to internal dependencies.

Brownfield applications are often laced with some complicated dependency graphs. In one project we encountered, a data access assembly contained references to a service assembly. Then, through the magic of reflection, that service dependency loaded a separate instance of the data access assembly to access a different object. In short, it went to a lot of trouble to create a circular dependency.

Think of any class as a third-party component. Examine how you’re using that class and consider how you would like to use it. Is the interface complex and/or fragile? Does it require a lot of setup? Is it an internal web service? A common company web service? If so, you have the option to change it—it is your code, after all.

But you have another option. You could apply one of the patterns and techniques we demonstrated here to simplify things. It may not be the ideal solution, but the reality of brownfield applications is that you have to pick your battles. Sometimes it can be useful to wrap a complicated class in another layer of abstraction with the intent to clean up the underlying class at a later date.


Note

If wrapping ugly code rather than fixing it sounds like a cop-out, it is. The phrase “lipstick on a pig” comes to mind. When you’re faced with the decision to clean up a class or hide it behind an adapter, you should make a concerted effort to refactor it. Are you sure you don’t have time at least to add tests around it before popping it behind an interface? No, seriously. Are you very sure?


Before we close out this chapter, we thought it’d be useful to provide the names of some of the patterns we’ve discussed here and throughout the book.

12.7. Design patterns for dealing with third-party dependencies

We’ll finish up with a quick discussion of some patterns that can be useful when abstracting third-party dependencies. Some of them we’ve talked about already even if we haven’t assigned a name to them. We’ll explore them here so that you can match the concepts to names if they come up in conversation. The patterns are listed in figure 12.6.

Figure 12.6. Useful patterns for dealing with external dependencies. All deal with the same theme of hiding parts or all of the access to the dependency.

Each of these patterns is a variation on the same theme: they represent a layer of abstraction over another component or system of components. Where they differ is in their intent.


Patterns in brownfield applications

Although we try not to focus on the jargon, the underlying concepts behind each pattern name are useful for brownfield applications. It’s often easier to refactor to a pattern than to design one from scratch. Sometimes, you can’t see where another layer could be beneficial until you see the code in place and the way classes interact.

Also, many times an additional layer doesn’t become obvious until the need arises. You may not consider wrapping a TextBox, for example. But the first time the client asks for autocompletion behavior, or some other feature that isn’t included in a basic TextBox, it answers the question “What are the odds of this ever changing?” rather definitively. If it’s changing once, chances are it will change again. (Incidentally, to refer to our earlier comparison at the beginning of the chapter, insurance companies tend to follow the same principle when they hike your rates after a car accident.)


Our first pattern is the Adapter.

12.7.1. Adapter

The Adapter pattern should be familiar to you. We’ve talked about the Adapter previously, including in the first example in this chapter.

An adapter comes in handy if you’re using a component and want to use a different interface to access it. Perhaps the existing one doesn’t meet your needs. Maybe you need it to implement a specific interface to make it work with yet another third-party component. With an adapter, you create a layer above the component you wish to modify and expose the methods and properties that you actually need to use.

Let’s review the first example of this chapter. The original web service returned a DataSet, which we were using happily. Then the company decided it wanted to return a list of domain objects.

Rather than rewrite all our code, we could add an adapter to the mix. We’d already created a proxy class to control how we access the web service, but we could also add in an adapter. This adapter would take the list of objects retrieved from the web service and convert it back into a DataSet until we’re ready to make the switch over to the list of domain objects.

Another pattern we’ve discussed is the Proxy.

12.7.2. Proxy

A proxy is simply a class that controls access to some resource. They’re commonly used for wrapping web services. In fact, when you connect to a web service, Visual Studio creates a proxy class for you, as shown in figure 12.7. This figure shows an example of a remote proxy, which is a variation on the Proxy. With a remote proxy, you interact with a local object, which handles the nitty-gritty of connecting to the remote resource (in this case, a web service).

Figure 12.7. When connecting to a web service, Visual Studio generates a proxy class to control access to it.

Proxies are valuable for hiding the manner in which you connect to a resource. By adding a proxy, you decouple a client from knowing exactly how it connects to a service. This approach allows you to test the calling code without having to configure an actual connection to the service.

Next up: the Façade.

12.7.3. Façade

The keyword for the Façade pattern is simplicity. You take a complex system, often involving several objects, and simplify it behind a single interface that’s easier to use in your client code. It allows you to stay focused on a class’s core functionality without getting mired in the complexity of a third-party interface.

The third example in this chapter, from section 12.6, used a façade to simplify the complexity of loading the music metadata. Before we added the interface from listing 12.15, our form had to perform some acrobatics to retrieve this information.

Once we moved that behind a façade, it became much easier for the form to manage. Even though the underlying code barely changed, we effected a useful change for our maintainability.

A façade is helpful when dealing with third-party components that have been in use for a long time. Typically, the companies behind them add more and more functionality until even the simplest task becomes an exercise in self-restraint. We mentioned Microsoft Office as one example. Third-party grid controls are another.

Adapter, Proxy, and Façade are the three patterns we’ve used directly in this chapter. In the next section, we’ll zip through a few others we’ve found useful in both brownfield and greenfield applications.

12.7.4. Other patterns

The previous three patterns are ones we’ve used or talked about explicitly. But there are others that are equally useful in managing third-party dependencies. This isn’t a patterns book—for that, we recommend Martin Fowler’s Patterns of Enterprise Application Architecture (Addison-Wesley Professional, 2002), Eric and Elisabeth Freeman’s Head First Design Patterns (O’Reilly Media, 2004), and, of course, the original Gang of Four’s Design Patterns (Addison-Wesley Professional, 1994). Here we’ll provide a brief description of other patterns you may find useful.

Decorator

A decorator is useful when you have a class that doesn’t quite do everything you need. With a decorator, you wrap the class and provide additional functionality on top of it. In a typical implementation, the client code must create the object being decorated. Thus, it doesn’t totally hide the object being decorated, so exercise caution when using it as a means to reduce exposure to third-party components.

Remote Façade

The Remote Façade, like its namesake, is used to simplify an interface. It differs in its focus. Whereas a façade is a general way of simplifying a complex process, a remote façade’s specific purpose is to provide a coarse-grained way of accessing a finegrained object.

Think of a remote façade as a bulk accessor. On a Customer object, instead of calling GetPhoneNumber, GetEmailAddress, GetFaxNumber, and GetMobileNumber, a remote façade would provide a method, GetContactInfo, that does all that for you. It collects other accessors into a single one to minimize the number of calls you need to make, which can be an issue if you’re making the calls over a network connection.

Data Transfer Object

We’ve mentioned data transfer objects (DTOs) before, in the context of binding screens to objects. They’re representations of one or more objects designed (often for a single purpose or screen) to reduce the number of calls to the underlying object(s) they represent. Additionally, DTOs are often used to transport data in over-the-wire scenarios (web services, for example) in place of more complex, harder-to-transport objects.

DTOs are similar to a façade in that they simplify one or more objects. But they’re often implemented as a “dumb” class, usually just with getter properties (setters are usually unnecessary as the values are set in the constructor arguments), for the purpose of binding a screen or some other object. A façade, on the other hand, is all about simplifying a complex process that often involves more than one class.

That should do it for design patterns. You may not have thought that so many could apply to such a simple concept, but each has its place. Hopefully, they got you thinking about how to handle some of the issues you’ve had with external dependencies on your brownfield project.

12.8. Summary

There’s a quote often attributed to either David Wheeler or Butler Lampson that says “Any problem in computer science can be solved with another layer of indirection.” This chapter is a testament to that quote. Third-party dependencies can expose you to considerable risk, and in this chapter we demonstrated what those risks are and how you can minimize them.

We started with our usual pain points, though you don’t have to get too far in your career to experience them firsthand. We followed with a distinction between compile-time and runtime dependencies before launching into the crux of the discussion: how can you reduce your exposure to third-party dependencies?

To demonstrate, we provided three examples, taking a bit of a departure with the first to discuss larger issues and tie things back to other topics in the book. In the examples, we worked with a web service on both sides as well as with two third-party compile-time dependencies.

Finally, we closed by applying some names to the patterns we used in the examples and with a reminder that these techniques apply equally well within your own code-base, even without third-party dependencies.

It’s been quite a journey and we’re just about to wrap it up. In our final chapter, we’ll take a step back from the code and the environment. We’ll return to the social and political concerns of chapter 1 and see what we can do to keep the momentum going.

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

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