Skip to content

Instantly share code, notes, and snippets.

@wataruoguchi
Created June 16, 2021 04:40
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/88e27215932eca71919d301bcd3a06ef to your computer and use it in GitHub Desktop.
Save wataruoguchi/88e27215932eca71919d301bcd3a06ef to your computer and use it in GitHub Desktop.
Clean Architecture - PART 5 - Architecture (~Chapter 20) #bookclub

Clean Architecture

PART V - Architecture

Chapter 15 - What Is Architecture?

Software architects are the best programmers, and they continue to take programming tasks, while they also guide the rest of the team toward a design that maximizes productivity. (...) They do this because they cannot do their jobs properly if they are not experiencing the problems that they are creating for the rest of programmers.

Certainly, productivity is what I care, and the reason why I'm learning this.

Development

Developers in a small team work effectively. Architecture might be an impediment during early days of development. This is likely the reason why so many systems lack good architecture. -> This reminds me of Pioneers in On Pioneers, Settlers, Town Planners and Theft..

On the other hand, when the system is developed by multiple teams, it's hard to make progress unless system is divided into well-defined components with reliably stable interface. -> To be Settlers, we need to follow Interface first.

Deployment

A goal of a software architecture, then, should be to make a system that can be easily deployed with a single action. Unfortunately, deployment strategy is seldom considered during initial development. This leads to architectures that may make the system easy to develop, but leave it very difficult to deploy.

The example of the latter is with micro service architecture. It says the problem is managing interconnections. But today, we have no such problems with cloud services such as AWS. Yay!

Operation

Almost any operational difficulty can be resolved by throwing more hardware at the system without drastically impacting the software architecture.

And auto scaling. This will never be a problem.

A good software architecture communicates the operational needs of the system.

Maintenance

Maintenance is the most costly. Digging through the existing software, determine the best place and the best practice to add a feature or to repair is costly. A carefully thought-through architecture vastly mitigates these costs.

Keeping Options Open

All software systems can be decomposed into two major elements: policy and details.

  • Policy: embodies all the business rules and procedures.
  • Details: things that are necessary to enable humans to communicate with the policy, but that do not impact the behavior of the policy at all.

The goal of the architect is to create a shape for the system that recognizes policy as the most essential element of the system while making the details irrelevant to that policy.

You can choose the DB, the web server, the framework later. They are all "Details".

Device Independence

One of the old mistakes was to bind code directly to the IO devices. - punch cards

It was hard to migrate from those cards to magnetic tapes because the code need to be re-written.

Open-closed Principle - Once code is written, it can be read / written anywhere. No IO device dependence.

Junk Mail

The program was written in a magnetic tape. The program had a shape. That shape disconnected policy from detail.

  • Policy: The formatting of the name and address record
  • Detail: The device

Physical Addressing

The success was that they were able to decouple the decision about disk drive structure from the application.

Conclusion

Good architects carefully separate details from policy, and decouple the policy from the details. Good architects design the policy, so that decisions about the details can be delayed and deferred for as long as possible.

What DB do we use? What programming language do we use? What framework do we use?

Chapter 16 - Independence

A good architecture must support:

  • The use cases and operation of the system
  • The maintenance of the system
  • The development of the system
  • The deployment of the system

Use Cases

Clarify and expose the software's behavior. This makes the system "visible" at the architecture level without details.

Operation

If the system must handle 100,000 customers per second, the architecture must support that kind of throughput and response time for each use case that depends on it.

What operations do we need to support? Architects need to leave open.

Development

Conway's law:

Any organization that designs a system will produce a design whose structure is a copy of the organization's communication structure.

The teams should not interfere with each other during development.

This is accomplished by properly partitioning the system into well-isolated, independently develop-able components.

Deployment

The goal is "immediate deployment".

Leaving Options Open

There're many principles but it's so hard to satisfy all. Some principles of architecture are relatively inexpensive to implement and can help us partition our systems into well-isolated components that allow us to leave as many options open as possible.

Decoupling Layers

The validation of input fields is a business rule - tied to the application. The calculation of interest on an account and the counting of inventory are business rules - tied to the domain.

Horizontal layers (for example):

  • The UI
  • Application-specific business rules
  • Application-independent business rules (I think this is domain rules)
  • The database

Decoupling Use Cases

Use cases are very natural way to divide the system.

Use cases are narrow vertical layers.

Decoupling Mode

Decoupling use cases help with operations. -> Service Oriented Architecture. Sometimes we have to separate our components all the way to the service level.

Independent Develop-ability

When components are strongly decoupled, the interference between teams is mitigated.

Independent Deployability

If the decoupling is done well, then it should be possibly to hot-swap layers and use cases in running systems.

Duplication

Duplication is generally a bad thing in software. However, there are True Duplication and False (Accidental) Duplication.

If two apparently duplicated sections of code evolve along different paths - if they change at different rates, and for different reasons - then they are not true duplicates.

Most likely, if two use cases have very different components, they are false duplicates. Avoid unifying them.

Decoupling Models (Again)

There are many ways to decouple layers and use cases. They can be decoupled at the source code level, at the binary code (deployment) level, and at the execution unit (service) level.

  • Source level: We can control the dependencies between source code modules.
  • Deployment level: We can deploy separately.
  • Service level: Micro services. Services communicate through network packets.

What is the best? Depending on the phase of the project. If you can do, decouple at the service level by default. A problem is it's expensive and encourages coarse-grained decoupling.

The author's preference is deployment level.

Conclusion

The decoupling mode of a system is one of those things that is likely to change over time.

Chapter 17 - Boundaries: Drawing Lines

Which kinds of decisions are premature? Decisions that have nothing to do with the business requirement.

  • frameworks
  • databases
  • web servers
  • utility libraries
  • dependency injection

A good system architecture does not depend on these decisions, but allow those decisions to be made at the latest possible moment.

A Couple of Sad Stories

  • The devs decide to have three different instantiations. Each of them has a model. Adding a field to a model could cost x3 as they live separately.
  • Introducing a gigantic service oriented architecture - you need to fire up too many services to do one thing. Messages needed to bounce from service to service, and waited in queue after queue.

FitNesse

The success experience. Because they delay and defer decisions, they can focus on building the business logic. They didn't have a DB server early on, and they eventually changed their mind and didn't use it. They didn't need to struggle with schema change and stuff. They draw a line to not to use the DB.

Which Lines Do You Draw, And When Do You Draw Them?

The database is a tool that the business rules can use indirectly.

  • Fig 17.1 The database behind an interface
  • Fig 17.2 The boundary line

The boundary is between the interface and the class.

  • Fig 17.3 The business rules and database components

The database knows about the business rules but the business rules don't know about the database. This implies that the database interface class lives in the business rules component, not in the database component.

What About Input And Output

Your experience is dominated by the interface: the screen, the mouse, the buttons and the sounds. You forget that behind that interface there is a model.

Business rules and the GUI separated by a boundary line.

  • Fig 17.4 The boundary between GUI and Business Rules components

Plugin Architecture

  • Fig 17.5 Plugging in to the business rules

The DB and GUI are plug-able.

The Plugin Argument

  • Fig 17.6 ReSharper depends on Visual Studio

We want certain modules to be immune to others.

GUIs change at different times and at different rates than business rules.

The SRP (Single Responsibility Principle) tells us where to draw our boundaries.

Conclusion

