Skip to main content

Chapter 13: Component Cohesion

"Which classes belong in which components? This is an important decision, and requires guidance from good software engineering principles."

๐ŸŽ“ For New Learnersโ€‹

The Three Cohesion Principlesโ€‹

When deciding which classes to group into a single deployable component (JAR), three principles guide the decision:

PrincipleRuleFocus
REP โ€” Reuse/Release EquivalenceGroup for reusersClasses that are released together should be reused together
CCP โ€” Common ClosureGroup for maintainersClasses that change together should live together
CRP โ€” Common ReuseDon't force unnecessary dependenciesClasses not used together should not be in the same component

REP: Reuse/Release Equivalence Principleโ€‹

"The granule of reuse is the granule of release."

If you publish a library for others to reuse, everything in that library should make sense as a coherent release unit. You shouldn't bundle unrelated things in a single versioned release.

When you put 2.3.0 on a JAR, users trust that all classes in that JAR changed together for related reasons. If you bundle a web client and a PDF generator in the same library, upgrading for a PDF fix drags in web client changes โ€” even if the consumer doesn't use the web client.

From the consumer's perspective: only depend on libraries where most of what they contain is useful to you. Importing a 50-class JAR for one utility class is an ISP violation at the component level.

CCP: Common Closure Principleโ€‹

"Gather into components those classes that change for the same reasons and at the same times."

CCP is SRP applied to components. A component should have only one reason to change โ€” changes caused by the same forces should be localized to a single component.

If a business rule change forces modifications across five different JARs, you have five JARs to rebuild, test, and redeploy. That's expensive. Group the business rule's classes into one component, and a rule change means one JAR changes.

CCP is the dominant principle for applications (as opposed to reusable libraries). Applications care more about ease of maintenance than reusability.

CRP: Common Reuse Principleโ€‹

"Don't force users of a component to depend on things they don't need."

Don't put classes together if they're not used together. When one class in a component changes, the whole component must be rereleased โ€” and everyone who depends on that component must update, even if they use none of the changed classes.

CRP says: split components at natural usage seams, so that users only depend on what they actually use.

The Tension Triangleโ€‹

These three principles are in tension with each other:

REP
(group for reuse)
/ \
/ \
/ \
CRP --------- CCP
(don't over-bundle) (group for change)
  • REP + CCP (ignoring CRP): Too many unnecessary classes in components; consumers drag in things they don't use
  • REP + CRP (ignoring CCP): Changes ripple across many components; maintenance is painful
  • CCP + CRP (ignoring REP): Components are too fine-grained; hard to reuse as units

The right balance depends on your project's stage:

  • Early project: CCP dominates โ€” ease of development and change matters most
  • Mature library: REP + CRP dominate โ€” reusability and clean dependency contracts matter

๐Ÿ”ฌ Senior Deep Diveโ€‹

CCP in Practice: Feature Packaging vs Layer Packagingโ€‹

CCP directly argues against package by layer (all controllers together, all services together, all repositories together) in favor of package by feature or package by component.

// Package by layer โ€” CCP violation: a feature change touches all layers
com.example.controllers.OrderController
com.example.services.OrderService
com.example.repositories.OrderRepository

// Package by component/feature โ€” CCP compliant: feature change is local
com.example.orders.OrderController
com.example.orders.OrderService
com.example.orders.OrderRepository

When Order rules change, the second structure localizes the change to com.example.orders. In the first structure, you touch three packages, potentially affecting other features that share those layers.

REP Applied to Spring Startersโ€‹

Spring Boot starters are a perfect application of REP:

  • spring-boot-starter-web โ€” Spring MVC, Jackson, Tomcat. You use these together.
  • spring-boot-starter-data-jpa โ€” Spring Data, Hibernate, JDBC. You use these together.

Each starter is released as a unit. Upgrading spring-boot-starter-web doesn't force you to change your JPA configuration. The starters are designed so their contained classes are naturally reused together.

When building your own internal libraries, apply REP the same way: release things together that are naturally used together.

CRP and Maven Dependency Analysisโ€‹

CRP violations manifest as unused transitive dependencies. Maven's dependency:analyze goal reveals them:

mvn dependency:analyze

# Output example:
# [WARNING] Used undeclared dependencies:
# com.example:some-util:jar:1.0.0
# [WARNING] Unused declared dependencies:
# com.example:kitchen-sink-library:jar:3.2.0

kitchen-sink-library is a CRP violation: it packages classes you don't use, but you're forced to version-track it anyway. Extract only what you need into a focused dependency.

Component Cohesion and Microservicesโ€‹

The same principles apply at the service level:

  • REP: Services that are always deployed together should perhaps be one service (or share a deployment pipeline)
  • CCP: Business rules that change together for the same business reasons should be in the same service (the basis for bounded contexts in DDD)
  • CRP: Don't put in one service what different consumers need separately โ€” they'll step on each other's deployments

The tension triangle helps explain why "one microservice per database table" (all CRP, no CCP) produces systems where every business change requires coordinating five service teams.


Summaryโ€‹

PrincipleForce"Don't..."
REPReuse granule = release granuleDon't release things together that aren't used together
CCPChanges cluster in one componentDon't spread one reason to change across many components
CRPDon't drag in unused classesDon't put things in the same component that users need to take separately
TensionAll three conflict; balance depends on project maturityEarly projects: CCP first; libraries: REP+CRP first