A model SOA application in .NET Part 5: the service layer continued
December 30, 2013 6 Comments
Introduction
In the previous post we started implementing the IProductService interface. We got as far as declaring a couple of private fields and a constructor. Here we’ll implement the ReserveProduct and PurchaseProduct methods.
The concrete service continued
Open the project we’ve been working on in this series and locate ProductService.cs. The implemented ReserveProduct method looks as follows:
public ProductReservationResponse ReserveProduct(ReserveProductRequest productReservationRequest) { ProductReservationResponse reserveProductResponse = new ProductReservationResponse(); try { Product product = _productRepository.FindBy(Guid.Parse(productReservationRequest.ProductId)); if (product != null) { ProductReservation productReservation = null; if (product.CanReserveProduct(productReservationRequest.ProductQuantity)) { productReservation = product.Reserve(productReservationRequest.ProductQuantity); _productRepository.Save(product); reserveProductResponse.ProductId = productReservation.Product.ID.ToString(); reserveProductResponse.Expiration = productReservation.Expiry; reserveProductResponse.ProductName = productReservation.Product.Name; reserveProductResponse.ProductQuantity = productReservation.Quantity; reserveProductResponse.ReservationId = productReservation.Id.ToString(); } else { int availableAllocation = product.Available(); reserveProductResponse.Exception = new LimitedAvailabilityException(string.Concat("There are only ", availableAllocation, " pieces of this product left.")); } } else { throw new ResourceNotFoundException(string.Concat("No product with id ", productReservationRequest.ProductId, ", was found.")); } } catch (Exception ex) { reserveProductResponse.Exception = ex; } return reserveProductResponse; }
First we let the product repository locate the requested product for us. If it doesn’t exist then we throw an exception with an appropriate message. We check using the CanReserveProduct method whether there are enough products left. If not then we let the client know in an exception message. Otherwise we reserve the product, save the current reservation status and populate the reservation response using the product reservation returned by the Save method. We wrap the entire code in a try-catch block to make sure that we catch any exception thrown during the process.
Here’s the implemented PurchaseProduct method:
public PurchaseProductResponse PurchaseProduct(PurchaseProductRequest productPurchaseRequest) { PurchaseProductResponse purchaseProductResponse = new PurchaseProductResponse(); try { if (_messageRepository.IsUniqueRequest(productPurchaseRequest.CorrelationId)) { Product product = _productRepository.FindBy(Guid.Parse(productPurchaseRequest.ProductId)); if (product != null) { ProductPurchase productPurchase = null; if (product.ReservationIdValid(Guid.Parse(productPurchaseRequest.ReservationId))) { productPurchase = product.ConfirmPurchaseWith(Guid.Parse(productPurchaseRequest.ReservationId)); _productRepository.Save(product); purchaseProductResponse.ProductId = productPurchase.Product.ID.ToString(); purchaseProductResponse.PurchaseId = productPurchase.Id.ToString(); purchaseProductResponse.ProductQuantity = productPurchase.ProductQuantity; purchaseProductResponse.ProductName = productPurchase.Product.Name; } else { throw new ResourceNotFoundException(string.Concat("Invalid or expired reservation id: ", productPurchaseRequest.ReservationId)); } _messageRepository.SaveResponse<PurchaseProductResponse>(productPurchaseRequest.CorrelationId, purchaseProductResponse); } else { throw new ResourceNotFoundException(string.Concat("No product with id ", productPurchaseRequest.ProductId, ", was found.")); } } else { purchaseProductResponse = _messageRepository.RetrieveResponseFor<PurchaseProductResponse>(productPurchaseRequest.CorrelationId); } } catch (Exception ex) { purchaseProductResponse.Exception = ex; } return purchaseProductResponse; }
If you recall from the first part of this series we talked about the Idempotent pattern. The IsUniqueRequest is an application of the pattern. We check in the message repository if a message with that correlation ID has been processed before. If yes, then we return the response stored in the repository to avoid making the same purchase again. This is not the only possible solution of the pattern, but only one of the viable ones. Probably the domain layer could have a similar logic as well, but I think this is more robust.
If this is a new request then we locate the product just like in the ReserveProduct method and throw an exception if the product is not found. If the product exists then we need to check if the reservation can be made using the reservation ID of the incoming message. If not, then the reservation either doesn’t exist or has expired and a corresponding exception is thrown. Otherwise we confirm the purchase, save the product and dress up the product purchase response using the product purchase object returned by the ConfirmPurchaseWith method. Just like above, we wrap the code within a try-catch block.
This completes the service layer of the SOA project. We’ll look at the web client in the next post. The web client will be a web service client based on the Web API technology so that we don’t need to waste time and energy on presentation stuff such as HTML and CSS.
View the list of posts on Architecture and Patterns here.
Really cool stuff. I’m hoping to get into an SOA approach in the near future and was really confused as to how to get started. This definitely helps with the creation of the services.
Something that I have yet to understand is – say you were to create an authentication service which all other services called in order to either authenticate a user login, or ensure that the user is logged in – how does this service get integrated into other services without being a part of those projects and/or the authentication service being tightly coupled with all other services?
I guess I get the idea that the services that call the authentication service have to know how to structure their calls to the authentication service, but this seems pretty tightly coupled at least in the way I’m thinking about it.
Looking forward to your next post!
Authentication is always a different beast. There are certainly many ways to integrate authentication and authorisation into a service and they all have their pros and cons. You can separate out this entirely by taking a single sign-on approach where the auth server and logic resides in an assembly separated from your main application.
If you are then designing a SOA service like the ticketing service in this series then all users coming wishing to use it must authenticate with the auth server first and come to the ticketing service with a security token. The ticketing service must then evaluate the token early on in the application’s lifetime and reject the request with some appropriate error message if the token is invalid.
The auth token can be generated in a couple of different ways. I have devoted a number of posts to Claims based security that can get you started.
In mobile applications nowadays a different approach is taken: OAuth2, where login is delegated to providers such as Twitter, Google etc. You’ve probably seen consent screens such as “this application will be able to read your tweets”. That’s a telling sign of OAuth2 in the background.
//Andras
Hey Andras, Thanks a lot for posting this. I am digging into SOA architecture and it really helps. I just got your project from GITHUB and it looks like the SOATester project is missing. Can you hook a brother up?
Thanks and again awesome work!
Hi Allen,
Thanks for your feedback. Just added the tester, please re-check the Git repo.
//Andras
Pingback: Architecture and patterns | Michael's Excerpts
Pingback: Architecture and patterns | Michael's Excerpts