Skip to content

Instantly share code, notes, and snippets.

@wataruoguchi
Created June 2, 2021 05:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wataruoguchi/bd21fe1ccf319dc54a62aa2f9f3b4831 to your computer and use it in GitHub Desktop.
Save wataruoguchi/bd21fe1ccf319dc54a62aa2f9f3b4831 to your computer and use it in GitHub Desktop.
Clean Architecture - PART 4 - Component Principle #bookclub

Clean Architecture

PART IV - Component Principle

  • SOLID principle How to arrange the bricks into walls and rooms.
  • Component principle How to arrange the rooms into buildings.

Chapter 12 - Component

Components, jars, gems, packages, DLLs, .jar, .dll, .exe ... well designed components always retain the ability to be independently deployable and deployable.

A Brief History of Components

It used to take a long time to compile the code. Programmers made the code into smaller components and compiled them individually to save up the compile time.

  • Fig 12.1 Early memory layout

Application and Function Library are mapped in difference memory spaces.

  • Fig 12.2 Splitting the application into two address segments

When the application grew, programmers needed to split the applications into two address segments.

Relocatability

The bigger the program is, the more space to allocate needed. The solution was "relocatable binaries". The loader was told which part of loaded data had to be altered to be loaded at the selected address. ...then the linking loader was born.

Linkers

The linking loader - In the late 1960s and early 1970s, programs got a lot bigger. It worked well when small programs were being linked with small libraries. But this approach was too slow, because the linking loader had to scan through tons of library to resolve. Then the rise of linker - the output of this was a linked relocatable that a relocating loader could load very quickly.

Murphy's law of program size:

Program will grow to fill all available compile and link time.

Conclusion

These dynamically linked files, which can be plugged together at runtime, are the software components of our architectures. It has taken 50 years, but we have arrived at a place where component plugin architecture can be the casual default as opposed to the herculean effort it once was.

Chapter 13 - Component Cohesion

Three principles of Component Cohesion

  • REP: The Reuse / Release Equivalence Principle
  • CCP: The Common Closure Principle
  • CRP: The Common Reuse Principle

The Reuse/Release Equivalence Principle (REP)

The granule of reuse is the granule of release.

From a software design and architecture point of view,this principle means that the classes and modules that are formed into a component must belong to a cohesive group.

I used to think mono-repo was useful only for packages that are public. However, this principle changed my perspective. It works for private projects as well. Each repository needs to be grouped in "make sense", then gets managed the release/reuse.

The Common Closure Principle (CCP)

Gather into components those classes that change for the same reasons and at the same times. Separate into different components those classes that change at different times and for different reasons.

It's close to OCP (Open Closed Principle). 100% closure is not attainable, closure must be strategic.

Similarity with SRP
  • SRP (Single Responsibility Principle) for components.
    • "A class should not contain multiple reasons to change."
  • CCP
    • "A component should not have multiple reasons to change."

Both principles can be summarized by the following sound bite:

Gather together those things that change at the same times and for the same reasons. Separate those things that change at different times or for different reasons.

The Common Reuse Principle (CRP)

Don't force users of a component to depend on things they don't need.

What classes and modules should be belonging to the component.

Reusable classes collaborate with other classes that are part of the reusable abstraction.

It seems like the abstraction level is the key. Any example?

classes that are not tightly bound to each other should not be in the same component.

^ e.g., Data model objects likely not be in the same component.

Relation to ISP
  • ISP (Interface Segregation Principle)
    • not to depend on classes that have methods we don't use.
  • CRP
    • not to depend on components that have classes we don't use.

Don't depend on things you don't need.

The Tension Diagram for Component Cohesion

  • REP and CCP are inclusive principles. -> driving components to be bigger.

  • CRP is exclusive principle. -> driving components to be smaller.

  • Fig 13.1 Cohesion principles tension diagram

The diagram describes:

1. Nodes:
  • REP: Group for reusers
  • CCP: Group for maintenance
  • CRP: Split to avoid unneeded releases
2. Relations:
  • If you only focus on REP and CRP ... too many components are impacted when simple changes are made.
  • If you only focus on CCP and REP ... too many unneeded release to be generated.
  • If you only focus on CCP and CRP ... components are hard to reuse.

Find what's the current problem your team is facing, and take the best option.

I personally found this is interesting. What I care last a few years is the struggle of maintainability. Hard to debug, hard to fix. Although that means the product's stage is leaning toward to the left of the diagram. However, when the first developers were making this, what they cared was develop-ability. (But it doesn't mean it's really maintainable, though.) This is a map of buildability and maintainability.

Also, releasing is no longer a big hustle nowadays. Browsers get updated pretty often, and so are OSes.

Conclusion

In choosing the classes to group together into components, we must consider the opposing forces involved in reusability and develop-ability.

Chapter 14 - Component Coupling

This chapter will talk about the relationship between components.

The Acyclic Dependencies Principle (ADP)

