Component coupling

Last time, component aggregation reflected the selection criteria for “basic elements” within components. Component coupling, introduced in Chapter 14, refers to the relationships between components, some of which are good and some of which are bad. The principles we will see clarify what is a good dependency standard.

Key Points of this Chapter

  1. Acyclic Dependencies Principle
  2. SDP Stable Dependencies Principle
  3. SAP (Stable Abstractions Principle)

Dependence is trust

A dependency is really a relationship of trust that I summed up. Why do people believe in the fact that when a lot of people smarter than you are starting something new — yes, I’m talking about blockchain — it’s the future. As FAR as I am concerned, every smart person involved in the project is building a new dependency relationship. The more the dependency is, the more stable the underlying building will be. Stability is the foundation of trust, and more talents will be attracted to participate in the project and scale effect will be formed. Why do so many people dare to use Alipay and wechat to pay? This sense of trust not only comes from the dachang brand behind, but also from a wide range of user groups. Every time a user makes a secure payment, it reinforces trust.

Block chain, the so-called cost reduction and efficiency, to build a perfect credit investigation system, is the same. One immutable transaction builds no trust, but a thousand times, a million times, so that all transactions in a person’s life are immutable and recorded, and a person’s reputation system is established. When all transaction data of an industry, or even a country, is recorded, the credit investigation system is automatically perfected.

I could go on and on and make it easier to understand in software development. I have seen two different teams build two microservices that have dependencies, and although the services could easily communicate via APIS, they chose to build highly available message queues between the two services because of distrust of the availability of each other’s services. One service publishes data messages and the other subscribes. This seemingly well-decoupled solution is actually a compromise. Distrust leads to no reliance, and no reliance leads to no trust at all. It’s a vicious circle.

In addition to the REP (Reuse publishing Equivalence principle) mentioned last time in the component aggregation principle, publishing of components is also similar to a publish and subscribe model. This pattern transforms tightly coupled source-level dependencies into dependencies on version numbers and release documents. The publishing team can continue development in its own private repository, while the subscription team can decide whether to upgrade or not. This essentially reverses the dependence on code to the dependence on release. The published output is stable and therefore trustworthy, so it can be safely placed in your own code.

In today’s software development process, the reference to third-party components has become commonplace and indispensable, which is the original purpose of software reuse. Developing even large software systems involves relying on multiple components, and these relationships can sometimes become complex and cumbersome to manage. For example, in Java projects, you occasionally deal with multiple version conflicts of a component. This problem is caused by dependency management tools such as Maven allowing transitive depenency, where a common solution is to exclude a version from the components that depend on it. Of all the problems with dependency, the most unacceptable is circular dependency.

ADP independent ring principle

There should be no loops in component dependency diagrams


Not rely on the ring

The problem with cyclic dependencies between components is that a change in any component necessarily results in a change in the other components. Imagine a scenario where there are no dependencies between components, where each component can change independently without affecting the others. Imagine a scenario with only one-way dependencies, where changes to the dependent component inevitably affect the dependent component, so we would be careful to minimize the frequency of such component releases. At this point, the dependent party is free to change. In a bidirectional (cyclic) dependency scenario, the impact of a change on either side is almost equal to the kinetic energy of a particle cyclotron, making it almost impossible to get a stable usable version of any component.

We know that there should be no dependencies between classes when coding, because cyclic dependencies often cause classloaders to fall into an endless loop of loading, which is the chicken-and-egg equivalent of the problem. However, cyclic dependencies can be eliminated, and this is where DIP (dependency inversion) comes in handy.

The DIP principle tells us that there are two ways to eliminate cyclic dependencies in components.

The first is to declare an interface within a component that generates cyclic dependencies and reverse the component it depends on into its own interface. This way, not only does it break the loop dependency, but it also avoids generating new components.

The second is to generate a new component that both interdependent parties point to. The adapter pattern is a good example. When is a new component generated? This problem needs to be solved using the component aggregation principle [1] mentioned last time, which is not repeated here.

SDP stability dependence principle

Dependencies must point in a more stable direction


Stable rely on

Stability is relative to instability. Instability is because components are always changing. If root cause analysis is used, frequent changes will soon rise to the height of fickle PM. It must be made clear, however, that demand will always change. A software architecture that does not respond quickly to changing requirements is a backwater, and there is no need to enforce any design principles.

