More on this book
Community
Kindle Notes & Highlights
Read between
March 28 - October 28, 2018
For example, it’s not difficult to imagine that a system that runs comfortably on one server right now might grow to the point where some of its components ought to run on separate servers.
As the development, deployment, and operational issues increase, I carefully choose which deployable units to turn into services, and gradually shift the system in that direction.
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.
Recall that the goal of an architect is to minimize the human resources required to build and maintain the required system. What it is that saps this kind of people-power? Coupling—and especially coupling to premature decisions.
These include decisions about frameworks, databases, web servers, utility libraries, dependency injection, and the like.
A good system architecture allows those decisions to be made at the latest possible moment, without significant impact.
You draw lines between things that matter and things that don’t.
The business rules don’t need to know about the schema, or the query language, or any of the other details about the database. All the business rules need to know is that there is a set of functions that can be used to fetch or save data. This allows us to put the database behind an interface.
This implies that the DatabaseInterface classes live in the BusinessRules component, while the DatabaseAccess classes live in the Database component.
This may be hard to grasp at first. We often think about the behavior of the system in terms of the behavior of the IO.
Boundaries are drawn where there is an axis of change.
This is simply the Single Responsibility Principle again. The SRP tells us where to draw our boundaries.
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.
The trick to creating an appropriate boundary crossing is to manage the source code dependencies.
It is simply a disciplined segregation of functions and data within a single processor and a single address space. In a previous chapter, I called this the source-level decoupling mode.
From a deployment point of view, this amounts to nothing more than a single executable file—the so-called monolith.
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.
The services assume that all communications take place over the network.
Software systems are statements of policy. Indeed, at its core, that’s all a computer program actually is. A computer program is a detailed description of the policy by which inputs are transformed into outputs.
Part of the art of developing a software architecture is carefully separating those policies from one another, and regrouping them based on the ways that they change.
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 higher its level. The policies that manage input and output are the lowest-level policies in the system.
We want source code dependencies to be decoupled from data flow and coupled to level.
Higher-level policies—those that are farthest from the inputs and outputs—tend to change less frequently, and for more important reasons, than lower-level policies.
Another way to look at this issue is to note that lower-level components should be plugins to the higher-level components.
If we are going to divide our application into business rules and plugins, we’d better get a good grasp on just what business rules actually are.
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. For example, our loan requires a loan balance, an interest rate, and a payment schedule.
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.1
An Entity is an object within our computer system that embodies a small set of critical business rules operating on Critical Business Data.
All that is required is that you bind the Critical Business Data and the Critical Business Rules together in a single and separate software module.
This is a use case.2 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.
Use cases contain the rules that specify how and when the Critical Business Rules within the Entities are invoked. Use cases control the dance of the Entities.
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. 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.
Entities are generalizations that can be used in many different applications, so they are farther from the inputs and outputs of the system.
REQUEST AND RESPONSE MODELS
The use case class accepts simple request data structures for its input, and returns simple response data structures as its output. These data structures are not dependent on anything. They do not derive from standard framework interfaces such as HttpRequest and HttpResponse. They know nothing of the web, nor do they share any of the trappings of whatever user interface might be in place.
so should the architecture of a software application scream about the use cases of the application.
Frameworks are tools to be used, not architectures to be conformed to.
Look at each framework with a jaded eye. View it skeptically. Yes, it might help, but at what cost? Ask yourself how you should use it, and how you should protect yourself from it.
If your system architecture is all about the use cases, and if you have kept your frameworks at arm’s length, then you should be able to unit-test all those use cases without any of the frameworks in place.
Your architecture should tell readers about the system, not about the frameworks you used in your system.
Source code dependencies must point only inward, toward higher-level policies.
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.
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.
The presenters, views, and controllers all belong in the inter...
This highlight has been truncated due to consecutive passage length restrictions.
The models are likely just data structures that are passed from the controllers to the use cases, and then back from the use...
This highlight has been truncated due to consecutive passage length restrictions.
Similarly, data is converted, in this layer, from the form most convenient for entities and use cases, to the form most convenient for whatever persistence f...
This highlight has been truncated due to consecutive passage length restrictions.