Chapter 11: Service Layer Refactoring


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:

  • Find a recording by recordingId

  • Add a new review to an existing recording

  • Delete a review from an existing recording

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:

click to expand
Figure 11-1: Application packages

There are several packages in the application:

  • Data Access package This package is responsible for implementation of CRUD low-level operations for the underlying data in the data source. Data gateways ( RecordingGateway , LabelGateway , and so on) are abstractions populating this package.

    This package also has TransactionManager and its supporting classes ( Command and CommandExecutor ), defined here to make data gateways transaction-capable.

    This package also has implemented application-specific transaction management and higher-level business operations. The Catalog class is another core abstraction in this package and it consolidates the CRUD operations into higher-level business operations.

  • Data Model package This package defines a typed data set that establishes the data-level contract between the data source and the rest of the application. This contract is pretty low level and is tightly coupled to the database schema of the underlying data source. Although ADO.NET and the typed data set give a database-vendor independent views of the data, conceptually the two schemas ”the data set and the database ”are very tightly coupled and must be maintained together.

  • Service Interface package To reduce the dependency between the data source s schema and the rest of the application, we have defined a Recording Data Transfer Object (DTO) ”an XML representation of the data as viewed by the application. Recording DTO is the application-level representation of the data in the database, and this representation is captured by the RecordingDto class. This application-level contract hides some of the database- related specifics; for example, the database schema normalizations are not directly exposed on the Recording DTO; Artist, Label, Genre, and Reviewer are not represented as individual entities and are flattened to be simple string attributes on the corresponding objects ( Recording , Track , and Review ). We have created a RecordingAssembler class that is responsible for mapping between the two representations of the recording data.

    This package also has CatalogService and DatabaseCatalogService classes that implement the functionality of retrieving the recording from the database and mapping it to the RecordingDto using the RecordingAssembler .

    The Service Interface package hosts the CatalogServiceInterface class that exposes the application s operations as a Web service and adds mapping of the application exceptions into SoapFaults using the ExceptionMapper class.

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:

  • First, the package responsible for exposing the application s operations using the Web services; we keep the name as the Service Interface package

  • Second, the package that actually defines the application services in a way that is independent of which client uses it; we call it the Service Layer

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 :

  • RecordingDto generated from RecordingDto schema

  • RecordingAssembler

  • CatalogService

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

  • CatalogServiceFixture (this class was called DatabaseCatalogServiceFixture before we rolled up the CatalogService and DatabaseCatalogService into one class: CatalogService )

  • DatabaseUpdateReviewFixture

  • InMemoryRecordingBuilder

  • RecordingAssemblerFixture , ReviewAssemblerFixture , and TrackAssemblerFixture

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.

click to expand
Figure 11-2: Application architecture after ServiceLayer refactoring



Test-Driven Development in Microsoft .NET
Test-Driven Development in Microsoft .NET (Microsoft Professional)
ISBN: 0735619484
EAN: 2147483647
Year: 2004
Pages: 85

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net