Skip to main content

Component cohesion

Image: Pixabay
Breaking your application down into components can be a useful approach to a "divide and conquer" methodology.  Assigning specific behaviour to a component and then defining interfaces for other components to access it allows you to develop a service driven architecture. 

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 components to be modular to allow them to be developed and deployed as independently as possible.  I'm using the approach suggested by Eric Evans in his book on domain driven design where he describes the concept of "bounded contexts".  I like to think of a bounded context as being for domain models as a namespace is for classes.  These contexts are spaces where a domain model defined in the Ubiquitous Domain Language will have a precise and consistent meaning.  Keeping components modular helps to define and maintain these boundaries.

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
Your application will fall somewhere within this triangle as you balance your focus between the 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.

Comments

Popular posts from this blog

Using Fail2Ban to protect a Varnished site from scrapers

I'm using Varnish to cache a fairly busy property site.  Varnish works like a bomb for normal users and has greatly improved our page load speed. For bots that are scraping the site, presumably to add the property listings to their own site, though the cache is next to useless since the bots are sequentially trawling through the whole site. I decided to use fail2ban to block IP's who hit the site too often. The first step in doing so was to enable a disk based access log for Varnish so that fail2ban will have something to work with. This means setting up varnishncsa.  Add this to your /etc/rc.local file: varnishncsa -a -w /var/log/varnish/access.log -D -P /var/run/varnishncsa.pid This starts up varnishncsa in daemon mode and appends Varnish access attempts to /var/log/varnish/access.log Now edit or create /etc/logrotate.d/varnish and make an entry to rotate this access log: /var/log/varnish/*log { create 640 http log compress ...

Translating a bit of the idea behind domain driven design into code architecture

I've often participated in arguments discussions about whether thin models or thin controllers should be preferred.  The wisdom of a thin controller is that if you need to test your controller in isolation then you need to stub the dependencies of your request and response. It also violates the single responsibility principal because the controller could have multiple reasons to change.   Seemingly, the alternative is to settle on having fat models. This results in having domain logic right next to your persistence logic. If you ever want to change your persistence layer you're going to be in for a painful time. That's a bit of a cargo cult argument because honestly who does that, but it's also a violation of the single responsibility principal.   One way to decouple your domain logic from both persistence and controller is to use the "repository pattern".   Here we encapsulate domain logic into a data service. This layer deals exclusively with imple...

Separating business logic from persistence layer in Laravel

There are several reasons to separate business logic from your persistence layer.  Perhaps the biggest advantage is that the parts of your application which are unique are not coupled to how data are persisted.  This makes the code easier to port and maintain. I'm going to use Doctrine to replace the Eloquent ORM in Laravel.  A thorough comparison of the patterns is available  here . By using Doctrine I am also hoping to mitigate the risk of a major version upgrade on the underlying framework.  It can be expected for the ORM to change between major versions of a framework and upgrading to a new release can be quite costly. Another advantage to this approach is to limit the access that objects have to the database.  Unless a developer is aware of the business rules in place on an Eloquent model there is a chance they will mistakenly ignore them by calling the ActiveRecord save method directly. I'm not implementing the repository pattern in all its ...