Loose coupling and high cohesion are two fundamental principles in software design that are often discussed but it’s difficult. After many failures, I believe I have found the key to solving the above problem: clearly defining the responsibilities and boundaries of each component. In this article, I will explain why this is a crucial step and provide practical guidance on how to achieve it.
1. What’re Coupling and Cohesion?
Cohesion is the degree to which the elements inside a module belong together.
Coupling is the degree of interdependence between software modules.
Although the above two definitions are talking about modules, in my experience it ranges from system architecture, to components to classes. I have mentioned its benefits when applied in system architecture in the article The Importance of Modularity and DDD in System Architecture and DDD is the tool that helps us do it.
2. Identifying Responsibilities and Boundaries
To create a loosely coupled and highly cohesive system, it’s critical to identify the responsibilities and boundaries of each module and component. Here’s how to approach this at different levels:
2.1. Applying Responsibility and Boundaries at the Domain Level
When designing at the System Architecture level, a domain-based structure is often the most effective approach, especially for large systems with multiple business domains. Domain-Driven Design (DDD) is particularly valuable here, as it encourages breaking down a complex system into smaller, independent domains with clearly defined responsibilities. This reduces interdependencies and keeps each domain focused on its unique purpose, which naturally leads to a more cohesive and loosely coupled system.
2.2. Single Responsibility
The Single Responsibility Principle is a guiding force behind well-defined modules. Each component, whether it’s a module, class, or function, should have one and only one responsibility. This clear delineation not only ensures that each module is cohesive but also reduces the chance of introducing unintended side effects when making changes. In practice, this means every module should serve a distinct purpose without overlapping with others.
2.3 Interface Dependency
To maintain loose coupling, we need to be mindful of interface dependency. By designing interactions between components around well-defined interfaces, we prevent tightly coupling our system to specific implementations. This is particularly important when working across domains or when modules need to evolve independently. Through dependency inversion and interface-driven design, components can collaborate without being tightly coupled, enhancing the system’s modularity.
3. Who will identify responsibilities and boundaries?
Responsibility definition isn’t a one-person job; it involves collaboration across roles to ensure alignment at various levels:
- Solution Architect: For the system level, the Solution Architect defines boundaries across domains and ensures alignment with business goals.
- Tech Lead & Developers: At the domain and component level, Tech Leads and Developers take responsibility for identifying boundaries within each domain. They focus on the practical implementation of modularization and are instrumental in maintaining a cohesive design.
- Developers: At the level of modules, packages, and classes, individual developers ensure that each element serves its single responsibility, maintaining both loose coupling and high cohesion in the codebase. This layered approach ensures that boundaries and responsibilities are clearly defined and maintained at every level, making it easier to achieve a modular, flexible, and scalable system.
Note: The image above shows responsibilities at each level, responsibilities according to each level. Solution Architecture for system level, Tech Lead and Developer for the domain level, Developer for components-modules, packages, classes.
4. Conclusion
Everything in software architecture is a trade-off.
— Mark Richards and Neal Ford - Fundamentals of Software Architecture: An Engineering Approach
Identifying responsibilities and boundaries within a system requires significant time, effort, and collaboration, and there’s always a risk of failure. However, the rewards for successfully implementing these principles are substantial. A system with well-defined boundaries and responsibilities is easier to maintain, more resilient to change, and allows for a level of flexibility that’s essential in complex, evolving systems.
In short, while defining responsibilities and boundaries may be challenging, the investment is well worth it. A loosely coupled and highly cohesive system not only supports current needs but also prepares you for future growth and adaptability. By taking the time to structure your system around these principles, you’re setting the stage for long-term success.
Reference links
- Cohesion and Coupling: the difference
- LOW COUPLING VÀ HIGH COHESION LÀ GÌ?
- How Cohesion and Coupling Correlate
- Difference Between Cohesion and Coupling
Thank you for reading this far.