Granularity in Software Architecture
What does granularity mean?
Granularity refers to the level of detail or the degree of abstraction used in software design. It determines the size and complexity of the software components and their interactions.
In software architecture, granularity is an important consideration because it affects the flexibility, maintainability, and scalability of the system. A system with fine-grained components may be more flexible but harder to maintain, while a system with coarse-grained components may be easier to maintain but less flexible.
Choosing the right level of granularity for software components is a balancing act that depends on the specific requirements of the system and the trade-offs between flexibility and maintainability, and in this article, we will talk about how to divide the system into multiple components ( e.g micro-services) with some guidance.
While the rationale for breaking down service may involve a single factor, in most cases, it is based on multiple factors.
Service Scope and Function
Service scope and function are the first and most common drivers for breaking up a single service into smaller ones, particularly about microservices. There are two dimensions to consider when analyzing the service scope and function.
The first dimension is cohesion: the degree and manner to which the operations of a particular service interrelate.
The second dimension: is the overall size of a component, usually measured in terms of the total number of statements summed from the classes that make up that service. Is the service performing too many unrelated tasks?
This rule is related to the single-responsibility principle coined by Robert C. Martin as part of his SOLID principles, which states, “Every class should
have responsibility over a single part of that program’s functionality, which it should encapsulate. All of that module, class, or function’s services should be narrowly aligned with that responsibility.” While the single-responsibility principle was originally scoped within the context of classes, in later years it has expanded to include components and services.
Code volatility
it’s the rate at which the source code changes that's another good driver for
breaking a service into smaller ones. This is also known as volatility-based decomposition. Objectively measuring the frequency of code changes in a service (easily done through standard facilities in any source code version-control system) can sometimes lead to a reasonable justification for breaking apart a service. As an example, we have a payment service that handles multiple payment methods. Service scope (cohesion) alone was not enough to justify breaking the service apart.
A difference in code volatility can push services apart into smaller pieces for two reasons:
- Smaller units are easier to test and verify and are typically built and deployed.
- Separating more volatile code from less volatile code means we can leave more of the less volatile code to be deployed less frequently, resulting in less risk
So we can have payment methods that change a lot because of any reason, like integration contract changes, and the rest of the methods don't change frequently, so we can divide the payment methods into separate services based on this case.
Scalability and throughput
The scalability demands of different functions of a service can be objectively measured to determine whether a service should be broken apart. Consider once again the payment service example, where single-service customers use a lot to pay with debit cards. Measuring the scalability demands of
This single service reveals the following information:
• Debit service: 220,000/minute
• credit service: 500/minute
• PayPal service: 1/minute
Notice the extreme variation between the debit service and the PayPal service. As a single service, email and PayPal services must unnecessarily scale to meet the demands of debt service, impacting cost and also elasticity in terms of mean time to startup.
Breaking the Payment Service into three separate services allows
each of these services to scale independently to meet their varying demands for throughput.
Fault tolerance
Fault tolerance describes the ability of an application or functionality within a particular domain to continue to operate even though a fatal crash occurs (such as an out-of-memory condition). Fault Tolerance is another good
Consider the same consolidated payment service example. If the PayPal service functionality continues to have problems with integration conditions and fatally crashes, the entire service comes down, including debit and credit letter processing.
Separating this single consolidated payment service into three separate services provides a level of fault tolerance for the domain of customer payment. Now, a fatal error in the functionality of the PayPal service doesn’t impact other payments.
Extensibility
the ability to add additional functionality as the service context grows. Consider our payment service, which manages payments through multiple payment methods, including credit cards, debit cards, and PayPal transactions. Suppose we want to start supporting other managed payment methods, such as reward points and store credit from returns, and other third-party payment services, such as ApplePay, SamsungPay,
and so on. How easy is it to extend the payment service to add these additional payment methods?
These additional payment methods could certainly be added to a single, consolidated payment service. However, every time a new payment method is added, the entire payment service needs to be tested (including other payment types), and the functionality for all other payment methods is unnecessarily redeployed into production. Thus, with the single consolidated payment service, the testing scope is increased and deployment risk is higher, making it more difficult to add additional
payment types.
Now consider breaking up the existing consolidated service into three separate services (credit card processing, debit processing, and PayPal transaction processing).
Now that the single payment service is broken into separate services by payment methods, adding another payment method (such as reward points) is only a matter of developing, testing, and deploying a single service separate from the others. As a result, development is faster, testing scope is reduced, and deployment risk is lower.
Conclusion
Granularity in software architecture refers to the level of detail or abstraction used in software design, which affects the flexibility, maintainability, and scalability of the system. Choosing the right level of granularity for software components is a balancing act that depends on the specific requirements of the system and the trade-offs between flexibility and maintainability. We have many factors, such as service scope and function, code volatility, scalability and throughput, fault tolerance, and extensibility, that will help us plan how to divide the system into multiple components.