Test-Driven Development in Microsoft .NET (Microsoft Professional)

In Chapter 10, Programmer Tests: Using Transactions, we added support for transactions to our application. It was a significant architectural change that we made after a substantial part of the application had been built. In this chapter, we will implement another architectural refactoring that has to do with reorganizing the packaging of the application.

The Problem

In Chapter 12, Implementing a Web Client, we will develop an ASP.NET Web client for the application. A typical enterprise application can have a variety of clients, and some of these clients provide interfaces for users to interact with the system; clients can also be in the form of other software systems consuming the services of the enterprise application. In both cases, the set of service operations provided by the enterprise application is conceptually shared among the clients . In the case of the application, we have a set of client-specific operations:

These operations are exposed to the single client of the application via the Web service. This client is another software system, or software systems that are likely to be hosted on a variety of technology platforms, which will be consuming the services of the application. This is why we chose to use Web services as the technology to expose the application s services.

Let s review the application architecture from the packaging perspective. Figure 11-1 shows what it looks like:

Figure 11-1: Application packages

There are several packages in the application:

The programmer tests are compiled into separate assemblies from the application code so that we can choose to deploy the code with or without the tests.

What s Wrong?

Let s take a closer look at the Service Interface package, which has classes that provide both a Web service-independent implementation of the application functionality and classes that are responsible for exposing and adapting this functionality via the Web service. The set of classes that are responsible for Web service “independent implementation of the application functionality includes CatalogService , DatabaseCatalogService , RecordingDto , and RecordingAssembler . This set of classes defines and implements a service-level contract between the application and its clients. We are adding a new client to the application, and this client needs to have access to this application-level contract. Unfortunately, however, this contract is packaged with the Web service into one assembly.

We have also been putting off some cleanup for the programmer tests. We still have StubCatalogService and StubCatalogServiceFixture classes in our service.interface.tests assembly. These two classes do not add to the test coverage, and they complicate the application code.

The Solution

We will start by removing the stub implementation of the CatalogService and the corresponding programmer test. This change is very simple, and we can simply remove the two classes: StubCatalogService and StubCatalogServiceFixture .

The first change allows us to simplify the implementation of the CatalogService class. Now that we have just one implementation of the data retrieval strategy, we don t need to have the implementation span two classes: CatalogService and DatabaseCatalogService . These two classes can be merged into one ( CatalogService ), and we can get rid of the abstract methods and keep only the implementation. The following code shows what the new version of the CatalogService looks like:

using System; using DataAccess; using DataModel; namespace ServiceInterface { public class CatalogService { public RecordingDto FindByRecordingId(long id) { RecordingDataSet dataSet = new RecordingDataSet(); RecordingDataSet.Recording recording = Catalog.FindByRecordingId(dataSet, id); if(recording == null) return null; return RecordingAssembler.WriteDto(recording); } public ReviewDto AddReview(string reviewerName, string content, int rating, long recordingId) { RecordingDataSet dataSet = new RecordingDataSet(); RecordingDataSet.Review review = Catalog.AddReview(dataSet, reviewerName, content, rating, recordingId); return RecordingAssembler.WriteReview(review); } public void DeleteReview(long reviewId) { Catalog.DeleteReview(reviewId); } } }

To do part of this refactoring, we had to change the users of the old CatalogService and DatabaseCatalogService classes.

The next step is to split the Service Interface package into two:

As part of this split, we need to restructure a few other packages. The mechanics of this refactoring are not complex. We will define a separate namespace, ServiceLayer , to host the classes of the Service Layer. We need to move the classes that define the application-level data contract and the classes that are responsible for the implementation of this contract into the ServiceLayer namespace. These classes are as follows :

We will also move the related programmer test fixtures into this new namespace. The following test classes will be moved to this namespace:

Modern refactoring tools support such refactoring, and they make it much easier to handle a task like this for much larger applications, in which thousands of classes might need to be moved. However, we do not have one of those tools, so we need to do this manually. When we get everything to compile again, we rerun all the tests, and they pass.

We are almost finished. We have one undesirable package dependency still present. The ExistingReviewMapper class from the ServiceInterface package needs to have access to the ExistingReviewException class defined in the DataAccess package. We want to break that dependency by moving the ExistingReviewException into a new package: ServerExceptions .

Figure 11-2 shows what our application architecture looks like after the extraction of the Service Layer.

Figure 11-2: Application architecture after ServiceLayer refactoring

Категории