Clean Architecture: A Craftsman's Guide to Software Structure and Design
Rate it:
Open Preview
Kindle Notes & Highlights
46%
Flag icon
like. Or the data can simply be arguments in function calls. Or you can pack it into a hashmap, or construct it into an object.
46%
Flag icon
We don’t want to cheat and pass Entity objects or database rows.
46%
Flag icon
For example, many database frameworks return a convenient data format in response to a query. We might call this a “row structure.” We don’t want to pass that row structure inward across a boundary.
47%
Flag icon
The job of the Presenter is to repackage the OutputData into viewable form as the ViewModel, which is yet another plain old Java object.
47%
Flag icon
Whereas the OutputData may contain Date objects, the Presenter will load the ViewModel with corresponding Strings already formatted properly for the user.
47%
Flag icon
This leaves the View with almost nothing to do other than to move the data from the ViewModel into the HTML page.
47%
Flag icon
By separating the software into layers and conforming to the Dependency Rule, you will create a system that is intrinsically testable, with all the benefits that implies.
47%
Flag icon
Presenters are a form of the Humble Object pattern, which helps us identify and protect architectural boundaries.
47%
Flag icon
Using the Humble Object pattern, we can separate these two kinds of behaviors into two different classes called the Presenter and the View.
47%
Flag icon
The View is the humble object that is hard to test. The code in this object is kept as simple as possible. It moves data into the GUI but does not process that data.
47%
Flag icon
The Presenter is the testable object. Its job is to accept data from the application and format it for presentation so that the View can simply move it to the screen.
47%
Flag icon
Anything and everything that appears on the screen, and that the application has some kind of control over, is represented in the View Model as a string, or a boolean, or an enum. Nothing is left for the View to do other than to load the data from the View Model into the screen. Thus the View is humble.
47%
Flag icon
It has long been known that testability is an attribute of good architectures.
47%
Flag icon
For example, if the application needs to know the last names of all the users who logged in yesterday, then the UserGateway interface will have a method named getLastNamesOfUsersWhoLoggedInAfter that takes a Date as its argument and returns a list of last names.
47%
Flag icon
The interactors, in contrast, are not humble because they encapsulate application-specific business rules. Although they are not humble, those interactors are testable, because the gateways can be replaced with appropriate stubs and test-doubles.
48%
Flag icon
First, let’s get something straight: There is no such thing as an object relational mapper (ORM). The reason is simple: Objects are not data structures. At least, they are not data structures from their users’ point of view. The users of an object cannot see the data, since it is all private. Those users see only the public methods of that object. So, from the user’s point of view, an object is simply a set of operations.
48%
Flag icon
A data structure, in contrast, is a set of public data variables that have no implied behavior.
48%
Flag icon
ORMs would be better named “data mappers,” because they load data into data structures from ...
This highlight has been truncated due to consecutive passage length restrictions.
48%
Flag icon
Indeed, ORMs form another kind of Humble Object boundary between the gateway interfaces and the database.
48%
Flag icon
On the input side, the service listeners will receive data from the service interface and format it into a simple data structure that can be used by the application.
48%
Flag icon
Full-fledged architectural boundaries are expensive. They require reciprocal polymorphic Boundary interfaces, Input and Output data structures, and all of the dependency management necessary to isolate the two sides into independently compilable and deployable components.
48%
Flag icon
This kind of anticipatory design is often frowned upon by many in the Agile community as a violation of YAGNI: “You Aren’t Going to Need It.” Architects, however, sometimes look at the problem and think, “Yeah, but I might.” In that case, they may implement a partial boundary.
48%
Flag icon
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.
48%
Flag icon
However, it does not require the administration of multiple components. There’s no version number tracking or release management burden. That difference should not be taken lightly.
48%
Flag icon
The full-fledged architectural boundary uses reciprocal boundary interfaces to maintain isolation in both directions.
49%
Flag icon
It is easy to think of systems as being composed of three components: UI, business rules, and database. For some simple systems, this is sufficient. For most systems, though, the number of components is larger than that.
49%
Flag icon
For example, language is not the only axis of change for the UI. We also might want to vary the mechanism by which we communicate the text.
49%
Flag icon
That means that there is a potential architectural boundary defined by this axis of change.
49%
Flag icon
If we were to look inside GameRules, we would find polymorphic Boundary interfaces used by the code inside GameRules and implemented by the code inside the Language component. We would also find polymorphic Boundary interfaces used by Language and implemented by code inside GameRules.
50%
Flag icon
This example is intended to show that architectural boundaries exist everywhere. We, as architects, must be careful to recognize when they are needed.
50%
Flag icon
We also have to be aware that such boundaries, when fully implemented, are expensive. At the same time, we have to recognize that when such boundaries are ignored, they are very expensive to add in later—even in the presence of comprehensive test-suites and refactoring discipline.
50%
Flag icon
So there you have it. O 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.
50%
Flag icon
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.
50%
Flag icon
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...
This highlight has been truncated due to consecutive passage length restrictions.
51%
Flag icon
It is in this Main component that dependencies should be injected by a Dependency Injection framework.
52%
Flag icon
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.
52%
Flag icon
Since it is a plugin, it is possible to have many Main components, one for each configuration of your application.
52%
Flag icon
• Services seem to be strongly decoupled from each other. As we shall see, this is only partially true. • Services appear to support independence of development and deployment. Again, as we shall see, this is only partially true.
52%
Flag icon
The architecture of a system is defined by boundaries that separate high-level policy from low-level detail and follow the Dependency Rule.
52%
Flag icon
It’s just that services, in and of themselves, do not define an architecture.
52%
Flag icon
Service interfaces are no more formal, no more rigorous, and no better defined than function interfaces.
52%
Flag icon
First, history has shown that large enterprise systems can be built from monoliths and component-based systems as well as service-based systems.
53%
Flag icon
One of the taxi suppliers has agreed to participate in this program. Others are likely to follow. Still others may decline. Of course, some drivers may be allergic to cats, so those drivers should never be selected for this service. Also, some customers will undoubtedly have similar allergies, so a vehicle that has been used to deliver kittens within the last 3 days should not be selected for customers who declare such allergies. Look at that diagram of services. How many of those services will have to change to implement this feature? All of them.
53%
Flag icon
This is the problem with cross-cutting concerns. Every software system must face this problem, whether service oriented or not. Functional decompositions, of the kind depicted in the service diagram in Figure 27.1, are very vulnerable to new features that cut across all those functional behaviors.
53%
Flag icon
Those services do not define the architectural boundaries of the system; instead, the components within the services do.
54%
Flag icon
Yes, that’s right: The tests are part of the system,
54%
Flag icon
TESTS AS SYSTEM COMPONENTS
54%
Flag icon
Tests, by their very nature, follow the Dependency Rule; they are very detailed and concrete; and they always depend inward toward the code being tested. In fact, you can think of the tests as the outermost circle in the architecture. Nothing within the system depends on the tests, and the tests always depend inward on the components of the system.
54%
Flag icon
Tests are also independently deployable. In fact, most of the time they are deployed in test systems, rather than in production systems. So, even in systems where independent deployment is not otherwise necessary, the tests will still be independently deployed.
54%
Flag icon
DESIGN FOR TESTABILITY