This post is part of The Software Architecture Chronicles, a series of posts about Software Architecture. In them, I write about what I’ve learned on Software Architecture, how I think of it, and how I use that knowledge. The contents of this post might make more sense if you read the previous posts in this series.
The architecture of a system is the high-level view of that system, the big picture, the system design in broad strokes. The architectural decisions are the structural decisions in the system, the ones that affect the whole code base, the ones that define where everything else will be built on top of.
Amongst other things, architecture is responsible for deciding the systems:
- Relationships between components
- Principles guiding the design and evolution of components and relationships
In other words, these are the design decisions that are more difficult to change as the system evolves, it’s the foundations that give support to feature development.
Some of the projects I worked on, had a completely random structure, they did not reflect neither the architecture nor the domain. If I would ask “where should I put this value object?“, I would get an answer like “put it somewhere in the src folder“. If I would ask “where is the Service that does this logic?“, I would receive a “make a search with your IDE“. This means there was no reasoning whatsoever to the organisation of the project.
This is a big problem because it means there is no package modularity, the high-level code relationships and flow does not have a logic structure that can be followed, it leads to highly coupled and low cohesive modules, actually it might mean that there are no modules at all and the code that should belong in a module is scattered throughout the code base. This is the same as saying that the code base is Spaghetti Code, or maybe Spaghetti Architecture!
A Maintainable codebase
Having a maintainable code base means we can apply maximum conceptual changes with minimum code changes. In other words, when we need to apply changes to one code unit, we should need to apply the least changes to other code units as possible.
This gives us some very important advantages:
- Changes are simpler because they impact on less code;
- Changes are faster because there will be less code to change;
- Likelihood of bugs is lower because there is less code changed;
Encapsulation, Low coupling and High cohesion are the core principles that provide the code isolation that makes it possible to have a maintainable code base.
It’s the process of concealing the internal representation and implementation of a class.
That is, it hides the implementation such that the internal structure of a class is free to change without affecting the implementation of the classes that use this particular class.
Coupling refers to the relationship of a code unit with another code unit. A module is said to be highly coupled with another module if changes to it will result in changes to the other module. And a module is said to be loosely coupled if a module is independent of any other modules. This can be achieved by having a stable interface that effectively hides the implementation from other modules.
Benefits of low coupling
- maintainability – changes are confined in a single module
- testability – modules involved in unit testing can be limited to a minimum
- readability – classes that need to be analysed are kept at a minimum
Cohesion refers to the measure of how strongly-related the functions of a module are. Low cohesion refers to modules that have different unrelated responsibilities. High cohesion refers to modules that have functions that are similar in many aspects.
The benefits of high cohesion
Readability – (closely) related functions are contained in a single module
Maintainability – debugging tends to be contained in a single module
Reusability – classes that have concentrated functionalities are not polluted with useless functions
Impact on Structure
The previous principles are usually associated with classes, however, they are also valid for groups of classes. Groups of classes are called Packages in a generic way, but in a more concrete way we can call them Modules if they have a purely functional goal (ie. ORM) and Components if they have a Domain goal (ie. AccountManagement). This is the same definitions as Bass, Clements and Kazman explain in their book Software Architecture in Practice.
We can, and should, have packages with low coupling and high cohesion because that will allow us to:
Change one package without impacting other packages, which helps in having fewer bugs;
Change one package without needing to change other packages, which helps have a higher pace of delivery;
Have teams specialised in specific Packages, which makes for faster, less error prone and better-designed changes;
Teams can develop with fewer dependencies and conflicts between them, increasing productivity.
Have a better reasoning over the Components relations, which will allow us to better model the application as a whole and therefore deliver a higher quality system.
I feel that structuring our projects in a way that reflects both the architecture and the domain will add a lot to the maintainability of our code base. I actually dare to go as far as stating that it is the only way (when we deal with medium to large enterprise applications).
In a well-organised codebase, there will be only one possible location for a specific code unit to live. We might not even know what that location is, but there will be only one logical path leading us to it.
Definition of Package
By grouping classes into packages we can reason about the design at a higher level of abstraction. The goal is to partition the classes in your application according to some criteria, and then allocate those partitions to packages. The relationships between those packages expresses the high level organization of the application.
Robert C. Martin 1996, Granularity pp. 3
We need to aim at defining packages of conceptually related code. Those packages are important because they define conceptually related code units that should be isolated from other packages, and the relations between several packages.
This is done in order to:
- Understand the relations between code units
- Maintain the logical relationships between code units
- Have code packages that have low coupling and high cohesion
- Refactor code packages, having no/minimal impact on the application
- Swap code packages implementation, having no/minimal impact on the application
We can accomplish this by, amongst other principles, following the packaging principles published by Robert C. Martin back in 1996 and 1997, mainly the CCP (Common Closure Principle), the CRP (Common Reuse Principle), and the SDP (Stable Dependencies Principle).
Packaging Principles, by Robert C. Martin:
- Package Cohesion Principles
- Package Coupling Principles
To properly use the SDP, we should define conceptual units of code (Components) and layers of Components, so that we know what Components should know about (depend on) other Components.
However, if those Components boundaries are not clear, we will end up mixing code units that should be kept ignorant of each other, coupling them, turning them into spaghetti code and eventually in unmaintainable code.
In order to make those boundaries explicit, we need to group together the classes that are conceptually related into packages, the same way we group conceptually related methods into classes. At a package level, we can only do it using folders with conceptual names that mean something in the domain (ie. UserManagement, Orders, Payments, …). Only in the last level of the structure, in its leafs, we can separate classes per functional role (ie. Entity, Factory, Repository, …), if needed.
To design well decoupled components, it helps to reflect about:
“if I would want to remove this business concept, by deleting its component root folder would all of the business concept code be removed and would the remaining application not break?”
If the answer is yes, then we have a well decoupled component.
For example, in a Command Bus architecture the command and the handler do not work one without the other, they are conceptually and functionally bound together, so if we would need to remove that logic, we would remove them both, and if they are in the same place, we just remove one folder (the problem we are trying to solve is not about deleting code, it’s about having decoupled and cohesive code, but it helps to think in these terms). So to follow the CCP and the CRP, a command should be in the same folder as its handler.
Any code unit should only have one logical location to exist, obvious to even a new and junior developer in the project. That will avoid inconsistencies, lost code, duplicated code and developer frustration. If we need to search for code because we don’t know where it is supposed to be, and/or if it is difficult to understand what code is related to the code we are working on… then we have a bad project structure, or worse, a bad architecture.
Screaming Architecture was thought of by Robert C. Martin, and it basically states that the project should tell us very clearly what the system is about: its main domain. Naturally, then the first folders to appear in the source folder should be related to domain concepts, the top level bounded contexts (ie. patients, doctors, appointments, …). They should not be about the tools used by the system (ie. Doctrine, MySQL, Symfony, Redis, …), nor about the functional pieces of the system (ie. repositories, views, controllers, …), nor about delivery mechanisms (http, console, …).
Your architectures should tell readers about the system, not about the frameworks you used in your system. If you are building a health-care system, then when new programmers look at the source repository, their first impression should be: “Oh, this is a heath-care system”.
Robert C. Martin 2011, Screaming Architecture
This is, in fact, a simpler way of thinking of the packaging principles he had published 15 years before, and which I enunciated above. This packaging style is also known as “Package by feature”.
2008 – Johannes Brodwall – Package by feature
2012 -Johannes Brodwall – How Changing Java Package Names Transformed my System Architecture
2012 – sivaprasadreddy.k – Is package by feature approach good?
2013 – Lahlali Issam – Lessons to Learn from the Hibernate Core Implementation
2013 – Manu Pk – Package your classes by Feature and not by Layers
2015 – Simon Brown – Package by component and architecturally-aligned testing
2015 – César Ferreira – Package by features, not layers
2017* – javapractices.com – Package by feature, not layer
1996 – Robert C. Martin – Granularity
1997 – Robert C. Martin – Stability
2009 – 500internalservererror – What do low coupling and high cohesion mean? What does the principle of encapsulation mean?
2011 – Robert C. Martin – Screaming Architecture