Since “outwardly seek xuan” cannot get, so “seek oneself” somehow is also a way. Uncle Bob says stability comes from having enough dependence. You first put aside the black lines on your face and the broken thoughts in your heart: the purpose of my investigation of “what is stability” is to find the direction of stability and then rely on it. Now you tell me that if you rely on enough, you will be stable? Which begs the question, which came first, the chicken or the egg?

I, too, was shocked and writhed by the idea. Where does Uncle Bob get the confidence to make this point? Just calm down a little bit. Let’s go through his thoughts. In illustrating the ADP principle above, Uncle Bob draws a somewhat unfortunate conclusion from the elimination of cyclic dependencies: component schematics cannot be designed from the top down. The reason is that it must evolve and expand as the software system changes. Although it is commonly thought that component grouping rules at project team granularity result in component dependency structures, in fact, the component dependency structure diagram is not intended to describe application functionality, but is more like a map of the buildability and maintainability of the application. Combined with the dependency that I mentioned earlier, it is not difficult to find that stability is gradually precipitated out of the process of software system change, components are constantly disassembled, dependencies are constantly decomposed, the most dependent components will slowly emerge.

How do you define how stable a component is? Since the index of dependence is used, it is very convenient to construct an unstable function:

Fan-in refers to the number of classes externally pointing to a component, whereas fan-out refers to the number of classes internally pointing to an external component. Using the knowledge of graphs, fan-in is the input degree of nodes, and Fan-in is the output degree of nodes. When I=0, it means that the component is most stable, because there is only in degree but no out degree; When I=1, the component is most unstable, and there is only an out degree but no in degree.

SAP stable abstraction principle

The abstraction of a component should be consistent with its stability


The stable abstractions

There are certain parts of a software system that should not change very often, such as domains and subdomains in the DDD methodology. This part should be placed in a stable component that other components depend on. But that raises a tricky question, because if that part of the equation changes, the scale of the impact could be huge.

How to do? The OCP principle shines, now that we know that we are closed to modification and open to extension, which is also a means of embracing change. So there is a wonderful connection between stability and abstraction, and this connection requires that stable components also be abstract, not easy to modify but easy to extend.

SDP and SAP together represent a component level interpretation of DIP. SDP tells us to rely in a stable direction, while SAP tells us that stable direction implies abstract requirements. So dependencies should also be in the direction of abstraction.

How do you measure the abstraction of components? The ratio of abstract classes to interfaces is a good indicator:



Where, A is the degree of abstraction function. Na is the number of abstract classes and interfaces in the component, and Nc is the number of all classes in the component. whenA=0, indicates that the component has the lowest abstraction degree and no abstract class.A=1, indicating that the component is the most abstract because it is filled with abstract classes or interfaces.

Since dependencies should be in the direction of stability and abstraction, these two constraints require the stability and abstraction of the component to work together. The optimal position of the net force is on the line with slope negative 1 in the figure above.

Note that the coordinates (0, 0) indicate that the component is non-abstract but stable. Stable means hard to modify, and not abstract enough to extend. This is often the source of unsustainable maintenance of software systems. That’s why it’s called the pain zone.

Coordinates (1, 1) indicate that the component is abstract but unstable. Unstable means that it depends only on other components but is not dependent on any component, so abstraction in this case is usually useless. The most typical example is some abstract class lying in the corner of the code, no one will touch It. It is supposed to be easy to expand later, but we know It well: YAGNI (You Ain’t Gonna Need It). That’s why it’s called the dead zone.

summary

Coupling between components should be governed by the principles described above, which are what a good architecture should look like. At the same time, we also answer the deeper reasons why top-down design is unreliable.

In my opinion, component coupling and aggregation are not a one-size-fits-all relationship. SAP principles also guide the level of abstraction that should exist within each component, reinforcing the idea that the component structure diagram must evolve.

We have talked so much about the basic building blocks [2] and the components. It is like having a brick and cutting it into individual rooms. How to arrange these rooms to form a high-rise building will be the topic for next time. Want to know, what is software architecture? Listen to the breakdown next time.


In the 2018-11-04


  1. Guide to architecture cleanliness (2) Component aggregation ↩

  2. Introduction to clean architecture (I) Programming paradigm ↩