More on this book
Community
Kindle Notes & Highlights
by
Evans Eric
Read between
May 28, 2022 - September 10, 2023
Rather than focusing on the attributes or even the behavior, strip the ENTITY object’s definition down to the most intrinsic characteristics, particularly those that identify it or are commonly used to find or match it.
Add only behavior that is essential to the concept and attributes that are required by that behavior.
In this example, the phone and address attributes moved into Customer, but on a real project, that choice would depend on how the domain’s customers are typically matched or distinguished.
For example, if a Customer has many contact phone numbers for different purposes, then the phone number is not associated with identity and should stay with the Sales Contact.
As mentioned earlier, object-oriented languages have “identity” operations that determine if two references point to the same object by comparing the objects’ locations in memory.
This kind of identity tracking is too fragile for our purposes.
In most technologies for persistent storage of objects, every time an object is retrieved from a database, a new instance is created, and so the initial identity is lost. Every time an object is transmitted across a network, a new instance is...
This highlight has been truncated due to consecutive passage length restrictions.
When there is no true unique key made up of the attributes of an object, another common solution is to attach to each instance a symbol (such as a number or a string) that is unique within the class.
Once this ID symbol is created and stored as an attribute of the ENTITY, it is designated immutable.
This is why identity-assigning operations often involve human input. Checkbook reconciliation software, for instance, may offer likely matches, but the user is expected to make the final determination.
Because the most conspicuous objects in a model are usually ENTITIES, and because it is so important to track each ENTITY’s identity, it is natural to consider assigning an identity to all domain objects.
Analytical effort is required to define meaningful identities and work out foolproof ways to track objects across distributed systems or in database storage.
An object that represents a descriptive aspect of the domain with no conceptual identity is called a VALUE OBJECT.
In software for a mail-order company, an address is needed to confirm the credit card, and to address the parcel.
But if a roommate also orders from the same company, it is not important to realize they are in the same location.
Address is a VALU...
This highlight has been truncated due to consecutive passage length restrictions.
We don’t care which instance we have of a VALUE OBJECT. This lack of constraints gives us design freedom we can use to simplify the design or optimize performance.
This involves making choices about copying, sharing, and immutability.
The economy of copying versus sharing depends on the implementation environment. Although copies may clog the system with huge numbers of objects, sharing can slow down a distributed system.
When a copy is passed between two machines, a single message is sent and the copy lives independently on the receiving machine. But if a single instance is being shared, only a reference is passed, requiring a message back to the object for each interaction.
Sharing is best restricted to those cases in which it is most valuable ...
This highlight has been truncated due to consecutive passage length restrictions.
In a relational database, you might want to put a particular VALUE in the table of the ENTITY that owns it, rather than creating an association to a separate table. In a distributed system, holding a reference to a VALUE OBJECT on another server will probably make for slow responses to messages; instead, a copy of the whole object should be passed to the other server. We can freely make these copies because we are dealing with VALUE OBJECTS.
But, while bidirectional associations between ENTITIES may be hard to maintain, bidirectional associations between two VALUE OBJECTS just make no sense.
Without identity, it is meaningless to say that an object points back to the same VALUE OBJECT that points to it.
A good SERVICE has three characteristics.
Many domain or application SERVICES are built on top of the populations of ENTITIES and VALUES, behaving like scripts that organize the potential of the domain to actually get something done.
Yet it isn’t just code being divided into MODULES, but concepts. There is a limit to how many things a person can think about at once (hence low coupling).
But just as model objects tend to start out naive and concrete and then gradually transform to reveal deeper insight, MODULES can become subtle and abstract.
Letting the MODULES reflect changing understanding of the domain will also allow more freedom for the objects within them to evolve.
Refine the model until it partitions according to high-level domain concepts and the corresponding code is decoupled as well.
MODULES need to coevolve with the rest of the model. This means refactoring MODULES right along with the model and code.
Such changes can be disruptive to team communication and can even throw a monkey wrench into development tools, such as source code control systems.
As a result, MODULE structures and names often reflect much earlier forms of the model than the classes do.
This kind of framework design is attempting to address two legitimate issues. One is the logical division of concerns:
One object has responsibility for database access, another for business logic, and so on.
This is not a book on framework design, so I won’t go into alternative solutions to that problem, but they do exist. And even if there were no options, it would be better to trade off these benefits for a more cohesive domain layer.
Elaborate technically driven packaging schemes impose two costs.
An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes.
Each AGGREGATE has a root and a boundary. The boundary defines what is inside the AGGREGATE.
The root is a single, specific ENTITY contained in the AGGREGATE. The root is the only member of the AGGREGATE that outside objects are allowed to hold references to, although objects wit...
This highlight has been truncated due to consecutive passage length restrictions.
Intention-Revealing Interfaces
If a developer must consider the implementation of a component in order to use it, the value of encapsulation is lost.
If someone other than the original developer must infer the purpose of an object or operation based on its implementation, that new developer may infer a purpose that the operation or class fulfills only by chance. If that was not the intent, the code may work for the moment, but the conceptual basis of the design will have been corrupted, and the two developers will be working at cross-purposes.
to force your thinking into client developer mode.
In the public interfaces of the domain, state relationships and rules, but not how they are enforced; describe events and actions, but not how they are carried out; formulate the equation but not the numerical method to solve it. Pose the question, but don’t present the means by which the answer shall be found.
point. It is unsatisfying at this point because the code in the test doesn’t tell us what it is doing. Let’s rewrite the test to reflect the way we would like to use the Paint objects if we were writing a client application.
Entire subdomains can be carved off into separate modules and encapsulated behind INTENTION-REVEALING INTERFACES.
Interactions of multiple rules or compositions of calculations become extremely difficult to predict. The developer calling an operation must understand its implementation and the implementation of all its delegations in order to anticipate the result. The usefulness of any abstraction of interfaces is limited if the developers are forced to pierce the veil. Without safely predictable abstractions, the developers must limit the combinatory explosion, placing a low ceiling on the richness of behavior that is feasible to build.
Place as much of the logic of the program as possible into functions, operations that return results with no observable side effects. Strictly segregate commands (methods that result in modifications to observable state) into very simple operations that do not return domain information. Further control side effects by moving complex logic into VALUE OBJECTS when a concept fitting the responsibility presents itself.
SIDE-EFFECT-FREE FUNCTIONS, especially in immutable VALUE OBJECTS, allow safe combination of operations. When a FUNCTION is presented through an INTENTION-REVEALING INTERFACE, a developer can use it without understanding the detail of its implementation.

