Graduate Program KB

Clean Architecture

Chapter 14 - Component Coupling

  • This chapter discusses 3 principles dealing with component coupling and how they can be balanced to build maintainable systems

Acyclic Dependencies Principle

  • "Allow no cycles in the component dependency graph"
  • A common issue developers face is trying to constantly integrate their changes on top of others, usually due to working on the same files and causing merge conflicts
  • Essentially this repetitive process can cause the "morning after syndrome", where being able to build a stable version of a project becomes much more difficult
  • Two solutions have evolved to address this issue: "The weekly build" and the Acyclic Dependencies Principle (ADP)

The Weekly Build

  • How it works:
    • Developers ignore each other for the first 4 days of the week, working on private copies without worrying about integration
    • On Friday, they integrate all their changes and build the system
  • The approach allows developers to freely do as they please at the start with the incoming penalty of integrating potentially large changes
  • However, as the project grows, the integration phase becomes less feasible
    • Developers are either forced to work extra on Saturdays or begin earlier
    • Eventually, integration time increases which reduces development time, overall reducing efficiency

Eliminating Dependency Cycles

  • Solution: Partition development environment into releasable components
  • Components become the units of work that a single or team of developers can be responsible for
    • Components can be released with a release number for other developers to use
  • Other teams aren't immediately affected by new components or required to use it, so friction between teams is non-existent or minimal
  • Developers must manage the components dependency structure properly, if there are cycles within it then the "morning after syndrome" can't be avoided
  • Example of component dependency structure in Figure 14.1:
    • Structure is a directed graph
      • Components are nodes
      • Dependency relationships are directed edges
    • The structure has no cycles
      • Impossible to begin at one component and follow the dependencies to wind back up to that component
      • This type of graph is called a directed acyclic graph
    • Consider the Presenters component:
      • If Presenters has an update, View and Main who depend on that component will need to decide when to integrate their work with the new release
      • When testing and building Presenters, they need to only build it with the Interactors and Entities components that it depends on
      • No other components in the system need to be involved, reducing the impact of these changes
      • When the whole system is released, it builds the components bottom up based off the dependencies between them

The Effect of a Cycle in the Component Dependency Graph

  • In Figure 14.2, Entities now depends on Authorizer (User class uses Permissions class from different components)
    • This creates a dependency cycle between Entities, Authorizer and Interactors
    • Essentially, these three components become one LARGE component
      • Releasing Database means building this large component, which now includes the new dependency, Authorizer
      • With more areas of the system affected, developers will begin to experience the same issues from before
  • This level of coupling can cause multiple issues:
    • Building multiple components as a result of more dependencies
    • Difficult to isolate components
    • Difficult to do unit testing and more risks of releasing
    • Build issues increasing with the number of modules and the order to build components is unclear

Breaking the Cycle

  • Two primary mechanisms for breaking the cycle of dependencies:
    • Apply the Dependency Inversion Principle
      • Ex. adding an interface between Entities and Authorizer to invert the dependency between them
    • Create a new component that both Entities and Authorizer depend on
      • Move the classes User and Permissions into the new component
      • This solution of creating new components grows the dependency structure and makes it volatile
        • Important to always monitor for cycles and to always break any cycles that occur

Top-down Design

  • Component structure can't be designed from the top down
  • Component dependency diagrams don't describe the application's function, but the buildability and maintainability of the application
    • This diagram does not exist at the project's beginning, since there is no software to maintain at that point
    • The necessity to manage dependencies grows as more and more modules accumulate
    • Focusing more on SRP and CCP to group classes that are likely to change together
  • This structure also protects frequently changing components from affecting stable components
    • Ex. changes to GUI shouldn't impact business rules or modification of reports to impact high-level policies
  • As the application grows, we become more concerned with reusable elements, thus, CRP begins to influence the composition of components
  • The component dependency structure grows and evolves with the logical design of the system
    • Must be aware of reusable elements otherwise we might create components with dependency cycles

Stable Dependencies Principle

  • "Depend in the direction of stability"
    • Stable components should not depend on volatile components
    • Otherwise, it will be difficult to change the volatile component
  • Volatility is necessary for a maintainable design, it cannot be completely static
    • By applying CCP, we create components sensitive to certain changes but immune to others
    • Some components are designed to be volatile

Stability

  • Stability is measured by the amount of work required to make a change
    • Ex. a coin is not stable just because it can maintain its position on its side, rather its unstable because little work is required to topple it
    • A component is stable if it is hard to make a change
  • A component which is depended upon by lots of components is stable because any changes made to it will require reconciliation with all dependent components
  • In Figure 14.5, component X has 3 reasons to not change as 3 components depend on it
  • In Figure 14.6, component Y is unstable, its changes may come from any of the 3 components it depends on

