A model SOA application in .NET Part 4: the service layer part 1
December 26, 2013 1 Comment
Introduction
Now that we’re done with the domain and repository layers it’s time to build the service layer. If you are not familiar with the purposes of the service layer then make sure to read this post. I’ll employ the same Request-Response pattern here.
The service layer
Open the application we’ve been working on and add a new C# class library called SoaIntroNet.Service. Remove Class1.cs and make a reference to both the Domain and the Repository layers. Insert a new folder called Responses and in it a base class for all service responses:
public abstract class ServiceResponseBase { public ServiceResponseBase() { this.Exception = null; } public Exception Exception { get; set; } }
The clients will be able to purchase and reserve products. We put the result of the purchasing and reservation processes into the following two Response objects:
public class PurchaseProductResponse : ServiceResponseBase { public string PurchaseId { get; set; } public string ProductName { get; set; } public string ProductId { get; set; } public int ProductQuantity { get; set; } }
public class ProductReservationResponse : ServiceResponseBase { public string ReservationId { get; set; } public DateTime Expiration { get; set; } public string ProductId { get; set; } public string ProductName { get; set; } public int ProductQuantity { get; set; } }
Nothing really fancy up to this point I hope. In order to make a purchase or reservation the corresponding Request objects must be provided by the client. Add a new folder called Requests and in it the following two requests:
public class PurchaseProductRequest { public string CorrelationId { get; set; } public string ReservationId { get; set; } public string ProductId { get; set; } }
public class ReserveProductRequest { public string ProductId { get; set; } public int ProductQuantity { get; set; } }
The only somewhat unexpected property is the correlation id. Recall its purpose from the opening post in this series: ensure that a state-changing operation is not carried out more than once, e.g. purchasing the same tickets twice.
Add a folder called Exceptions and insert the following two custom exceptions:
public class ResourceNotFoundException : Exception { public ResourceNotFoundException(string message) : base(message) { } public ResourceNotFoundException() : base("The requested resource was not found.") { } }
public class LimitedAvailabilityException : Exception { public LimitedAvailabilityException(string message) : base(message) {} public LimitedAvailabilityException() : base("There are not enough products left to fulfil your request.") {} }
As the names imply we’ll throw these exception if some requested resource could not be located or that the required amount in the request could not be fulfilled.
Add the following service interface to the Service layer:
public interface IProductService { ProductReservationResponse ReserveProduct(ReserveProductRequest productReservationRequest); PurchaseProductResponse PurchaseProduct(PurchaseProductRequest productPurchaseRequest); }
This represents our service contract. We state that our service will be able to handle product purchases and reservations and they need the necessary Request objects in order to fulfil their functions. The concrete implementation – which we’ll look at in a bit – is an unimportant implementation detail from the client’s point of view.
However, before we do that we’ll need to revisit the repository layer and make room for saving and retrieving the messaging history based on the correlation ID. These messages are not part of our domain so we won’t bother with setting up a separate Message object.
Open SoaIntroNet.Repository and add a new folder called MessagingHistory. Insert the following interface which describes the operations a message repository must be able to handle:
public interface IMessageRepository { bool IsUniqueRequest(string correlationId); void SaveResponse<T>(string correlationId, T response); T RetrieveResponseFor<T>(string correlationId); }
The purpose of IsUniqueRequest is to show whether the message with that correlation ID has been saved before. You can probably guess the purpose of the other two methods.
Here comes an implementation of the repository with the same lazy loading static initialiser we saw in the ProductRepository example:
public class MessageRepository : IMessageRepository { private Dictionary<string, object> _responseHistory; public MessageRepository() { _responseHistory = new Dictionary<string, object>(); } public bool IsUniqueRequest(string correlationId) { return !_responseHistory.ContainsKey(correlationId); } public void SaveResponse<T>(string correlationId, T response) { _responseHistory[correlationId] = response; } public T RetrieveResponseFor<T>(string correlationId) { if (_responseHistory.ContainsKey(correlationId)) { return (T)_responseHistory[correlationId]; }; return default(T); } public static MessageRepository Instance { get { return Nested.instance; } } private class Nested { static Nested() { } internal static readonly MessageRepository instance = new MessageRepository(); } }
We store the responses in a Dictionary. In a real case scenario we can store these in any type of storage mechanism: a database, a web service, cache, you name it. An in-memory solution is the easiest in this case.
A side note: it’s not necessary to go with the lazy singleton pattern for all repositories. However, in this example the pattern will make sure that a single object is created every time one is required and the in-memory objects won’t be re-instantiated. We don’t want to lose the Dictionary object every time somebody sends a new purchase request. In the series on DDD, especially in this post and the one after that, I show how it suffices to lazily instantiate a Unit of Work instead of all implemented repositories.
Just like for the ProductRepository we’ll need the corresponding factory:
public interface IMessageRepositoryFactory { MessageRepository Create(); }
public class LazySingletonMessageRepositoryFactory : IMessageRepositoryFactory { public MessageRepository Create() { return MessageRepository.Instance; } }
We can now turn our attention to the Service layer again. Add a class called ProductService with the following stub:
public class ProductService : IProductService { public ProductReservationResponse ReserveProduct(ReserveProductRequest productReservationRequest) { throw new NotImplementedException(); } public PurchaseProductResponse PurchaseProduct(PurchaseProductRequest productPurchaseRequest) { throw new NotImplementedException(); } }
The ProductService will need some help from the Repository layer to complete these tasks. It will need a product repository and a message repository. These objects are created by our lazy singleton factories. We know from the discussion on SOLID and especially the Dependency inversion principle that an object – in this case the ProductService – should not create its own dependencies. Instead, the caller should inject the proper dependencies.
The most obvious technique is constructor injection. Add the following private fields and a constructor to ProductService:
private readonly IMessageRepositoryFactory _messageRepositoryFactory; private readonly IProductRepositoryFactory _productRepositoryFactory; private readonly IMessageRepository _messageRepository; private readonly IProductRepository _productRepository; public ProductService(IMessageRepositoryFactory messageRepositoryFactory, IProductRepositoryFactory productRepositoryFactory) { if (messageRepositoryFactory == null) throw new ArgumentNullException("MessageRepositoryFactory"); if (productRepositoryFactory == null) throw new ArgumentNullException("ProductRepositoryFactory"); _messageRepositoryFactory = messageRepositoryFactory; _productRepositoryFactory = productRepositoryFactory; _messageRepository = _messageRepositoryFactory.Create(); _productRepository = _productRepositoryFactory.Create(); }
We let the factories be injected through the constructor. This follows the good habit of programming against abstractions. We then ask the factories to create the repositories for us – note that they are abstractions as well.
In a real application you wouldn’t rely on the memory to store objects for you. A more “normal” constructor would look like this:
private readonly IMessageRepository _messageRepository; private readonly IProductRepository _productRepository; public ProductService(IMessageRepository messageRepository, IProductRepository productRepository) { if (messageRepository == null) throw new ArgumentNullException("MessageRepository"); if (productRepository == null) throw new ArgumentNullException("ProductRepository"); _messageRepository = messageRepository; _productRepository = productRepository; }
However, for the reasons outlined above we go with the factory solution in this demo. If we followed this simplified version then our in-memory data would be re-instantiated after every subsequent request making a demo meaningless.
We’ll see in the next post how the reserve ticket and purchase ticket methods are implemented.
View the list of posts on Architecture and Patterns here.
Pingback: Architecture and patterns | Michael's Excerpts