[Book Study] Clean Architecture — Part 4

Zong Yuan
11 min readNov 14, 2021

This article starts covering Architecture section.

The Chapter 15 — What is Architecture & Chapter 16 — Independence is covered in this article.

Since architecture section has a lot chapters, I will split them into different articles.

What is Architecture?

Software architect continue to take programming tasks while they also guide the rest of the team toward a design that maximizes productivity. They need to engage in programming tasks to identify the problems of existing software architecture and continue enhance them.

The architecture of a software system is the shape given to that system by developers, including the divisions of system into components, the arrangements of components and the communications between components.

There are systems that work correctly but hard to maintain or extend, that’s why we need a good architecture to help for maintainability and expendability.

The purpose of architecture is to facilitate the development, deployment, operation and maintenance of the software system contained within it. And the strategy behind the facilitation is to leave as many options open as possible, for as long as possible.

In other words, a good architecture makes the system easy to understand, easy to develop, easy to maintain and easy to deploy.

The ultimate goal is to minimized the lifetime cost of the system and to maximize programmer productivity.

Reduce the cost of maintenance and reduce the cost/efforts required by programmer to make changes in future.

So how a good architecture is related or benefit to development, deployment, operation and maintenance?

Development

In early stage or a small team of developers, the benefits of a good architecture is rarely being realized, and most developers did not want to spend extra efforts on a good architecture. And this is why many systems don’t have a good architecture.

Imagine when the team expands into 5 teams, and all of them working on the same system development. The development can’t make any progress until the system has a good architecture that divide the system into well-defined components with reliably stable interfaces.

A good architecture allows multiple teams/developers to work on same system development at the same time.

Deployment

A goal of software architecture is to make a system that can be easily deployed with a single action.

Don’t make your system easy to develop but hard to deploy.

Deployment efforts should be considered during initial development, for example, the number of micro-services will increase the efforts to make a full deployment. If the deployment issues are considered at early stage, the number of micro-services and the deployment efforts can come to acceptable tradeoff.

Operation

The impact of architecture on operation is lesser compared with other aspects.

You can always add more hardware at the system to overcome operation performance issues caused by inefficient architectures. However, the costs will be increased, and the costs included development, deployment and maintenance costs.

Architecture plays another role in the operation of the system.

A good software architecture makes the operation of the system readily apparent to the developers.

A good software architecture will reduce the efforts for the developers to understand the operation of system, which greatly aids in development and maintenance.

Maintenance

The primary cost of maintenance is spelunking and risk.

Spelunking is the cost of digging through the existing software, trying to determine the best place and the best strategy to add new feature or to repair a defect.

A good software architecture can minimize these costs

  1. Isolating the components, so the risk of inadvertent breakage can be reduced
  2. Less cost from spelunking if the software architecture simplifies the understanding of the system

Keep Options Open

Software was invented because we needed a way to quickly and easily change the behavior of machines. The flexibility of software is depends on the software architecture.

Keep software soft/flexible by leaving as many options open as possible, for as long as possible. The options are the details that don’t matter.

Software can be decomposed into two major elements

  1. Policy: Business rules and procedures, the true value of the system lives.
  2. Details: Things that are necessary to enable humans, other systems and programmers to communicate with the policy, but do not impact the behavior of the policy at all. Such as IO devices, database, server, framework an so forth.

The goal of the architect is recognizes policy as the most essential element, while making the details irrelevant to policy. This allows decisions about those details to be delayed and deferred.

A good architect maximizes the number of decisions not made.

The book has provided several examples explained why separating details and policy in software architecture, and keep those details decisions open are important. I will skip those here but I highly recommend everyone to take a look on them.

Independence

It is quite difficult to summarize this chapter according the original composition, so I will write it down with my personal understanding, feel free to correct me if you find anything incorrect or improper.

As mentioned previously, a good architecture must support 4 elements

  1. The use cases and operation of the system
  2. The maintenance of the system
  3. The development of the system
  4. The deployment of the system

This chapter introduces the decoupling method to allow use cases, operations, development and deployment of the system become independent.

The benefits of independency of 4 elements above are

  1. Adding new use cases will be unlikely to affect existing use cases.
  2. System will be easier to upgrade to another structure if the requirement of operations change in the future.
  3. Developments of different components are independent, so multiple teams are able to work together on a system at the same time.
  4. Deployments of single use case or component are now possible and other components won’t be affected.

Use Cases

The architecture of the system must support the intent of the system; The architecture must support the use cases.

Architecture does not influence much over the behavior of the system, but what important is a good architecture can make the intent of the system visible to developers at the architecture level.

A shopping cart application with a good architecture should look like a shopping cart application.

The use cases will be plainly visible within the structure of the system.

Refer to Clean Code, the function/class/module name should clearly describe their function

Operation

Architecture should support the operation of the system.

  • If the system must handle 100k requests per second, the architecture must support the throughput and response time

Some systems need a monolith architecture but some systems need micro-services architecture.

The book suggests that this decision should be one of the options that leave open.

  • A system that is designed as monolith architecture and that depends on that monolith structure, cannot easily be upgraded to multiple processes, multiple threads or micro-services should the need arise.

An architecture that maintains the proper isolation of its components, and does not assume the means of communication between those components, will be much easier to transition through the spectrum of threads, processes, and services as the operational needs of the system change over time.

Development

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

Partitioning the system into well-isolated, independently developable components properly, so the components can be allocated to different teams that can work independently of each other.

Deployment

A good architecture helps the system to be immediately deployable after build.

Again, this is achieved through the proper partitioning and isolation of the components of the system, including those master components that tie the whole system together and ensure that each component is properly started, integrated, and supervised.

Leaving Options Open