Stability Metrics

  • Positional stability of a component: Number of dependencies entering and leaving a component
    • Fan-in (incoming): No. of classes from other components that depend on classes within the component
    • Fan-out (outgoing): No. of classes within the component that depend on classes outside the component
    • I (instability): Has the range [0, 1] calculated from Fan-out / (Fan-in + Fan-out).
      • A lower value indicates more stability, with 0 meaning no outward dependencies
      • A higher value indicates high unstability, with 1 meaning no inward dependencies and at least 1 outward dependency
  • In programming languages, dependencies are typically represented by import statements
  • SDP states that the I metric of a component should be larger than the I metric of the components it depends on
    • I metrics should decrease in the direction of dependency
    • Dependant components are more likely to change (more unstable)

Not All Components Should Be Stable

  • All components being maximally stable means a system would be unchangeable
    • Ideally, some components are unstable and others are stable
    • The unstable components should depend on stable components
    • Useful convention in diagrams is to put unstable components above since any visible dependency arrow going upwards shows a clear violation of SDP
  • In situations where a stable components depends on an unstable component, apply DIP
  • In Figure 14 and 14.11:
    • Class u in the Stable component depends on class c in Flexible component
    • Solution: Create another component UServer with an interface class called us which declares all methods u needs to use. c then implements this interface, breaking the dependency of the Stable and Flexible components
    • Dependencies also now flow in the direction of decreasing I metric, with the new component having I = 0
  • Creating abstract components (no executable code, contains only an interface) is common and they're very stable, making them ideal targets for less stable components to depend on

Stable Abstractions Principle

  • "A component should be as abstract as it is stable"
  • High-level policies and business rules within a software system should not be volatile
    • Should be placed in stable components
    • However, the source code representing these policies become hard to change
  • The stable component needs to be flexible enough to withstand change
    • OCP shows functionality can be extended without requiring modification of other classes
  • SAP is a relationship between stability and abstractness
    • Stable components should be abstract such that stability does not prevent it from being extended
    • Unstable components should be concrete since its instability allows the concrete code within it to be easily changed
    • Implies that a stable component should consist of interfaces and abstract classes
  • DIP for components is SAP and SDP combined
    • Dependencies run in the direction of abstraction
    • DIP deals with classes, which are either abstract or not
    • SAP and SDP combined deal with components, which can be partially abstract and partially stable

Measuring Abstraction

  • A metric measures the abstractness of components
    • The ratio of classes and combined interfaces and abstract classes within a component
    • Nc: No. of classes
    • Na: No. of abstract classes and interfaces
    • A: Na / Nc, it ranges from [0, 1] with a higher value indicating more abstractness of the component

The Main Sequence

  • The relationship between stability (x-axis) and abstractness (y-axis) can be depicted on a graph
    • Maximally stable components sit at (0, 1)
    • Maximally unstable components sit at (1, 0)
  • Components have varying degrees of abstraction and stability, so they'll rarely sit at the end points
  • Instead the graph can consist of a locus of points that define reasonable points for the components to reside

The Zone of Pain

  • Components at (0, 0) are concrete and high stable, but it has no flexibility for withstanding change
  • The area around this point is referred to the Zone of Pain
  • Examples:
    • Database schemas are concrete and highly depended on, making updates difficult
    • Utility library such as String, very commonly used it's considered non-volatile

The Zone of Uselessness

  • Components at (1, 1) are abstract and are not depended on, essentially they are useless components
  • Entities within this area are usually unused code or forgotten abstract classes that aren't implemented

Avoiding the Zones of Exclusion

  • Clearly components should avoid either zones around (0, 0) and (1, 1)
  • The locus of points maximally distant from each zone is the connecting line from (0, 1) and (1, 0)
    • Referred to as the Main Sequence
    • Components around this line are not too abstract, not too unstable, not too useless and not too painful
    • Ideally, components sit at either end points or close to them

Distance From the Main Sequence

  • The D metric measures how far a component is away from the Main Sequence
    • Calculated by | A + I - 1 |, using the abstractness and instability metrics
    • The range is [0, 1] where a value of 0 means the component is on the Main Sequence
  • Thus, any component with a D value not near 0 can be re-evaluated
  • Statistical analysis to calculate variance and identify the "best" components for examination is also possible
    • Can also be useful to examine the change in D over releases, ensuring the component dependency tree is consistently managed

Conclusion

  • The dependency management metrics describe a pattern of dependency and abstraction for components shaped by the three principles
    • Acyclic Dependencies Principle: Dependencies shouldn't form cycles, changes are harder to make
    • Stable Dependencies Principle: Less stable modules should depend on more stable modules
    • Stable Abstractions Principle: The more stable a module is, the more abstract it should be to enable flexibility
  • Don't treat metrics as an absolute, but they are generally good guidelines to follow