Allow no cycles in the component dependency graph.

"morning after syndrome" -> You built something and go home. Then on the next day it doesn't work because someone changed.

To mitigate the issue, two solutions are made:

  • "The weekly build"
  • Acyclic Dependencies Principle (ADP)
The Weekly Build

All developers work individually until Friday, then merge on Friday.

  • You can work freely most of the week.
  • You need to deal with all debt on Friday. (And deploying on Friday!?)

This is pretty bad.

Eliminating Dependency Cycles

The components become units of work that can be the responsibility of a single developer, or a team of developers.

Then when it's done they release. The other team, or developers takes the released version into their dev-env. Integration happens in small increments.

You need to manage dependency structure of components - DO NOT MAKE CIRCULAR DEPENDENCIES!

  • Fig 14.1 Typical component diagram

With the graph, you'd notice:

  • the structure is a directed graph. The components are nodes, and the dependency relationships are the direct edges.
  • the structure has no cycle. It is a directed acyclic graph (DAG).
The effect of a cycle in the component dependency graph
  • Fig 14.2 A dependency cycle

The User class in Entities use the Permissions class in Authorizer. But the Autorizer is used in Entities.

Circular dependency. This also makes it testing harder.

Breaking the circle

To mitigate the circle problem,

  1. Apply the Dependency Inversion Principle (DIP)
    • Fig 14.3 Inverting the dependency between Entities and Authorizer
  2. Create a new component that both Entities and Authorizer depend on.
    • Fig 14.4 The new component that both Entities and Authorizer depend on
The "JITTERS"

The second solution above creates component structure that is volatile, then the component dependency structure jitters and grows.

Top-Down Design

The component structure cannot be designed from the top down. It is not one of the first things about the system that is designed, but rather evolves as the system grows and changes.

If we tried to design the component dependency structure before we designed any classes, we would likely fail rather badly.

Component structure grows, you can't design in the beginning.

The Stable Dependencies Principle

Depend in the direction of stability.

Any component that we expect to be volatile should not be depended on by a component that is difficult to change.

Stability
  • Fig 14.5 x: a stable component

The component 'x' has lots of incoming dependencies thus not easy to change -> stable.

  • Fig 14.6 y: a very unstable component

The component 'y' has no components relying on it. It depends on other components.

Stability metrics

How can we measure the stability?

  • Fan-in: Incoming dependencies.

  • Fan-out: Outgoing dependencies.

  • Instability: I = Fan-out / (Fan-in + Fan-out)

    • The range [0, 1]
    • 0: Maximally stable
    • 1: Maximally unstable
  • Fig 14.7 Our example

The component Cc has three incoming deps and one outgoing dep. Fan-in = 3, Fan-out = 1, thus I = 1/(3+1) = 1/4.

Not all components should be stable
  • Fig 14.8 An ideal configuration for a system with three components
  • Fig 14.9 SDP (Stable Dependencies Principle) violation
  • Fig 14.10 U within Stable uses C within Flexible
  • Fig 14.11 C implements the interface class US

The flexible component (meant to be volatile by design) has an incoming dependency from a stable component. To mitigate this, we can use the DIP (Dependency Inversion Principle).

Abstract Components

It only contains an interface.

The Stable Abstractions Principle

A component should be as abstract as it is stable.

Where do we put the high-level policy?
  • The software that encapsulates the high-level policies of the system should be placed into stable components.
  • The software that we want to be able to change should be placed into unstable components.
Introducing the Stable Abstractions Principle (SAP)

if a component is to be stable, it should consist of interfaces and abstract classes so that it can be extended. Stable components that are extensible are flexible and do not overly constrain the architecture.

Measuring abstraction

The A metrics to measure the abstractness of a component.

  • Nc: The number of classes in the component.
  • Na: The number of abstract classes in the component.
  • A: Abstractness. A = Na / Ac
    • The range [0, 1]
    • 0: The component has no abstract classes
    • 1: The component has only abstract classes
The main sequence
  • Fig 14.12 The I/A graph

The graph takes Instability and Abstractness.

The Zone of Pain

Highly stable, and concrete.

Examples:

  • Database schemas are volatile, and concrete. Schema update is generally painful.
  • A concrete utility library. It's painful if you need to change the String component.
The Zone of Uselessness

Highly abstract, but no deps.

Just nobody is using it.

Avoiding the zone of execution

Ideally, components should be on the main sequence.

Distance from the main sequence
  • D: Distance. D = |A + I - 1|

    • The range [0, 1]
    • 0: The component is on the main sequence
    • 1: Far away from the main sequence
  • Fig 14.14 Scatterplot of the components

Plot all components into the graph.

  • Fig 14.15 Plot of D for a single component over time

Plot a component into the graph over time

Conclusion

The dependency management metrics described in this chapter measure the conformance of a design to a pattern of dependency and abstraction that I think is a "good" pattern. Experience has shown that certain dependencies are good and others are bad.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment