Last year, Apiumhub organized the Global Software Architecture Summit, a 2-day event that focused on software architecture metrics. During this event, I had the opportunity to attend the talk “Understanding the Differences Between Modularity and Granularity” by Mark Richards, an experienced, hands-on software architect and co-author of the books “Fundamentals of Software Architecture” and “Software Architecture: The Hard Parts.” In this presentation, Mark explained how modularity and granularity can be helpful to decide how an application may evolve, being split into different services or grouping them back together.
To better understand what we are talking about, let´s first understand the definition of the terms modularity and granularity (from the book Software Architecture: The Hard Parts by the same Richards, Neal Ford, Pramod Sadalage, and Zhamak Dehghani);
Modularity concerns breaking up systems into separate parts.
Granularity deals with the size of those separate parts.
Interestingly enough, most issues and challenges within distributed systems are typically not related to modularity, but rather granularity.
Review of Understanding the Differences Between Modularity and Granularity
Why should I consider breaking apart my application into smaller distributed components?
In software development, some decisions are taken based on opinions and not based on objective criteria. It shouldn’t be taken on the trendy topic but based on the real needs.
It may be a good justification to split the monolithic application when your system must follow some ‘ilities’ like:
- Fault tolerance
How should I approach breaking apart my monolithic application into smaller parts?
Once the team has decided that the decomposition is justified and feasible, it has to check the best way to do it. Depending if the project is in a mature state, where the different components of the system are definable, we could choose a component-based decomposition. On the contrary, if it’s in a more chaotic state, tactical forking may be a better approach.
a) Component-based decomposition
- Monolithic application
- Identify and size components
- Gather common components
- Flatten components
- Analyze component dependencies
- Create component domains
- Create domain services
- Break apart further
Once the components are identified, classified, grouped or separated into different concerns, different domain services will arise and different microservices will be able to emerge.
A detailed description can be found in the mentioned book.
b) Tactical forking
When the project doesn’t have the modules so clear, don’t try to extend the new service. It’s probably going to be painful and full of errors. Instead, the whole project must be replicated and deployed twice. From here, each application will follow its own path and will evolve as a different project. Any code from the other application can be deleted without any problem. In the long run, different applications will appear.
What is the right level of granularity for a service?
Mark Richards asked the audience how we would split a service with different responsibilities. Some hands were raised to choose an option, some others for the second one. And the best answer was ‘it depends’. It depends on each application, and on their usage. Again, we need to take decisions based on some data, not on what we may think.
When should I consider breaking apart a service?
Let’s consider a typical notification service, which includes different ways to send a notification. Via SMS text, Email and Postal Letter.
1.- Service functionality
We may break the single service into three services, following the single responsibility principle: SMS service, email service, and letter service.
2.- Code volatility
We could find that some parts of the code changed quicker than others. For example, the SMS text and the postal letter have some well-standardized protocols, and the code is rarely modified. But the email service has changed a lot recently due to some changes in providers,… In this case, it may be correct to split into two services: SMS text and postal letter, which are kept under TraditionalService, and email under Electronic Notification Service.
3.- Scalability and throughput
All the parts of the code scale the same? Do they have the same output request? In our service, we see that a postal letter is sent once every minute, 500 emails are sent every minute, and thousands of SMS are sent. Based on that data, we may need to split the services so each one can be scaled in different ways. We may not need to scale the postal service at all, but the SMS service must be scaled to cope with all the traffic.
4.- Fault tolerance
Some areas are more error-prone than others and can impact other services. For example, in our case, the email service is working with a provider that is failing from time to time. If that provider fails, all the notifications might be affected as well. It seems a good idea to split the email service into its own service so the rest of the application works without any issues.
5.- Access restriction
Not all the code needs the same restriction regarding security. Some parts should be treated very carefully. For example, if we have a Profile service where we manage the profile data and the credit card details, it may be a good principle to split it into Profile and Wallet services, where the Wallet service has some restrictions when accessing its data.
When should I consider putting services back together?
1.- Database transactions
One of the big difficulties is managing database transactions among different microservices. For example, when creating a new user, if we keep the profile service in one microservice, and the security information in another, there is no acid (db) transaction possible. That may be a good time to join these two services.
2.- Data dependencies
When service A needs data from service C, service B from A, and service C from A and B, something is telling us that those three services could be glued together.
3-. Workflow and choreography
From one monolithic service, we created several microservices.
Before, the response time was acceptable. Now the responsiveness and performance are much worse because we need to add the time of all the calls among microservices.
Also, reliability and data consistency are much more complicated. If one microservice fails, the customer receives an error. If that is the case, you need to consider going back to monoliths.
This was a thought-provoking talk by Mark Richards about a topic that sometimes is not well-considered. In software architecture, as they say, there is no silver bullet and for each case objective points and data must be collected and analyzed. The favorite response for Mister Richards to most of the questions is ‘It depends’ because this is a reality. Every situation has a convenient solution. In this talk, Richards summarized some points to decide when to join or when to split a service, based on real knowledge.
Interested in watching other talks from the past edition of GSAS, you can head to Apiumhub‘s YouTube channel.
Tickets for the third edition of GSAS are already available on the event site. This year’s event will take place on October 9-11 at the AXA Auditorium in Barcelona. It will be focused on modern practices in software architecture: how to be more effective, efficient and enjoy what you do. Use code apiumhub-community at checkout to enjoy a 30% discount on your tickets for being part of our community!