Most of the time we don’t know what all the use cases are, nor do we know the operation constraints, the team structure, or the deployment requirements. And worse, we don’t know their changes in future. All these goals are indistinct and inconstant.

Some principles of architecture that are relatively inexpensive to implement and can help balance those concerns with unclear picture situation, are able to help us partition our systems into well-isolated components. With these isolations, we able to leave as many options open as possible, for as long as possible.

A good architecture makes the system easy to change, in all the ways that it must change, by leaving options open.

For example, if you leave those options open until the last 20% of development cycle, if the options need to be changed in future, only 20% efforts of development cycle have to be changed.

Decoupling Layer

Example of Decoupled Horizontal Layers

The architect wants the structure of the system to support all the necessary use cases, but does not know what all those use cases are. However, the architect does know the basic intent of the system. So the architect can employ the SRP and the CCP to separate those things that change for different reasons, and to collect those things that change for the same reasons.

For example,

  • UI change for reasons that have nothing to do with business rules
  • Use cases have elements of both

so architect might want to

  • Separate UI of a use case from the business rule so they can be changed independently

In short, decouple those components that have different rates and reasons for changes into different horizontal layers.

Horizontal layers example

  • UI
  • Application-specific business rules
  • Application-independent business rules
  • Database
  • others…

Decoupling Use Cases (Vertical Decoupling)

Use cases changes for different reasons!

Example of Decoupled Use Cases

Use cases are a very natural way to divide the system.

  • Separate the UI of the add-order use case from the UI of the delete-order use case
  • Do the same with the business rules, database and other horizontal layers

With this method, adding new cases will be unlikely to affect older ones.

Combined the horizontal decoupling and use cases decoupling, we now have the architecture with well-isolated components and use cases even we still don’t have full picture of use cases.

Decoupling and Operations

Decoupling that we did for the sake of the use cases also helps with operations

  • The different aspects of the use cases are separated, then those that must run at a high throughput are likely already separated from those that must run at a low throughput
  • The UI and the database have been separated from the business rules, then they can run in different servers
  • Those require higher bandwidth can be replicated in many servers

Independent Develop-ability

As long as the layers and use cases are decoupled, the architecture of the system will support the organization of the teams, irrespective of whether they are organized as feature teams, component teams, layer teams or other variations.

  • If business rules don’t know about the UI, then a team that focuses on the UI cannot much affect a team that focuses on the business rules
  • If the use cases themselves are decoupled from one another, then a team that focuses on the add order use case is not likely to interfere with a team that focuses on the delete order use case

Independent Deploy-ability

The decoupling of the use cases and layers also affords a high degree of flexibility in deployment.

  • Hot-swap layers and use cases in running systems are now possible
  • Adding new use case could be as simple as adding new jar files or services

Duplication

Duplication might seem unavoidable to achieve a good decoupling architecture.

However, there are different kinds of duplications.

  • True Duplication
    Every change to one instance necessitates the same change to every duplicate of that instance.
  • False/Accidental Duplication
    Two apparently duplicated sections of code evolve along different paths but they change at different rates and for different reasons. They will be very different from each other after few years.

Make sure the duplication is true duplication before you tempted to commit the sin of knee-jerk elimination of duplication.

  • Use cases might have similar screen structures, algorithms or database queries/schemas. However, they might be accidental duplication such as screen structure of use cases might have different requirement in future.
  • Data structure of a database record is similar to the data structure of a screen view. This duplication is almost certainly accidental. Creating the separate view model is not a lot of effort, and it will help you keep the layers properly decoupled.

I noticed that the projects generated by jhipster have entities and DTOs, the entities always stays in service layers, while DTOs will be used in resource layers as request parameters or response parameters. Entities never pass to resource layers, and service layers only accept the DTOs as parameters and return DTOs as return object. I always confuse about this setup until now.

Decoupling Mode

There are several level or mode for decoupling, however, this decoupling mode is one of those options that should be left open.

  • Source Level
    The decoupling happens at source code, such as dependencies between source code modules. So a change to one module do not effect of other modules. All components are being executed in the same address space, and communication between them are just simple function calls. People often call this a monolithic structure.
  • Deployment Level
    Deployable units such as jar files, DLLs or shared libraries. Changes on one module do not effect other modules unless you deploy the updated units to those modules. These components may still live in the same address space and communicate via function calls. But they also may live in other processes in the same processor, and communicate through interprocess communications, sockets, or shared memory.
  • Service Level
    The dependencies are down to level of data structure, and communicate solely through network packets such that every execution unit is entirely independent of source and binary changes to other (services or micro-services).

It is hard to know which mode is best during early phases of a project since the optimal mode may change as the project matures.

  • Decouple at service level by default
    Expensive and encourages coarse-grained decoupling. Dealing with service boundaries where none are needed is a waste of effort, memory and cycles.

The book introduces a solution for the best mode selection.

  1. Decoupling to the point where a service could be formed but then to leave the components in the same address space as long as possible. This leaves the option for a service open. (Source code level)
  2. When deployment or development issue arise, driving some of the decoupling to a deployment level may be sufficient for a while.
  3. As the development, deployment, and operational issues increase, carefully choose some deployable units to turn into services, and gradually shift the system in that direction.
  4. If the operational needs of the system decline, the service level decoupling components now may be able to go back to deployment level or even source-level decoupling

A good architecture will allow a system to be born as a monolith, deployed in a single file, but then to grow into a set of independently deployable units, and then all the way to independent services and/or micro-services. Later, as things change, it should allow for reversing that progression and sliding all the way back down into a monolith.

A good architecture protects the majority of the source code from those changes. Allow large deployments of the system can use one mode, whereas small deployments can use another.

Conclusion

In this article, we starts introducing what is architecture and also a starting point to design your system architecture.

--

--