Components are the smallest entities that can be implemented in a system (MARTIN, 2017). These components can be bundled into an executable, a single file, or independently implemented as separate dynamically loaded plugins. Using either way of implementation, as long as a component has been well designed, it has the ability to be independently developed and implemented.
Taking a brief look back at the history of components, it is noted that in the 1980s libraries were loaded into programs through linkers, which were nothing more than compileable and reloadable segments. Nonetheless, with the technological development, the increasing complexity of the programs and the use of high-level languages, the compilation time of these modules was very high and was about to become unfeasible.
However, in the late 1980s the discs started to slow down and get faster, greatly reducing this compilation time. In the mid-1990s, the time spent on calls dropped significantly, in some cases from 1 hour to a few seconds. Computers and devices became so fast that it became possible to make the connection at the time of loading, being able to connect files or shared libraries in seconds and from there run the program. This is how the architecture and component plugin came about (MARTIN, 2017).
The components have some principles, next we will see about the cohesion and coupling principles.
The principles of cohesion of components that will be presented are the following:
REP - Reuse/Release Equivalence Principle
CCP - Common Closure Principle
CRP - Common Reuse Principle
Next, each of the three will be analyzed in detail.
This principle says that in order to reuse software components, they must be tracked by a process and given release numbers. Without these numbers it would not be possible to guarantee the compatibility of the components with each other. Another importance of this number is for developers to know when new releases will be released and what changes they will include. Therefore, notifications must be generated and documents must be produced so that the user can make the decision to migrate to the new one or continue with the old one.
Analyzing this principle through the software architecture, it can be concluded that the modules and classes formed in a component must be part of a cohesive group, so that the component is not a mixture of random classes and modules, but rather has a theme or purpose that all modules share.
MARTIN (2017) expresses that the classes and modules grouped in a component must be able to have a release together, because sharing the same version, the same release tracking and being in the same documentation facilitates the understanding of both the author and users.
The Common Closure Principle states that classes that change for the same reasons and at the same times should be gathered into components, and classes that change at different times and for different reasons should be separated into different components (MARTIN, 2017).
This principle is a reformulation of the Single Responsibility Principle (SRP) seen in the SOLID article by applying it to components. Just as SRP states that a class shouldn't have much of a reason to change, CCP states that a component shouldn't have much of a reason to change.
When it is necessary to make a change to a code, it is ideal that these changes occur in only one component, not in several. Thus, it is necessary to re-implement only the modified component, without changing the others. Therefore, the CCP claims that all classes that are highly likely to change for the same reasons are gathered in the same place. If two or more classes are tightly linked and always change together, they must belong to the same component, in order to reduce the amount of work to redeploy, revalidate and release the software.
The CCP also relates to one more SOLID principle, the Open/Closed Principle (OCP) - also seen in the article about SOLID - with the term “closed” being used in both with the same sense. The OCP states that classes should be closed for modification, but open for extensions. Because it is not possible to have complete closure, classes are designed so that they are closed to most types of expected or observed changes. The CCP develops this definition by bringing together the closed classes for the same types of changes in the same component, so that when a modification in the requirements occurs, it has a great chance of being limited to a reduced number of components.
This principle attests that classes and modules that tend to be reused together belong to the same component (MARTIN, 2017). Due to the fact that classes are rarely reused in isolation, it is expected that several classes that are part of the reusable abstraction will collaborate with each other. Therefore, according to the CRP, these classes must belong to the same component, having several dependencies on each other.
In addition to indicating which classes must be gathered in a component, the CRP points out which classes must not be gathered in a component. When there is a dependency on a component, it is necessary to make sure that this dependency applies to all classes of a component, and that the classes of a component are so interconnected that it is impossible to depend on one and not depend on all of them. If this is not applied, you will need to redeploy more components than necessary.
Demonstrating in a practical way, it is considered a component A and a component B. B makes use of A, creating a dependency between them. Even if B only needs a class of A, whenever A is modified B will need corresponding modifications. So if A makes a modification to a class that B doesn't use, B still needs to be modified, recompiled, revalidated, and re-implemented. These modifications in B are unnecessary, as the changed class does not affect its functioning in any way, but due to its coupling in A it is necessary to do this unnecessary work. So classes that don't have a strong link shouldn't be in the same component.
As seen throughout the previous topics, the three principles differ greatly from each other. REP and CCP tend to increase the amount of components and CRP tends to decrease them. On the interaction of the principles with each other, MARTIN (2017) presents a tension diagram, in which its edges describe the cost of abandoning the opposite vertex principle.
The software architect needs to find a position in the tension triangle in the figure above in order to satisfy the current demands of development and to know that these demands will change over time. When focusing on REP and CRP, it is clear that many components are impacted by making simple changes. By focusing more on CCP and REP, it will generate a lot of unnecessary releases. MARTIN (2017) states that generally projects tend to start on the right side of the triangle and, as they mature, move to the left side.
Regarding the coupling of components, there are also three principles that address the relationships between components. The principles are as follows:
ADP - Acyclic Dependencies Principle
SDP - Stable-Dependency Principle
SAP - Stable-Abstractions Principle
Next, each of them will be analyzed.
One of the biggest mistakes of developers with little experience is having several people working on the same code. Because of this lack of division of a system, it is very common that as one problem is solved by one developer, another problem arises on the part of someone else's responsibility.
To prevent this from happening, it is necessary to partition the development environment into releaseable components (MARTIN, 2017). In this way, each component becomes a work environment under the responsibility of a developer or a team. When making this component work, a version is generated to be used by the other teams, after that, the developers go back to work in their private areas until they have another version. The other teams can decide whether to start using the new release or continue with the old one. In this way, changes to a component do not need to have an immediate effect on the other teams, it is the responsibility of each one to decide when to adapt their components for the new release.
The dependency structure of the components of a system that follows this principle must be a directed graph, where the nodes are the components, and the directed edges are the dependency relations.
When analyzing the dependency diagram above (adapted from MARTIN (2017)), it is noted that regardless of which component starts, it is impossible to go back to the initial component. This structure is a directed acyclic graph.
If there is a new requirement that changes the behavior of a class, it is possible that dependency cycles are generated, causing the problems mentioned at the beginning of this section to appear. If there is a cycle in the system, it must be broken in order to have a directed acyclic graph again.
There are two ways to break these cycles (MARTIN, 2017). The first is by applying the Dependency Inversion Principle, in which an interface can be created with the methods that a particular class needs and then inherit it in another class, so that the dependencies are inverted and thus break the cycle. The second way is to create a component that both classes that make up the cycle depend on, and move the essential classes in both into this new component. As a result, the classes depend on this new component and the cycle is broken.
The Principle of Stable Dependencies states that projects cannot be completely static, that some volatility is necessary to sustain the project. By using the Common Closure Principle, components are created that are sensitive to some changes, but not to others. Some of these components are created to be volatile, so they are expected to change (MARTIN, 2017).
Using the Principle of Stable Dependencies ensures that a module that is difficult to change does not depend on a volatile component, as this would make the volatile component difficult to change. The stability of something is related to the work required to make a change. Applied to software, there are several factors that make a component difficult to change, such as size, clarity, and complexity. A sure way to make a component difficult to change is to make other components depend on it, as this requires a lot of work to reconcile changes with all of its dependents (MARTIN, 2017).
However, not all components must be stable, as this would leave the system immutable. It is necessary to design the component structure to have both stable and unstable components. The figure (adapted from MARTIN (2017)) below shows that unstable components depend on a stable component.
This principle establishes a relationship between stability and abstraction. It states that a stable component must also be abstract so that stability does not prevent its extension, and that an unstable component must be concrete, since its instability makes its concrete code easily modified. Consequently, for a component to be stable it must have abstract interfaces and classes so that it can be extended (MARTIN, 2017).
After analyzing the principles of component cohesion, it is clear that there is a complex variety of cohesion, as balancing the opposing forces involved in reuse and development is not a trivial task. It is necessary to know that this balance is dynamic and what is suitable for the system today may not be ideal in the future.
By looking at coupling principles, we gain insight into how they also influence teamwork. The code is live and during development there is the insertion of dependencies by team members, and these principles guide us to have an integration without so many future problems.
When creating or using components, you should not only think about solving the technical problem, but how your solution will behave along with the components that already exist in the system. There are several factors to consider when creating or using a new component, so you must first analyze its purpose and the best way to implement it in the solution.
- MARTIN, Robert C. Clean Architecture: A Craftsman’s Guide to Software Structure and Design. 1st. ed. USA: Prentice Hall Press, 2017. ISBN 0134494164.