I'm in the process of decomposing a monolithic application into services that will eventually become standalone micro-services. Part of the task ahead lies in determining the service boundaries, which are analogous to software components for my micro-service application.
I want my components to be cohesive because I want my architecture to be so simple that people wonder why we need an architect at all. It should be intuitively obvious why a group of classes belong together in a component and what part of my domain logic they're implementing. Cohesion is a good thing and we're all familiar with writing cohesive classes, but what principals are important to consider when looking at grouping up classes into cohesive components?
Robert C Martin discusses three important concepts that govern component cohesion on his website (here)
- Release-Reuse equivalency principle (REP) - the granule of release is the granule of reuse
- Common Closure principle (CCP) - classes that change together are packaged together
- Common Reuse principle (CRP) - Classes that are used together are packaged together
The Release-Reuse equivalence principle (REP) is very simple. It states that classes that are packaged together into a component need to be able to be released together. In practice this boils down to properly versioning your releases and having all of the classes in your component versioned and released together.
The Common Closure principle (CCP) states that you should gather together classes that change for the same reasons and the same times. Conversely you should separate out classes that change for different reasons and at different times.
Remember that the S of SOLID stands for "single responsibility principle" (SRP) where a class should have only one reason to change? The CCP is for components what the SRP is for classes.
We can say that generally stuff that changes together should be kept together.
The Common Reuse principle (CRP) states that you should not force users of a component to depend on things they don't need.
The CRP more strongly suggests that we do not include classes into a component that are not tightly bound to the function of the component. Every time we touch one of those classes we will be forced to retest all of the client applications that are using the component. Our deployment process will be heavier than it needs to be, and crucially we'll be deploying more than we have to.
The CRP is a more general form of the interface segregation principle but suggests that a component should be built from classes that are commonly used together.
Generally speaking, we should avoid depending on things that we don't need.
We've seen three principles that govern how we group up classes into components. The REP and CCP are inclusive about grouping up classes and suggest what classes do belong together. The CRP is more strong about excluding classes from a component. There is therefore a balance to be walked between these principals.
Tim Ottinger suggested a diagram that helps to see the cost of abandoning a principle. The label on an edge is the cost of weakening adherence to the principle on the opposite vertex. So, for example the cost of abandoning CCP is that we have too many components changing at one time.
|Diagram suggested by Tim Ottinger illustrating tension between component cohesion principles|
This balance is dynamic and changes over time. Robert C Martin notes that “A good architect finds a position in that tension triangle that meets the _current_ concerns of the development team, but is also aware that those concerns will change over time.”
These principles will govern how I examine my monolith and identify classes that I can group together to form components.