Microservice Architecture Microservice Architecture

Unit of Work: How to make One of the Best Design Patterns for Microservice Architecture

The main objective of this article is to demonstrate how to implement a Unit of Work (UoW) pattern using a real and practical case.

Before starting to explain how to implement a Unit of Work (Uow) pattern using a real and practical case, it is important to clarify that there are several ways to implement a UoW. Some are more purist or manual forms, while others are simpler.On this occasion I am going to demonstrate the way I have found to be simpler and more complete in my experience as an engineer and the author of this article.All comments on other forms of implementation are of course welcome.What do we need to know before we start reading this article?

Issue Summary

Let’s suppose that our User Story states the following: “As a Sales System, when selling a product using the system, I would like to; inform the Stock Control System, inform the Distribution System, and inform (by means of a notification) the Selling User and the Buying User”It seems quite simple: a user buys a product from an online store, the stock must be reduced, the process for sending the product purchased must be initiated, and the owner of the product and the user who is buying it must be informed that the transaction has been completed (via e-mail or something similar).

The problem that presents itself here is when we want to be transactional: what do we do if there is an error in the distribution system? Do we sell the product anyway? Do we report the bug and move on?

Perhaps the same question, or series of questions, is repeated throughout the process. We must make a decision that maintains high cohesion and low coupling, as well as following the principle of “Single Responsibility”, meaning that, simply put, the Microservice that we are developing is responsible only for the “purchase”, and not for the entire data transfer orchestrated between the different applications.We are going to make the following decision, and the proposed transaction will look like this:
1In the SaS (Sales System), enter an order by the BU (Buying User), to purchase a product​2In the SaS (Sales System), enter an order by the BU (Buying User), to purchase a product​3The SaS consults the StS (Stock System) to ascertain whether there are units of the product available and if so registers that it blocks the quantity indicated
in the order​4The StS returns an identification of where the order to be withdrawn by the DS (Distribution System) is found​5The SaS requests that the DS create an order under the identification obtained by the SaS, and that the delivery process is initiated, and the DS returns an identification with the delivery information​6The SaS makes a request to the NS (Notification System), which must inform the BU and the SU (Selling User) that a transaction with the shipping data has been successfully processed.​
Previous
Next
Steps 2, 3, 4, and 5 will be mandatory. If the system cannot successfully complete any of these 4 steps it must execute a rollback operation (leaving the system as it was before the start of the transaction). In this way we will keep the system consistent. For step 6 we simply inform the NS that it should execute these operations, but we are not interested in whether it does so correctly or not.

Solution: Use a Unit Of Work

Well, as you can imagine, the solution is to use a Unit Of Work, which is a pattern that will help us monitor each step of the transaction and maintain the consistency of all the systems involved.

What are we going to need?

  • A dictionary or key-value map where we will store each step executed correctly (so in the event that we need to rollback we simply execute the opposite operation at each step)
  • Adapters to connect to each of the third party systems
  • A repository to store the data from our operation (the sale of the product)

And of course a piece of code

Project structure

Unit of Work Pattern

Code details

1. Dependencies: As the first step, we must define our dependencies: all the functionality we will need (or at least their signatures) to help us meet our objective:

a)  Adapters: Adapters allow us to define the signatures that must respect our adaptations of the external services that we will be using (please read more about this if you have any doubts).

Interface:
Unit of work Pattern
In the adapter interface we know that we will have to register a sale, and in the event of an error we will need to rollback or cancel the sale. Therefore, we specify that our point of contact with the Stock system will be these two methods.Implementation:
Unit of work pattern 2
Here in the implementation they have simply been mocked up as we are not particularly interested in connecting to a non-existent system, so we simply return a positive or successful value.b) Repositories: Repositories allow us to obtain an abstraction of the implementation of data access: we use them as services to access the database.Interface:
Unit of Work Pattern 4
In the interface a few methods are proposed just as an example, but the idea is to define the methods that we need in our UoW that we can mock up.Implementation:
Unit of Work Pattern 5
The implementation is simply an in-memory database from which we filter, add, and remove entities. Nothing spectacular.2. Unit Of Work: The code for a UoW is not complex since it consists of the steps defined in the user case. What is important is knowing how to handle exceptions and how to react to them. It is also important that there is some way to rollback all dependencies.For example, when it is requested that the Stock System is notified of a new operation, it is important to be able to notify it again in the event of a failure. In this way the operation can be canceled, keeping the systems consistent.Interface:
Unit Of Work Pattern 6
Implementation:
Unit of Work Pattern 7
Unit of work Pattern 8
Unit of work Pattern 9
As you can see in the code, the implementation has two private methods “Commit” and “Rollback”. These two methods will be responsible for executing the necessary operations so that the Unit Of Work remains consistent.Finally, the only thing to point out is that when all the code is executed correctly a commit takes place, and in the event of an exception a rollback is executed. As the steps take place, each one of them will be recorded to be confirmed in our dictionary of operations so that the inverse operations can be carried out if necessary.

Mauricio Bergallo

About the Author

Mauricio Bergallo is a Systems Engineer with extensive knowledge in a variety of programming languages. Great experience and understanding with all aspects of the software development life cycle.