Deciding When to Split or Join a Microservice
One of the valuable chapters in “Software Architecture: The Hard Parts” answers this difficult question by rationally justifying the “Wise Why’s” behind when a decision to split or join a microservice should or should not be made
Chapter 7 of the Book Software Architecture: The Hard Parts uses the term “Granularity Disintegrators” to refer to reasons to split a service and uses the name “Granularity Integrators” to indicate the reasons to keep a service as a single unit (or join several services if needed).
I’ll try here to summarize the decision factors discussed in this chapter briefly and add a twist to the advice by first considering your decision based on Integrators (keeping as a single unit) rather than Disintegrators simply because it’s difficult to undo the work after a microservice gets split.
Let’s start with Reasons To Keep (or Join) a Single Service.
Reasons To Join (or Keep) as a Single Service
Join Reason #1: Keeping Database Transactions. If having an ACID transaction is necessary from the business perspective, then keeping the service as a single unit is preferred here. Otherwise, performing rollbacks between two microservices will require running compensation queries and will fall outside the boundaries of an ACID transaction.
Join Reason #2: Services Becomes Call-Dependent on Each Other. When the service to be split will create separate services that depend on calling each other (using workflows or choreography) then nothing is gained from splitting the single service. Infact, fault tolerance will suffer since it creates a chain of dependency calls. In addition, performance will suffer due to the network delays introduced in each call to a service.
As a general guideline, and assuming a single service is to be split into Service A and Service B, if more than 30% of the requests made by Service A are flowing to Service B then probably you will want to keep those two services unified as a single service and avoid splitting to spare the system from the negatively impacted fault tolerance and increased network latencies introduced between calls.
Join Reason #3: Shared Code that ‘Changes Frequently’ or ‘Represent Shared Domain Functionality’. If splitting the service into two or more will result in sharing code functionality via a library, then every time the library code changes will require re-testing and re-deploying each service which could add burden to the maintenance of the system. This is a major issue if the shared code changes frequently or if the services share a high percentage of the same business domain code. In these cases, it might be better to keep the service as a single unit. Keep in mind that cross-cutting concern services such as logging, auditing, authentication, authorization, and monitoring do not fall within this category and would be beneficial to keep them separated.
Join Reason #4: Breaking The Data Bounded Context Rule. The Data Bounded-Context rule in microservices simply states that no service will access directly the datastore of any another service (even in read-only cases) but rather request that data via a request to the data-owning service. If splitting a service into two will result into the two services require reading from each other’s databases frequently, then keeping the services as a single service is beneficial here.
Reasons To Split a Service into Two
This section describes reasons to split a service (termed as “Granularity Disintegrators” in the Hard Parts Book).
Split Reason #1: Service Scope & Function. Simply means that the service has a specific and independent business purpose and can be extracted into it’s own service. Examples include a Notification Service, a Payment Service, a Password Service, etc.
Split Reason #2: Service Contains a Components that Has Frequently Changing Code In Relation to Other Parts. If a service has a component that has code that changes more frequently, this could result in re-deploying and re-testing other parts of the service frequently and unnecessarily. Extracting this frequently changing component into it’s own service could be justified in such case.
Split Reason #3: A Component in the Service Requires High Scalability & Throughput. Let’s say you have a service that among doing many things, has an Image Resizing sub-component within it. This image resizing component is eating up huge amounts of memory within the server running and not allowing other functionalities within the service to breath memory. An architect can take a decision to horizontally scale the service by deploying it to multiple EC2 instances, but still, the same problem remains, this image resizing sub-component is resource intensive, and affecting other functionalities within the service. In such case, we could justify moving the image resizing component into it’s own separate service.
Split Reason #4: Fault-Tolerance. Consider the Image Resizing component under heavy load crashing due to out-of-memory conditions. This will affect the other service functionalities and thus lead to availability issues. In such cases, splitting the service and extracting the image-resizing functionality to another service will add fault-tolerance to the system by containing those crashes into a smaller blast-radius and hence milder impact.
Split Reason #5: Security. If a component has higher access requirements from the rest of the service, then splitting it into it’s own separate service and adding the required security controls on the Service’s API is sufficient to justify the split. Usually, in PCI regulatory requirements, not only data-storage is required to be protected but also how the data is accessed should also be protected, which in this case preferably having a separate service.
Split Reason #6: Extensibility. If it is known ahead of time that a service will require extensibility; for example, a payment service requires adding a plug-in payment type every time a new payment type is introduced. In such scenario, it would be beneficial to create a separate service for each payment type. This will spare having to re-test and re-deploy the service with all it’s payment types at once (and thus risking payment functionality downtime).
Find the right balance before making the decision by discussing the above points with your team. Also, I would advise putting some time to read Chapter 7 from The Software Architecture The Hard Parts book to understand the above decision drivers in more detail.