To draw boundary lines:

  1. Partition the system into components (business rules, plugins)
  2. Arrange the code in those components (who's depending on what, toward the core business. use interfaces and abstract classes here.)

Chapter 18 - Boundary Anatomy

The architecture of a system:

  • Components
  • Boundaries

Boundaries have many forms.

Boundary Crossing

In runtime level, it's the source code: A function calling a function of the other side of the boundary.

The Dreaded Monolith

In deployment level, it's a monolith - single executable file.

... Such architectures almost always depend on some kind of dynamic polymorphism to manage their internal dependencies. This is one of the reasons that object-oriented development has become such an important paradigm in recent decades.

  • Fig 18.1 Flow of control crosses the boundary from a lower level to a high level
  • Fig 18.2 Crossing the boundary against the flow of control

Deployment Components

The simplest physical representation of an architectural boundary is a dynamically linked library like a .Net DLL, a Java jar file...

Deployment does not involve compilation. Deployment-level components are almost the same as monoliths. The difference is that the communication across the boundaries is happening when linking.

Threads

Threads are not architectural boundaries or units of deployment, but rather a way to organize the schedule and order of execution.

Local Processes

Local processes are much stronger physical architectural boundaries. They are typically ran by a command and run in separate address spaces. They communicate each other via sockets, message queues, shared memory.

Services

The strongest boundary. Communication across service boundaries are slow, often over the network.

Conclusion

Most systems, other than monoliths, use more than one boundary strategy.

Chapter 19 - Policy and Level

Software systems are statements of policy.

Part of the art of developing a software architecture is carefully separating those policies from one another, and regrouping them based on the way that they change. Policies that change for the same reasons and the same times, are at the same level and belong together in the same component.

Level

A strict definition of "level" is "the distance from the inputs and outputs." The farther a policy is from both the inputs and the outputs of the system, the highest its level. The policies that manage input and output are the lowest-level policies in the system.

  • Fig 19.1 A simple encryption program
function encrypt() {
  while (true) {
    writeChar(translate(readChar));
  }
}

This code snippet above is incorrect. Because the high level encrypt depends on lower level readChar and writeChar functions.

  • Fig 19.2 Class diagram showing a better architecture for the system

With the class diagram, it shows:

// charReader and charWriter are interfaces.
function encrypt(charReader, charWriter) {
  while (true) {
    charWriter(translate(charReader));
  }
}

Policies are grouped together by the SRP and CCP.

  • Fig 19.3 Lower-level components should plug in to higher-level components

Conclusion

this discussion of policies has involved a mixture of the Single Responsibility Principle, the Open-Closed Principle, the Common Closure Principle, the Dependency Inversion Principle, the Stable Dependencies Principle, and the Stable Abstractions Principle.

Chapter 20 - Business Rules

business rules are rules or procedures that make or save the business money. -> Critical Business Rules

Critical Business Rules require some data to work with. -> Critical Business Data -> We call it Entity

Entities

The interface of the Entity consists of the functions that implement the Critical Business Rules that operate on that data.

  • Fig 20.1 Loan entity as a class in UML

The Entity is a pure business and nothing else.

Use Cases

Not all business rules are as pure as Entities. Some business rules make / save money for the business by defining and constraining the way that an automated system operates.

A use case is a description of the way that an automated system is used. It specifies the input to be provided by the user, the output to be returned to the user, and the processing steps involved in producing that output. A use case describes application-specific business rules as opposed to the Critical Business Rules within the Entities.

  • Fig 20.2 Example use case

Use cases contain the rules that specify how and when the Critical Business Rules within the Entities are involved. Use cases control the dance of the Entities.

Use cases do not describe how the system appears to the user. Instead, they describe the application-specific rules that govern the interaction between the users and the Entities. How the data gets in and out of the system is irrelevant to the use cases.

Entities have no knowledge of the use cases that control them.

Why are Entities high level and use cases lower level? Because use cases are specific to a single application and, therefore, are closer to the inputs and outputs of that system.

Request And Response Models

We certainly don't want the code within the use case class to know about HTML or SQL!

You might be tempted to have these data structures contain references to Entity objects. You might think this makes sense because the Entities and the request/response models share so much data. Avoid this temptation! The purpose of these two objects is very different. Over time they will change for very different reasons

Conclusion

The business rules should remain pristine, unsullied by baser concerns such as the user interface or database used.

The business rules should be the most independent and reusable code in the system.

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