Thursday 29 September 2011

Enterprise Software Architecture: How To Do It Part 5



This is part 5 in a series. For earlier and later posts in this series, please see here: Enterprise Software Architecture: How To Do It. For the accompanying code, click here.

The Application Layer


Click here to download the code from Codeplex, and be sure to download 'Code For Part 5'. You will see a new assembly added called Lucid.ESA.ClientDataSystem.Application.

The job of the application layer is to provide an entry point to the functionality of the system. It consists of completely stateless, self contained services, with each call fully encapsulating the functionality of a use case. Ideally, they should accept and return dumb Data Transfer Objects with no behaviour (no methods that can be called), rather than entities.

The Application Layer does not contain business logic, and is not concerned with the implementation of persistance. All it does is orchestrate the Domain and Data Layer. A typical application service will follow the pattern of 'get entities, call business logic on entities, save entities'. This is explained in the comments of the code. I would probably not normally include these comments: I am moving towards the school of thought that limits comments. If a class follows the Single Responsibility Principle, it should be clear what it does. A Repository class should not need a definition.

You may also notice a new assembly containing Integration Tests has been added.

Inversion Of Control


Rather than creating an instance of the repositories using the new keyword, the services use the ObjectFactory (part of StructureMap) to get an instance of the interface. This way, the executing assembly can decide what concrete class to return. If you look at the AssemblyInitializer class of the integration tests, you will see that it instructs StructureMap to return the concrete classes, where as in  the UnitTests/Application/ClientServiceTests, A RhinoMocks mock is injected instead. For more information on StructureMap and RhinoMocks, please follow the links.

Data Transfer Objects


You may notice that I map all my entities to Data Transfer Objects (DTOs). Many people think this is an unnecessary overhead, but I favour it for two reasons. Firstly, it removes all problems with serialising entities, cause by uninitialised NHibernate proxies. This becomes a problem when serialising for WCF, and can also be a problem data binding or serialising to ViewState in ASP.NET. These issues outweigh the overhead of mapping to DTOs. Secondly, it removes any posibility of calling any of the methods containing business logic from outside the application layer, ensuring that all business logic is encapsulated within service calls. Mapping to DTOs is simplified with AutoMapper.

Request/Response


The services communicate using a combination of the Request/Response pattern and the Special Case Pattern. The services contracts are designed to return a base response, which contains only a Status field. If it is a Create service, and the creation was successful, it returns an inherited CreateResponse object, which contains the new Id of the created entity. However, if the created entity failed validation, the inhertied Response is a ValidationErrorResponse, containing the validation messages. A Get service could return a NotFoundResponse if there was no entity matching the supplied Id, but that has not been implemented here.

The Request objects supplied for services that require many parameters (such as Create and Edit) are converted to the Parameter objects required for the factory methods of the entities. All primitive types are simply mapped using AutoMapper, and required entities are loaded into the Parameters based on the corresponding Ids in the Request. This follows the usual pattern of 'load what you need, perform business logic, and save'.

What Not To Do



public Response UpdateSite(SiteDto siteDto)
{
  ISiteRepository siteRepository =
    ObjectFactory.GetInstance();
  Mapper.Reset();
  Mapper.CreateMap<SiteDto, Site>();
  Site site = Mapper.Map<SiteDto, Site>(site);

  using (ITransactionProvider transactionProvider =
    ObjectFactory.GetInstance())
  {
    transactionProvider.BeginTransaction();
    siteRepository.SaveOrUpdate(site);
    transactionProvider.CommitTransaction();
  }

  return new CreateResponse(site.Id.Value);

}

The problem with this is that the SiteDto has been passed directly into the service. This means that the properties for the Site have been set externally to the service, and consequently any business decisions will have been made externally. This sort of service helps to ensure that business logic is pushed out of the domain, and possibly up into the UI.

No comments:

Post a Comment