More on this book
Community
Kindle Notes & Highlights
the same token, data formats declared in an outer circle should not be used by an inner circle, especially if those formats are generated by a framework in an outer circle. We don’t want anyt...
This highlight has been truncated due to consecutive passage length restrictions.
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.
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.
We also do not expect this layer to be affected by changes to externalities such as the database, the UI, or any of the common frameworks. The use cases layer is isolated from such concerns.
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.
is this layer, for example, that will wholly contain the MVC architecture of a GUI.
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.
As you move inward, the software grows more abstract and encapsulates higher-level policies. The innermost circle is the most general and highest level.
Thus, when we pass data across a boundary, it is always in the form that is most convenient for the inner circle.
The job of the Presenter is to repackage the OutputData into viewable form as the ViewModel, which is yet another plain old Java object.
Conforming to these simple rules is not difficult, and it will save you a lot of headaches going forward. 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.
When any of the external parts of the system become obsolete, such as the database, or the web framework, you can replace those obsolete elements with a minimum of fuss.
The Humble Object pattern1 is a design pattern that was originally identified as a way to help unit testers to separate behaviors that are hard to test from behaviors that are easy to test.
The idea is very simple: Split the behaviors into two modules or classes. One of those modules is humble; it contains all the hard-to-test behaviors stripped down to their barest essence. The other module contains all the testable behaviors that were stripped out of the humble object.
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. 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.
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.
For example, if the application wants a date displayed in a field, it will hand the Presenter a Date object. The Presenter will then format that data into an appropriate string and place it in a simple data str...
This highlight has been truncated due to consecutive passage length restrictions.
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.
It has long been known that testability is an attribute of good architectures. The Humble Object pattern is a good example, because the separation of the behaviors into testable and non-testable parts often defines an architectural boundary. The Presenter/View boundary is one of these boundaries, but there are many others.
use case interactors and the database are the database gateways.
These gateways are polymorphic interfaces that contain methods for every create, read, update, or delete operation that can be performed by the application on the database.
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 ...
This highlight has been truncated due to consecutive passage length restrictions.
Recall that we do not allow SQL in the use cases layer; instead, we use gateway interfaces that have appropriate methods. Those gateways are implemented by classes in the database layer. That implementation is the humble object. It simply uses SQL, or whatever the interfac...
This highlight has been truncated due to consecutive passage length restrictions.
those interactors are testable, because the gateways can be replaced with appropriate stubs and test-doubles.
data structure, in contrast, is a set of public data variables that have no implied behavior. ORMs would be better named “data mappers,” because they load data into data structures from relational database tables.
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.
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. That takes a lot of work. It’s also a lot of work to maintain.
The boundary is simply defined by the Facade class, which lists all the services as methods, and deploys the service calls to classes that the client is not supposed to access.
We’ve seen three simple ways to partially implement an architectural boundary.
We, as architects, must be careful to recognize when they are needed. We also have to be aware that such boundaries, when fully implemented, are expensive.
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.
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.
You don’t simply decide at the start of a project which boundaries to impleme...
This highlight has been truncated due to consecutive passage length restrictions.
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 fri...
This highlight has been truncated due to consecutive passage length restrictions.
In every system, there is at least one component that creates, coordinates, and oversees the others. I call this component Main.
The Main component is the ultimate detail—the lowest-level policy.
Think of Main as the dirtiest of all the dirty components.
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.
For example, you could have a Main plugin for Dev, another for Test, and yet another for Production.
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 that simply separate application behaviors are little more than expensive function calls, and are not necessarily architecturally significant.
There are often substantial benefits to creating services that separate functionality across processes and platforms—whether they obey the Dependency Rule or not. It’s just that services, in and of themselves, do not define an architecture.
So it is with services. Services are, after all, just function calls across process and/or platform boundaries. Some of those services are architecturally significant, and some aren’t. Our interest, in this chapter, is with the former.
they can still be coupled by shared resources within a processor, or on the network. What’s more, they are strongly coupled by the data they share.
For example, if a new field is added to a data record that is passed between services, then every service that operates on the new field must be changed.
The services must also strongly agree about the interpretation of the data in that field. Thus those services are strongly coupled to the data record and, t...
This highlight has been truncated due to consecutive passage length restrictions.
Another of the supposed benefits of services is that they can be owned and operated by a dedicated team.