It’s common to hear that “microservice should be small” or “microservice should not be too big”. Well, this is not a very precise metric to go by. Very often teams start creating tiny executables each dealing with a very small concern, and sooner rather than later they run into lots of issues – tight coupling between microservices, distributed transactions, nightmarish development and release process, etc.
To be able to figure out what’s the right size for your microservices we need to go back to the fundamentals – in this case Domain Driven Design and its concepts of Bounded Context and Aggregate. We have already discussed microservices boundaries, and the sizing comes as a flip side of that discussion.
Once we have done a first pass at analyzing our domain model and have some rough bounded context identified, we are ready to look deeper – at DDD Aggregates, which would be our smallest possible releasable and deployable artifacts. We shouldn’t go smaller than a DDD Aggregate as that is our primary way to ensure transaction consistency in a given bounded context. On the flip side, should we go that granular with all of our microservices or should there be more coarse sized microservices?
My recommendation:
- Analyze the bounded context of interest and identify all of it’s aggregates – this would provide you with a list of potential releasable components
- Bounded Context analysis is an iterative process, so identification of aggregates will be happening over the course of system development as business domain understanding is growing, implementation of business processes is progressing.
- Determine which of the aggregates have their very own scalability, availability, security, etc. non-functional requirements – these, most likely, will become standalone microservices
- Determine which aggregates can comfortably co-exist in a single releasable and executable module – their scalability, security, availability, etc. non-functional requirements are similar and they wouldn’t affect each other too much
- In some cases we can have all aggregates (complete bounded context) in a single microservice – nothing is stopping us from doing that, really
- Take into account team ownership over bounded context. In most cases I would recommend having a single team own a bounded context for many reasons, however if there is more than one team involved – that would introduce another dimension of which components should be independently developed, released and managed.
Since we are building highly decoupled, autonomous components, the following set of checks may prove to be helpful to validate the breakdown:
- Transactions are not split between microservices – each request is processed and results in a persisted state change within the boundaries of a single microservice
- Ensuring a single aggregate is not split between different runtime units
- Eliminating distributed transactions
- Each microservice can be developed, versioned, released, scaled independently