Clean Architecture: A Craftsman's Guide to Software Structure and Design
Rate it:
Open Preview
31%
Flag icon
The Zone of Uselessness Consider a component near (1, 1). This location is undesirable because it is maximally abstract, yet has no dependents. Such components are useless. Thus this area is called the Zone of Uselessness.
31%
Flag icon
component that sits on the Main Sequence is not “too abstract” for its stability, nor is it “too unstable” for its abstractness. It is neither useless nor particularly painful. It is depended on to the extent that it is abstract, and it depends on others to the extent that it is concrete.
31%
Flag icon
The most desirable position for a component is at one of the two endpoints of the Main Sequence.
31%
Flag icon
D3: Distance. D = |A+I–1| . The range of this metric is [0, 1]. A value of 0 indicates that the component is directly on the Main Sequence. A value of 1 indicates that the component is as far away as possible from the Main Sequence.
31%
Flag icon
Statistical analysis of a design is also possible. We can calculate the mean and variance of all the D metrics for the components within a design. We would expect a conforming design to have a mean and variance that are close to zero. The variance can be used to establish “control limits” so as to identify components that are “exceptional” in comparison to all the others.
31%
Flag icon
Another way to use the metrics is to plot the D metric of each component over time. The graph in Figure 14.15 is a mock-up of such a plot. You can see that some strange dependencies have been creeping into the Payroll component over the last few releases.
32%
Flag icon
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. Software architects may not write as much code as other programmers do, but they continue to engage in programming tasks. 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 the programmers.
32%
Flag icon
The strategy behind that facilitation is to leave as many options open as possible, for as long as possible.
32%
Flag icon
Such a component-per-team architecture is not likely to be the best architecture for deployment, operation, and maintenance of the system. Nevertheless, it is the architecture that a group of teams will gravitate toward if they are driven solely by development schedule.
32%
Flag icon
A goal of a software architecture, then, should be to make a system that can be easily deployed with a single action.
32%
Flag icon
Software systems that have inefficient architectures can often be made to work effectively simply by adding more storage and more servers. The fact that hardware is cheap and people are expensive means that architectures that impede operation are not as costly as architectures that impede development, deployment, and maintenance.
33%
Flag icon
The primary cost of maintenance is in spelunking and risk.
33%
Flag icon
All software systems can be decomposed into two major elements: policy and details. The policy element embodies all the business rules and procedures. The policy is where the true value of the system lives. The details are those things that are necessary to enable humans, other systems, and programmers to communicate with the policy, but that do not impact the behavior of the policy at all. They include IO devices, databases, web systems, servers, frameworks, communication protocols, and so forth.
33%
Flag icon
A good architect maximizes the number of decisions not made.
34%
Flag icon
Our programs had a shape. That shape disconnected policy from detail. The policy was the formatting of the name and address records. The detail was the device. We deferred the decision about which device we would use.
35%
Flag icon
Good architects carefully separate details from policy, and then decouple the policy from the details so thoroughly that the policy has no knowledge of the details and does not depend on the details in any way. Good architects design the policy so that decisions about the details can be delayed and deferred for as long as possible.
35%
Flag icon
As we previously stated, 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.
35%
Flag icon
USE CASES The first bullet—use cases—means that the architecture of the system must support the intent of the system.
35%
Flag icon
The most important thing a good architecture can do to support behavior is to clarify and expose that behavior so that the intent of the system is visible at the architectural level.
35%
Flag icon
strange as it may seem, this decision is one of the options that a good architect leaves open.
35%
Flag icon
Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.
35%
Flag icon
A system that must be developed by an organization with many teams and many concerns must have an architecture that facilitates independent actions by those teams, so that the teams do not interfere with each other during development. This is accomplished by properly partitioning the system into well-isolated, independently developable components. Those components can then be allocated to teams that can work independently of each other.
35%
Flag icon
A good architecture makes the system easy to change, in all the ways that it must change, by leaving options open.
36%
Flag icon
Thus we find the system divided into decoupled horizontal layers—the UI, application-specific business rules, application-independent business rules, and the database, just to mention a few.
36%
Flag icon
At the same time, use cases are narrow vertical slices that cut through the horizontal layers of the system.
36%
Flag icon
In short, the decoupling that we did for the sake of the use cases also helps with operations.
36%
Flag icon
To run in separate servers, the separated components cannot depend on being together in the same address space of a processor. They must be independent services, which communicate over a network of some kind. Many architects call such components “services” or “micro-services,” depending upon some vague notion of line count.
36%
Flag icon
Remember, a good architecture leaves options open. The decoupling mode is one of those options.
36%
Flag icon
Architects often fall into a trap—a trap that hinges on their fear of duplication.
36%
Flag icon
Resist the temptation to commit the sin of knee-jerk elimination of duplication. Make sure the duplication is real.
37%
Flag icon
Back to modes. 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.
37%
Flag icon
One solution (which seems to be popular at the moment) is to simply decouple at the service level by default. A problem with this approach is that it is expensive and encourages coarse-grained decoupling.
37%
Flag icon
My preference is to push the decoupling to the point where a service could be formed. should it become necessary; but then to leave the components in the same address space as long as possible. This leaves the option for a service open.
37%
Flag icon
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.
37%
Flag icon
Software architecture is the art of drawing lines that I call boundaries. Those boundaries separate software elements from one another, and restrict those on one side from knowing about those on the other. Some
37%
Flag icon
Those that are drawn early are drawn for the purposes of deferring decisions for as long as possible, and of keeping those decisions from polluting the core business logic.
37%
Flag icon
Which kinds of decisions are premature? Decisions that have nothing to do with the business requirements—the use cases—of the system. These include decisions about frameworks, databases, web servers, utility libraries, dependency injection, and the like.
38%
Flag icon
The cost of those errors was sheer person-hours—person-hours in droves—flushed down the SoA vortex.
39%
Flag icon
WHICH LINES DO YOU DRAW, AND WHEN DO YOU DRAW THEM? You draw lines between things that matter and things that don’t. The GUI doesn’t matter to the business rules, so there should be a line between them. The database doesn’t matter to the GUI, so there should be a line between them. The database doesn’t matter to the business rules, so there should be a line between them.
40%
Flag icon
Boundaries are drawn where there is an axis of change. The components on one side of the boundary change at different rates, and for different reasons, than the components on the other side of the boundary.
40%
Flag icon
This is simply the Single Responsibility Principle again. The SRP tells us where to draw our boundaries.
40%
Flag icon
To draw boundary lines in a software architecture, you first partition the system into components. Some of those components are core business rules; others are plugins that contain necessary functions that are not directly related to the core business. Then you arrange the code in those components such that the arrows between them point in one direction—toward the core business. You should recognize this as an application of the Dependency Inversion Principle and the Stable Abstractions Principle. Dependency arrows are arranged to point from lower-level details to higher-level abstractions. 1.
41%
Flag icon
The simplest possible boundary crossing is a function call from a low-level client to a higher-level service. Both the runtime dependency and the compile-time dependency point in the same direction, toward the higher-level component.
41%
Flag icon
Even in a monolithic, statically linked executable, this kind of disciplined partitioning can greatly aid the job of developing, testing, and deploying the project. Teams can work independently of each other on their own components without treading on each other’s toes. High-level components remain independent of lower-level details.
41%
Flag icon
Communications between components in a monolith are very fast and inexpensive. They are typically just function calls. Consequently, communications across source-level decoupled boundaries can be very chatty.
41%
Flag icon
With that one exception, deployment-level components are the same as monoliths. The functions generally all exist in the same processor and address space. The strategies for segregating the components and managing their dependencies are the same.2 As with monoliths, communications across deployment component boundaries are just function calls and, therefore, are very inexpensive. There may be a one-time hit for dynamic linking or runtime loading, but communications across these boundaries can still be very chatty.
41%
Flag icon
Think of a local process as a kind of uber-component: The process consists of lower-level components that manage their dependencies through dynamic polymorphism.
42%
Flag icon
A system that makes use of service boundaries may also have some local process boundaries. Indeed, a service is often just a facade for a set of interacting local processes. A service, or a local process, will almost certainly be either a monolith composed of source code components or a set of dynamically linked deployment components.
42%
Flag icon
This means that the boundaries in a system will often be a mixture of local chatty boundaries and boundaries that are more concerned with latency.
42%
Flag icon
A computer program is a detailed description of the policy by which inputs are transformed into outputs.