Microservices and CQRS: the Benefits Business Should Know About
How to make client application implementation easier using CQRS? Are microservices the panacea that you have been looking for? Is architecture planning necessary for reducing the cost and scaling in the later development stages?
We will answer these questions below and illustrate them with examples from our experience.
Our post is for you if you are not that well-versed in the delicate technical points and have to manage software system or product development. The budget is always tight enough to look for a way to reduce the cost and make the release date as near as possible.
Software architecture is the bridge you didn’t know you needed but that saved your day. As software architecture connects often abstract business goals and the complete product or system, it serves as the basis for future product development, scaling, and optimization. Moreover, it can assist with improving customer experience, embrace business efforts carried out towards achieving KPIs, and determine how rapidly and cost-effectively business goals can be achieved with automated processes.
Cost
Software architecture creates a stock of reusable elements. Using them reduces the total cost and improves quality. Furthermore, the architecture makes product changing in the later development process easier and cheaper. Software architecture is important not only for the development process but also for your team. It makes it easier to work with a logically structured architecture and improves the communication within the team. Thus, the team performs better and brings in the desired results.
Risk management
Software architecture prevents development risks as well as business risks. Sound software architecture allows us to prioritize, implement, and maintain risk-reducing measures. In turn, these measures can improve the business processes in various ways and/or eliminate the disruptive impact of the risk.
It is always about competition
Businesses compete against each other: which of all the competing businesses can gain the largest share of a certain market? “Businesses that ensure their products have a good software architecture” is the answer. It ensures your business — responds to market demands faster than the competition by introducing changes to the product that are needed there and then.
Adaptable business-model
Businesses need to monitor the environment and adapt their behavior in response to the situation. To dynamically adapt to changes quickly,
it is necessary to have flexibility granted by architecture roadmap and product roadmap alignment. If there is no connection between software architecture and business architecture, there is no strategic adaptability.
Choices, choices, choices
Whether specific business requirements will be met depends on the architecture, which in turn determines the choice of technical solutions. The programming language, platform, framework, and tools can limit or expand the product’s capabilities. These technical solutions determine the speed of any change introduced to the product. The business requirement to be always available for service may not be met if MVP can’t cope with a rapid increase in the number of simultaneous users.
One extreme or the other
Some claim that there is no need for software architecture since the MVP release is a matter of urgency. Others contend that microservices is the silver bullet that will ensure scalability and flexibility of the product, and should be used from the very beginning of the design and development process.
Truth is somewhere in between those two extremes. There are a lot of questions still. If not from the beginning, then when should one proceed with architecture design? How detailed should it be? Should one consider one or another style and patterns? Later in the article, we will look at how to come to the middle ground.
Big Ball of Mud
Business stakeholders say that business needs to get the first users to sign up, and the rest is going to be figured out later. “The rest” is customer service, communication, and product improvement which are quite important for a business to succeed.
That’s how a big ball of mud — a haphazardly structured system — comes about.
Often, a big ball of mud is created through an iterative and incremental approach to software development. To make the release date as near as possible, MVP is developed with no architecture design, sometimes nonfunctional requirements are not even taken into consideration. And only then a long and complex cycle of refinement starts. As a result, the system performs poorly and can hardly be improved.
Alt text: Chaotic architecture of the Big ball of mud
To our knowledge, the absence of architecture adds extremely high costs of trying to remedy that situation. No one wants a product to crush because it is incapable of managing overwhelming users’ inflow.
Owing to the first customer crush, the team is working for hours and hours trying to fix constantly occurring bugs, frustrated customers stop using the product, and leave. The business is experiencing all consequences of the lack of architecture.
In some instances re-coding the entire system may be required. It is costly in terms of time, efforts and money. Moreover, the users are forced to assimilate the change. Thus, already frustrated users feel like business doesn’t care about their needs. It erodes the credibility of the business and undermines future sales.
What is it about microservices?
Microservices or monolithic architecture? That’s the question. Microservices is a suit of modular services, each of which supports a single business goal and communicates with other sets through an interface called API (application program interface). Each service is developed, deployd and tested individually. Conversely, monolithic architecture combines different components into a single program from a single platform.
Since modular services are so small and independent, they can be used separately. Thus, any specific task can be done with languages, frameworks, tools, etc. that fits the most.
Alt text: Microservice architecture is created in small, independent, modular services
Closer look at salient points
Over the past few years there has been much hype around microservices. Some clients insist on building microservice architecture right from the outset. This, of course, can cost dearly. To make this work out, one needs a fundamental understanding of technological targets and a vision of what microservices architecture will look like. The main reason for any microservice to exist is to have several services operate independently of each other. Without a vision this can result in a chaotic and ill-functioning system.
For example, it is a challenge to follow a sequence of calls to different services resulting in errors since their interdependence is low. And if the system performs poorly, how to find the hampering service? Ready-made infrastructure solutions (for example, Elastic for logging) and custom infrastructure services can address these challenges in some way, but there are no promises.
Should microservices share a database? Should each microservices own its data exclusively? Data integrity is worth our attention and should be put into consideration beforehand. If overlooked, the consequences won’t be pretty: data loss or desynchronization.
There are certain challenges to be met when creating a decoupled system with independent services. At the early stages, the overall functionality of the product isn’t clear yet nor is the structure of the business domain. If decoupling isn’t done correctly it will make it difficult to implement the required scenarios of work of the system. Sometimes leading to failures altogether.
The price is steep
Deployment of microservices may be costly, depending on the size of a product. If you have to do it for several services in several configurations and check performance and scalability then it may cost a substantial amount.
Furthermore, the outcome is always unknown. It is always hard to predict if the product is going to be successful and how many customers are ready to sign up. Because we are trapped in situations of limbo, there is little point in large architecture investments, especially at the early stages.
Thought out architecture underpins success
It takes a view beyond the surface with all of its complex interdependencies and the broad-based overall picture to avoid an avalanche of failures that appear one after another, turning the development and maintenance processes into bug fixing neverending parade. It is avoidable, though, if there is a certain architecture vision at the very early stages of product design. Qualified decomposition and description of the capacity, materials, and expertise business needs to perform key functions should be kept in mind while designing a microservices architecture.
Example
Now we have the request for an e-commerce website. Projects like this succeed as long as the development team has a full understanding of the main business processes and place user experience at the center of its work. What does it take to make a customer happy? Good user experience is shaped by many things. Not the least by accurate order assembly by the Order Picker at the warehouse, convenient online payment and delivery options available to the customer. For that, a thought out user interface for warehouse staff is required, as well as integration with payment systems and warehouse logistics system.
But what if one fails to wrap its head around the importance of the connection between business processes and website functionality? Then the system will not be able to correspond to the business requirements resulting in a useless system that no one can use effectively. What is more, additional budgeting is required to fix that situation. Could it be avoided? Definitely.
It takes a lot of skill and expertise to build the right functionality when the time comes. The interaction of the system services shouldn’t be overlooked. There should be a better understanding of how the system services interact with one another.
We can use a CRM system as an example of a situation where a readily available solution is possible since the client does not require anything unique in terms of customer relationship management. And after detailed research on the business environment, informed decision-making on the choice of software architecture and careful system decomposition is also possible.
Where should you begin?
Workshop time! One- or two-day workshop is just a necessity since it not only provides the development team with the critical business requirements and processes but also helps to shape architectural solutions. When dealing with a startup, start with filling out a Business Model Canvas to test the business idea for sustainability. The most important output will be a document describing non-functional requirements for the system.
Of course, there is always a chance that stakeholders decide to throw that business idea away as it is not viable after clear-headed evaluation and detailed discussion. Fear not, it is still worth the time and may save you a lot of money should you abandon the business idea that’s not worth pursuing.
Why even bother? A two-day workshop enables you to see some problems that may occur down the line. Thus, you and the development team can address the risks before they even occur. Prevention is better than cure.
The world we knew so well had changed in a matter of days. Now we face new challenges as the pandemic of Covid-19 rises and it’s not going to vanish in the nearest future. Businesses meet new days in a different environment and they are forced to adapt. This demonstrates how important it is to have the capability to change and transform.
As mentioned above, effective architecture equals the flexibility of a product and its ability to evolve following the change of the environment. For example, CQRS if used at the logical level at the beginning of the design process makes it possible to physically locate the services while the business and product are scaling. Thus, it provides us with a system divisible into smaller independent elements, with components that can be reused.
A word of caution. Distributed transactions and other dependencies are not that necessary at the early stage. Do not waste already limited resources trying to make useless implementations.
CQRS at the logical level
What is CQRS?
Command Query Responsibility Segregation (CQRS) is a popular architectural pattern that divides reading and writing into two different actions. It claims that every method should either be a command that changes or updates data, or a query that doesn’t change or only reads data.
Our experience
The client highlights from the start that the budget is tight and so are the deadlines. The request was to develop a maintenance system for a company managing a fleet of sea vessels. We chose the approach where we implemented CQRS at the level of logical architecture since it was thrifty and fast.
Alt text: Our approach choice of implementing CQRS at the level of logical architecture is detailed in the diagram.
All the commands and queries become reusable elements owing to observing the basic principles of SOLID (Dependency Inversion and Dependency Injection) and appropriately used Inversion of Control Containers.
3 parts of the system that can work similarly to the Model are detailed in the diagram.
- Admin Office Desktop App — Model interaction
- Ship Desktop Mechanic App — Model interaction
- SyncService — Model interaction
It is important to analyze the functionality of the system to make it more effective in regards to further cost-effective development, scalability, and maintenance. The budget is always tight, remember?
It was observed that the Admin Office Desktop App users, whose workplaces are located in the office, read data from the vessels more frequently than input any data. Au contraire, users from the vessels made more data entries rather than read any. Of course, architecture as it is at this stage is the base for capacity-building and developing a greater potential. When the time comes, there will be a solid base and enough knowledge to allocate query contexts to build services with the DB optimized to enable reads and allocate command contexts into separate services.
Segregation examples
You can a few examples of the main # C classes below. Feel free to use them in your own projects.
/// <summary>/// Universal factory for creating queries./// To be implemented in Composition Root (web api project, website main prototype, etc)/// </summary>public interface IHandlersFactory{IQueryHandler<TQuery, TResult> CreateQueryHandler<TQuery, TResult>();IAsyncQueryHandler<TQuery, TResult> CreateAsyncQueryHandler<TQuery, TResult>();ICommandHandler<TCommand> CreateCommandHandler<TCommand>();IAsyncCommandHandler<TCommand> CreateAsyncCommandHandler<TCommand>();}/// <summary>/// Basic interface for the command/// </summary>public interface ICommandHandler<TCommand>{void Execute(TCommand command);}/// <summary>/// Basic interface for the query/// </summary>public interface IQueryHandler<TQuery, TResult>{TResult Execute(TQuery query);}/// <summary>/// Factory Ninject to create standardised commands and queries/// </summary>public class NinjectFactory : IHandlersFactory{private readonly IResolutionRoot _resolutionRoot;public NinjectFactory(IResolutionRoot resolutionRoot){_resolutionRoot = resolutionRoot;}public IAsyncCommandHandler<TCommand> CreateAsyncCommandHandler<TCommand>(){return _resolutionRoot.Get<IAsyncCommandHandler<TCommand>>();}public IAsyncQueryHandler<TQuery, TResult> CreateAsyncQueryHandler<TQuery, TResult>(){return _resolutionRoot.Get<IAsyncQueryHandler<TQuery, TResult>>();}public ICommandHandler<TCommand> CreateCommandHandler<TCommand>(){return _resolutionRoot.Get<ICommandHandler<TCommand>>();}public IQueryHandler<TQuery, TResult> CreateQueryHandler<TQuery, TResult>(){return _resolutionRoot.Get<IQueryHandler<TQuery, TResult>>();}}Binding of queries with Ninject examplespublic override void Load(){// queriesBind<IQueryHandler<GetCertificateByIdQuery, Certificate>>().To<GetCertificateByIdQueryHandler>(); Bind<IQueryHandler<GetCertificatesQuery, List<Certificate>>>().To<GetCertificatesQueryHandler>(); Bind<IQueryHandler<GetCertificateByShipQuery, List<Certificate>>>().To<GetCertificateByShipQueryHandler>();………….}
Inject IHandlerFactory into the class and use your commands and queries as it is below.
Query example:
Ship ship = mHandlersFactory.CreateQueryHandler<GetShipByIdQuery, Ship>().Execute(new GetShipByIdQuery(id));
Command example:
mHandlersFactory.CreateCommandHandler<DeleteReportCommand>() .Execute(new DeleteReportCommand(report));
It is only by taking into account the business environment, functional and non-functional requirements, and the situation in general, one can make the right architectural decision. It is detrimental to go from one extreme to another. No need to go ahead and design fully detailed architecture for the entire product or system from the get-go, no need to abandon architecture completely. As the decision-making process takes place, ask yourself the following questions:
- Why is the structure of the software looking this way? What was the reason for using this particular implementation?
- Does the new implementation suit well the overall structure of the system?
- How to meet the non-functional requirements of the entire system?
In the next article, we’ll talk about Bounded Context identifying. It aims to find common ground for fruitful communication with a client, have a clear visual representation of the customer’s business structure, and manage the regression in the product.
Advantages of CQRS
Multiple design patterns and principles were used as a foundation for the application of basic architecture: MVVM, SOLID, CQRS, etc. With that, we were able to wisely reuse some parts of functionality in different client applications. So the implementation was fast-paced and cost-effective.
By the time the development was to begin, all of the team members were on the same level of understanding and perspective of the application architecture. Moreover, we developed classes. The subsequent stage in refinement yielded fruitful results: about 40% of functionality could be used again. The customer was able to reduce the development cost by around 30–40%.
To sum up
There are still two extremes. The agile approach calls for a quick build — measure — learn cycle, which does not imply architecture design as such. The second one promotes a thorough architecture planning trying to capture all possibilities of the potential product functionality right from the very start.
These extremes are dangerous and can be costly. A balanced approach is better for both the team and the business.
Firstly, decision-making is much easier if there is a certain vision for the product architecture. Thus, it makes development cheaper in the later development stages.
Secondly, documenting software architecture should describe why certain decisions were made. So your team can refer to what was decided and why and new employees can join the workflow easier.
Thirdly, monitoring the external environment allows us to reduce the risks of failures by adapting the product to the changing market requirements.
In that way, giving some thought to what a product’s architecture will look like using competent coding practices provides a solid foundation that will enable scaling the product in the future.