- The goal of software architecture is to minimize the human resources required to build and maintain the required system.
- The only way to go fast, is to go well.
A TALE OF TWO VALUES
The first value of software is its behavior. Programmers are hired to make machines behave in a way that makes or saves money for the stakeholders.
Software was invented to be "soft." It was intended to be a way to easily change the behavior of machines. To fulfill its purpose, software must be soft; that is, it must be easy to change.
The first value of software: behavior is urgent but not always particularly important. The second value of software: architecture is important but never particularly urgent.
Three big concerns of architecture:
Function, separation of components, and data management. Software is composed of sequence, selection, iteration, and indirection. Nothing more. Nothing less. To summarize:
- Structured programming is discipline imposed upon direct transfer of control.
- Object-oriented programming is discipline imposed upon indirect transfer of control.
- Functional programming is discipline imposed upon variable assignment.
SRP: The Single Responsibility Principle
The best way to understand this principle is by looking at the symptoms of violating it.
SYMPTOM 1: ACCIDENTAL DUPLICATION
These problems occur because we put code that different actors depend on into close proximity. The SRP says to separate the code that different actors depend on.
SYMPTOM 2: MERGES
The Single Responsibility Principle is about functions and classes but it reappears in a different form at two more levels. At the level of components, it becomes the Common Closure Principle. At the architectural level, it becomes the Axis of Change responsible for the creation of Architectural Boundaries.
OCP: The Open-Closed Principle
If component A should be protected from changes in component B, then component B should depend on component A. This is how the OCP works at the architectural level. Architects separate functionality based on how, why, and when it changes, and then organize that separated functionality into a hierarchy of components. Higher-level components in that hierarchy are protected from the changes made to lower-level components. The goal is to make the system easy to extend without incurring a high impact of change. This goal is accomplished by partitioning the system into components, and arranging those components into a dependency hierarchy that protects higher-level components from changes in lower-level components.
LSP: The Liskov Substitution Principle
The LSP can, and should, be extended to the level of architecture. A simple violation of substitutability, can cause a system’s architecture to be polluted with a significant amount of extra mechanisms.
ISP: The Interface Segregation Principle
In general, it is harmful to depend on modules that contain more than you need
DIP: The Dependency Inversion Principle
Don’t refer to volatile concrete classes. Refer to abstract interfaces instead. This rule applies in all languages, whether statically or dynamically typed. It also puts severe constraints on the creation of objects and generally enforces the use of Abstract Factories. DIP violations cannot be entirely removed, but they can be gathered into a small number of concrete components and kept separate from the rest of the system.
Well-designed components always retain the ability to be independently deployable and, therefore, independently developable.
The 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
The granule of reuse is the granule of release
THE COMMON CLOSURE PRINCIPLE
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. This is the Single Responsibility Principle restated for components. Just as the SRP says that a class should not contain multiples reasons to change, so the Common Closure Principle (CCP) says that a component should not have multiple reasons to change. 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 Don’t force users of a component to depend on things they don’t need.
CRP tells us more about which classes shouldn’t be together than about which classes should be together. The CRP says that classes that are not tightly bound to each other should not be in the same component.
THE TENSION DIAGRAM FOR COMPONENT COHESION
The REP and CCP are inclusive principles: Both tend to make components larger. The CRP is an exclusive principle, driving components to be smaller. It is the tension between these principles that good architects seek to resolve. A good architect finds a position in that tension triangle that meets the current concerns of the development team, but is also aware that those concerns will change over time. For example, early in the development of a project, the CCP is much more important than the REP, because develop-ability is more important than reuse. The balance is almost always dynamic. That is, the partitioning that is appropriate today might not be appropriate next year. As a consequence, the composition of the components will likely jitter and evolve with time as the focus of the project changes from develop-ability to reusability.
ELIMINATE DEPENDENCY CYCLES
Changes made to one component do not need to have an immediate affect on other teams. Each team can decide for itself when to adapt its own components to new releases of the components. Moreover, integration happens in small increments. There is no single point in time when all developers must come together and integrate everything they are doing. This is a very simple and rational process, and it is widely used. To make it work successfully, however, you must manage the dependency structure of the components.
BREAKING THE CYCLE
1 Apply the Dependency Inversion Principle (DIP). 2 Create a new component
Component dependency diagrams have very little do to with describing the function of the application. Instead, they are a map to the buildability and maintainability of the application.
THE STABLE DEPENDENCIES PRINCIPLE
Depend in the direction of stability. Modules that are intended to be easy to change are not depended on modules that are harder to change. One sure way to make a software component difficult to change, is to make lots of other software components depend on it. A component with lots of incoming dependencies is very stable because it requires a great deal of work to reconcile any changes with all the dependent components.
Fan-in: Incoming dependencies. This metric identifies the number of classes outside this component that depend on classes within the component. Fan-out: Outgoing depenencies. This metric identifies the number of classes inside this component that depend on classes outside the component. I: Instability: I = Fan-out , (Fan-in + Fan-out). This metric has the range [0, 1]. I = 0 indicates a maximally stable component. I = 1 indicates a maximally unstable component.
The SDP says that the I metric of a component should be larger than the I metrics of the components that it depends on. That is, I metrics should decrease in the direction of dependency.
INTRODUCING THE STABLE ABSTRACTIONS PRINCIPLE
The Stable Abstractions Principle (SAP) sets up a relationship between stability and abstractness. On the one hand, it says that a stable component should also be abstract so that its stability does not prevent it from being extended. On the other hand, it says that an unstable component should be concrete since it its instability allows the concrete code within it to be easily changed. Thus, 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. The SAP and the SDP combined amount to the DIP for components. This is true because the SDP says that dependencies should run in the direction of stability, and the SAP says that stability implies abstraction. Thus dependencies run in the direction of abstraction.
Two “good” kinds of components: components that are maximally stable and abstract and the components that are maximally unstable and concrete. Highly stable and concrete component means Zone of Pain. Some software entities do, in fact, fall within the Zone of Pain. An example would be a database schema. Database schemas are notoriously volatile, extremely concrete, and highly depended on. This is one reason why the interface between OO applications and databases is so difficult to manage, and why schema updates are generally painful. Consider a component maximally abstract, yet has no dependents. Such components are useless. Thus this area is called the Zone of Uselessness.
AVOIDING THE ZONES OF EXCLUSION
It seems clear that our most volatile components should be kept as far from both zones of exclusion as possible. The locus of points that are maximally distant from each zone is the line that connects them I call this line the Main Sequence.
WHAT IS ARCHITECTURE?
The architecture of a software system is the shape given to that system by those who build it. The form of that shape is in the division of that system into components, the arrangement of those components, and the ways in which those components communicate with each other. The purpose of that shape is to facilitate the development, deployment, operation, and maintenance of the software system contained within it. The strategy behind that facilitation is to leave as many options open as possible, for as long as possible. The primary purpose of architecture is to support the life cycle of the system. Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimize the lifetime cost of the system and to maximize programmer productivity. A good architect maximizes the number of decisions not made.
Business rules themselves may be closely tied to the application, or they may be more general. These two different kinds of rules will change at different rates, and for different reasons—so they should be separated so that they can be independently changed. The database, the query language, and even the schema are technical details that have nothing to do with the business rules or the UI. They will change at rates, and for reasons, that are independent of other aspects of the system. Consequently, the architecture should separate them from the rest of the system so that they can be independently changed.
Sometimes we have to separate our components all the way to the service level. Remember, a good architecture leaves options open. The decoupling mode is one of those options. The decoupling mode of a system is one of those things that is likely to change with time, and a good architect foresees and appropriately facilitates those changes.
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.
The high-level Client calls the f() function of the lower-level ServiceImpl through the Service interface. Note, however, that all dependencies cross the boundary from right to left toward the higher-level component. Note, also, that the definition of the data structure is on the calling side of the boundary.
Gems, wars and dlls
The segregation strategy between local processes is the same as for monoliths and binary components. Source code dependencies point in the same direction across the boundary, and always toward the higher-level component. For local processes, this means that the source code of the higher-level processes must not contain the names, or physical addresses, or registry lookup keys of lower level processes. Remember that the architectural goal is for lower-level processes to be plugins to higher-level processes.
The strongest boundary is a service. A service is a process, generally started from the command line or through an equivalent system call. Services do not depend on their physical location.
POLICY AND LEVEL
A computer program is a detailed description of the policy by which inputs are transformed into outputs. Policies that change for the same reasons, and at the same times, are at the same level and belong together in the same component. Policies that change for different reasons, or at different times, are at different levels and should be separated into different components. The art of architecture often involves forming the regrouped components into a directed acyclic graph. The nodes of the graph are the components that contain policies at the same level. The directed edges are the dependencies between those components. They connect components that are at different levels
The farther a policy is from both the inputs and the outputs of the system, the higher its level. The policies that manage input and output are the lowest-level policies in the system.
Lower-level components should be plugins to the higher-level components
Strictly speaking, business rules are rules or procedures that make or save the business money. Very strictly speaking, these rules would make or save the business money, irrespective of whether they were implemented on a computer. They would make or save money even if they were executed manually. We shall call these rules Critical Business Rules, because they are critical to the business itself, and would exist even if there were no system to automate them. Critical Business Rules usually require some data to work with. We shall call this data Critical Business Data. This is the data that would exist even if the system were not automated. The critical rules and critical data are inextricably bound, so they are a good candidate for an object. We’ll call this kind of object an Entity.
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. A use case is an object. It has one or more functions that implement the application specific business rules. It also has data elements that include the input data, the output data, and the references to the appropriate Entities with which it interacts.
Business rules are the reason a software system exists. They are the core functionality. They carry the code that makes, or saves, money. They are the family jewels. The business rules should remain pristine, unsullied by baser concerns such as the user interface or database used. Ideally, the code that represents the business rules should be the heart of the system, with lesser concerns being plugged in to them. The business rules should be the most independent and reusable code in the system.
THE CLEAN ARCHITECTURE
Systems that have the following characteristics:
- Independent of frameworks. The architecture does not depend on the existence of some library of feature-laden software. This allows you to use such frameworks as tools, rather than forcing you to cram your system into their limited constraints.
- Testable. The business rules can be tested without the UI, database, web server, or any other external element.
- Independent of the UI. The UI can change easily, without changing the rest of the system. A web UI could be replaced with a console UI, for example, without changing the business rules.
- Independent of the database. You can swap out Oracle or SQL Server for Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database.
- Independent of any external agency. In fact, your business rules don’t know anything at all about the interfaces to the outside world.
Pegar la imagen
The overriding rule that makes this architecture work is the Dependency Rule: Source code dependencies must point only inward, toward higher-level policies. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes functions, classes, variables, or any other named software entity ENTITIES Entities encapsulate enterprise-wide Critical Business Rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities can be used by many different applications in the enterprise. USE CASES The software in the use cases layer contains application-specific business rules. It encapsulates and implements all of the use cases of the system. These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their Critical Business Rules to achieve the goals of the use case. INTERFACE ADAPTERS The software in the interface adapters layer is a set of adapters that convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as the database or the web. FRAMEWORKS AND DRIVERS The outermost layer of the model in Figure 22.1 is generally composed of frameworks and tools such as the database and the web framework.
PRESENTERS AND HUMBLE OBJECTS
At each architectural boundary, we are likely to find the Humble Object pattern lurking somewhere nearby. The communication across that boundary will almost always involve some kind of simple data structure, and the boundary will frequently divide something that is hard to test from something that is easy to test. The use of this pattern at architectural boundaries vastly increases the testability of the entire system.
One way to construct a partial boundary is to do all the work necessary to create independently compilable and deployable components, and then simply keep them together in the same component. The reciprocal interfaces are there, the input/output data structures are there, and everything is all set up—but we compile and deploy all of them as a single component. Obviously, this kind of partial boundary requires the same amount of code and preparatory design work as a full boundary. However, it does not require the administration of multiple components. There’s no version number tracking or release management burden.
LAYERS AND BOUNDARIES
Software Architect, you must see the future. You must guess intelligently. You must weigh the costs and determine where the architectural boundaries lie, and which should be fully implemented, and which should be partially implemented, and which should be ignored. But this is not a one-time decision. You don’t simply decide at the start of a project which boundaries to implement and which to ignore. Rather, you watch. You pay attention as the system evolves. You note where boundaries may be required, and then carefully watch for the first inkling of friction because those boundaries don’t exist. At that point, you weigh the costs of implementing those boundaries versus the cost of ignoring them—and you review that decision frequently. Your goal is to implement the boundaries right at the inflection point where the cost of implementing becomes less than the cost of ignoring. It takes a watchful eye.
THE MAIN COMPONENT
The Main component is the ultimate detail: the lowest-level policy. It is the initial entry point of the system. Nothing, other than the operating system, depends on it. Its job is to create all the Factories, Strategies, and other global facilities, and then hand control over to the high-level abstract portions of the system. Think of Main as a plugin to the application; a plugin that sets up the initial conditions and configurations, gathers all the outside resources, and then hands control over to the high-level policy of the application. Since it is a plugin, it is possible to have many Main components, one for each configuration of your application. When you think about Main as a plugin component, sitting behind an architectural boundary, the problem of configuration becomes a lot easier to solve.
SERVICES: GREAT AND SMALL
As useful as services are to the scalability and develop-ability of a system, they are not, in and of themselves, architecturally significant elements. The architecture of a system is defined by the boundaries drawn within that system, and by the dependencies that cross those boundaries. That architecture is not defined by the physical mechanisms by which elements communicate and execute. A service might be a single component, completely surrounded by an architectural boundary. Alternatively, a service might be composed of several components separated by architectural boundaries. In rare cases, clients and services may be so coupled as to have no architectural significance whatever.
CLEAN EMBEDDED ARCHITECTURE
Letting all code become firmware is not good for your product’s long-term health. Being able to test only in the target hardware is not good for your product’s longterm health. A clean embedded architecture is good for your product’s long-term health.
THE DATABASE IS A DETAIL
THE WEB IS A DETAIL
FRAMEWORKS ARE DETAILS
Don’t marry the framework!
When faced with a framework, try not to marry it right away. See if there aren’t ways to date it for a while before you take the plunge. Keep the framework behind an architectural boundary if at all possible, for as long as possible. Perhaps you can find a way to get the milk without buying the cow.