Domain-Driven Design Book Club Notes
- 1 Preface
- 1.1 Quotes
- 2 Part 1
- 2.1 Quotes
- 3 Chapter 1 Crunching Knowledge and Chapter 2 Communication and the Use of Language
- 3.1 Quotes
- 3.2 Discussion
- 4 Chapter 3 Binding Model and Implementation and Chapter 4 Isolating the Domain
- 4.1 Quotes
- 4.2 Discussion
- 5 Chapter 5 A Model Expressed in Software
- 5.1 Quotes
- 5.2 Discussion
- 6 Chapter 6 Life Cycle of a Domain Object
- 6.1 Quotes
- 6.2 Questions
- 6.3 Discussion
- 7 Chapter 7 Example of Using the Language & 8 Breakthrough
- 7.1 Discussion
- 8 Chapter 9 Making Implicit Concepts Explicit
- 8.1 Quotes
- 8.2 Discussion
- 9 Chapter 10 Supple Design
- 9.1 Quotes
- 9.2 Discussion
- 10 Chapter 11 Applying Analysis Patterns
- 10.1 Quotes
- 10.2 Discussion
- 11 Part 4 Strategic Design
- 12 Chapter 14 Maintaining Model Integrity (1st half)
- 12.1 Quotes
- 12.2 Discussion
- 13 Chapter 14 Maintaining Model Integrity (2nd half)
- 13.1 Quotes
- 13.2 Discussion
- 14 Chapter 15 Distillation
- 14.1 Quotes
- 14.2 Discussion
- 15 Chapter 16 Large Scale Structures
- 15.1 Quotes
- 15.2 Discussion
- 16 Chapter 17. Bringing the Strategy Together
- 16.1 Quotes
- 16.2 Discussion
Preface
Quotes
"As the team gained new insight into the domain, the model deepened. The quality of communication improved not only among developers but also between developers and domain experts, and the design—far from imposing an ever-heavier maintenance burden—became easier to modify and extend."
"The Extreme Programming process assumes that you can improve a design by refactoring, and that you will do this often and rapidly."
Part 1
Quotes
"It is not just the knowledge in a domain expert’s head; it is a rigorously organized and selective abstraction of that knowledge."
Model Usage
The model and the heart of the design shape each other.
The model is the backbone of a language used by all team members.
The model is distilled knowledge.
Developer motivation
"Domain work is messy and demands a lot of complicated new knowledge that doesn’t seem to add to a computer scientist’s capabilities."
"Instead, the technical talent goes to work on elaborate frameworks, trying to solve domain problems with technology. Learning about and modeling the domain is left to others. Complexity in the heart of software has to be tackled head-on."
"There are systematic ways of thinking that developers can employ to search for insight and produce effective models. There are design techniques that can bring order to a sprawling software application. Cultivation of these skills makes a developer much more valuable, even in an initially unfamiliar domain."
Chapter 1 Crunching Knowledge and Chapter 2 Communication and the Use of Language
Quotes
"Design and Process are inextricable."
“The domain model will typically derive from the domain experts’ own jargon but will have been “cleaned up,” to have sharper, narrower definitions.”
“A document shouldn’t try to do what the code already does well. The code already supplies the detail. It is an exact specification of program behavior. Other documents need to illuminate meaning, to give insight into large-scale structures, and to focus attention on core elements. Documents can clarify design intent when the programming language does not support a straightforward implementation of a concept. Written documents should complement the code and the talking.”
“It takes fastidiousness to write code that doesn’t just do the right thing but also says the right thing.”
Discussion
Chapter 3 Binding Model and Implementation and Chapter 4 Isolating the Domain
Quotes
“Software development is all design. All teams have specialized roles for members, but over separation of responsibility for analysis, modeling, design, and programming interferes with MODEL-DRIVEN DESIGN.”
Example: “A user of Internet Explorer thinks of 'Favorites' as a list of names of Web sites that persist from session to session. But the implementation treats a Favorite as a file containing a URL, and whose filename is put in the Favorites list.”
“If the people who write the code do not feel responsible for the model, or don’t understand how to make the model work for an application, then the model has nothing to do with the software.”
“Every developer must be involved in some level of discussion about the model and have contact with domain experts.”
Discussion
Chapter 5 A Model Expressed in Software
Quotes
Intro
Does an object represent something with continuity and identity - something that is tracked through different states or even across different implementations? Or is it an attribute that describes the state of something else? This is the basic distinction between an ENTITY and a VALUE OBJECT. Defining objects that clearly follow one pattern or the other makes the objects less ambiguous and lays out the path toward specific choices for robust design.
Then there are those aspects of the domain that are more clearly expressed as actions or operations, rather than as objects. Although it is a slight departure from object-oriented modeling tradition, it is often best to express these as SERVICES, rather than forcing responsibility for an operation onto some ENTITY or VALUE OBJECT. A SERVICE is something that is done for a client on request. They emerge .. when some activity is modeled that corresponds to something the software must do, but does not correspond with state.
Associations
There are at least three ways of making associations more tractable:
Imposing a traversal direction
Adding a qualifier, effectively reducing multiplicity
Eliminating nonessential associations
It is important to constrain relationships as much as possible. A bidirectional association means that both objects can be understood only together. When application requirements do not call for traversal in both directions, adding a traversal direction reduces interdependence and simplifies the design. Understanding the domain may reveal a natural directional bias.
Constraining the traversal direction of a many-to-many association effectively reduces its implementation to one-to-many - a much easier design.
Entity
An object defined primarily by its identity is called an ENTITY. ENTITIES have special modeling and design considerations. They have life cycles that can radically change their form and content, but a thread of continuity must be maintained. Their identities must be defined so that they can be effectively tracked. Their class definitions, responsibilities, attributes, and associations should revolve around who they are, rather than the particular attributes they carry. Even for ENTITIES that don't transform so radically or have such complicated life cycles, placing them in the semantic category leads to more lucid models and more robust implementations.
When an object is distinguished by its identity, rather than its attributes, make this primary to its definition in the model. Keep the class definition simple and focused on life cycle continuity and identity.
Value Objects
Software design is a constant battle with complexity. We must make distinctions so that special handling is applied only where necessary. VALUE OBJECTS are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.
When you care only about the attributes of an element of the model, classify it as a VALUE OBJECT. Make it express the meaning of the attributes it conveys and give it related functionality. Treat the VALUE OBJECT as immutable. Don't give it any identity and avoid the design complexities necessary to maintain ENTITIES.
As long as a VALUE OBJECT is immutable, change management is simple - there isn't any change except full replacement. Immutable objects can be freely shared, as in the electrical outlet example. If garbage collection is reliable, deletion is just a matter of dropping all references.
Try to completely eliminate bidirectional associations between VALUE OBJECTS.
Services
A SERVICE is an operation offered as an interface that stands alone in the model, without encapsulating state, as ENTITIES and VALUE OBJECTS do. SERVICES are a common pattern in technical frameworks, but they can also apply in the domain layer. They are intrinsically activities or actions, not things.
A good SERVICE has three characteristics.
The operation relates to a domain concept that is not a natural part of an ENTITY or VALUE OBJECT.
The interface is defined in terms of other elements of the domain model.
The operation is stateless.
It takes care to distinguish SERVICES that belong to the domain layer from those of other layers, and to factor responsibilities to keep that distinction sharp. Layers:
Application service
Domain service
Infrastructure service
Granularity
Medium-grained, stateless SERVICES can be easier to reuse in large systems because they encapsulate significant functionality behind a simple interface. Also, fine-grained objects can lead to inefficient messaging in a distributed system.
As previously discussed, fine-grained domain objects can contribute to knowledge leaks from the domain into the application layer, where the domain object's behavior is coordinated. The complexity of a highly detailed interaction ends up being handled in the application layer, allowing domain knowledge to creep into the application or user interface code, where it is lost from the domain layer
Modules (Packages)
It is a truism that there should be low coupling between MODULES and high cohesion within them. Explanations of coupling and cohesion tend to make them sound like technical metrics, to be judged mechanically based on the distributions of associations and interactions. 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). Incoherent fragments of ideas are as hard to understand as an undifferentiated soup of ideas (hence high cohesion).
..tiered architectures can fragment the implementation of the model objects. Some frameworks create tiers by spreading the responsibilities of a single domain object across multiple objects and then placing those objects in separate packages. At that point, viewing the various objects and mentally fitting them back together as a single conceptual ENTITY is just too much effort.
Unless there is a real intention to distribute code on different servers, keep all the code that implements a single conceptual object in the same MODULE, if not the same object.
Discussion
Chapter 6 Life Cycle of a Domain Object
Quotes
Aggregates, Factories, Repositories
AGGREGATES tighten up the model itself by defining clear ownership and boundaries, avoiding a chaotic, tangled web of objects. This pattern is crucial to maintaining integrity in all phases of the life cycle.
using FACTORIES to create and reconstitute
REPOSITORIES address the middle and end of the life cycle, providing the means of finding and retrieving persistent objects while encapsulating the immense infrastructure involved.
Aggregates
Cluster the ENTITIES and VALUE OBJECTS into AGGREGATES and define boundaries around each. Choose one ENTITY to be the root of each AGGREGATE, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals.
Example: denormalize price to satisfy Purchase Order Aggregate invariant.
Now, to translate that conceptual AGGREGATE into the implementation, we need a set of rules to apply to all transactions.
The root ENTITY has global identity and is ultimately responsible for checking invariants.
Root ENTITIES have global identity. ENTITIES inside the boundary have local identity, unique only within the AGGREGATE.
Nothing outside the AGGREGATE boundary can hold a reference to anything inside, except to the root ENTITY. The root ENTITY can hand references to the internal ENTITIES to other objects, but those objects can use them only transiently, and they may not hold on to the reference. The root may hand a copy of a VALUE OBJECT to another object, and it doesn't matter what happens to it, because it's just a VALUE and no longer will have any association with the AGGREGATE.
As a corollary to the previous rule, only AGGREGATE roots can be obtained directly with database queries. All other objects must be found by traversal of associations.
Objects within the AGGREGATE can hold references to other AGGREGATE roots.
A delete operation must remove everything within the AGGREGATE boundary at once. (With garbage collection, this is easy. Because there are no outside references to anything but the root, delete the root and everything else will be collected.)
When a change to any object within the AGGREGATE boundary is committed, all invariants of the whole AGGREGATE must be satisfied.
Factories
Creation of an object can be a major operation in itself, but complex assembly operations do not fit the responsibility of the created objects. Combining such responsibilities can produce ungainly designs that are hard to understand. Making the client direct construction muddies the design of the client, breaches encapsulation of the assembled object or AGGREGATE, and overly couples the client to the implementation of the created object.
The two basic requirements for any good FACTORY are:
Each creation method is atomic and enforces all invariants of the created object or AGGREGATE. A FACTORY should only be able to produce an object in a consistent state.
The FACTORY should be abstracted to the type desired, rather than the concrete class(es) created.
I refer to the creation of an instance from stored data as reconstitution. A FACTORY used for reconstitution is very similar to one used for creation, with two major differences:
An ENTITY FACTORY used for reconstitution does not assign a new tracking ID. To do so would lose the continuity with the object's previous incarnation.
A FACTORY reconstituting an object will handle violation of an invariant differently.
Repositories
Abstract the type of the object returned
Take advantage of the decoupling from the client.
Leave transaction control to the client.
The FACTORY makes new objects; the REPOSITORY finds old objects.
These two views can be reconciled by making the REPOSITORY delegate object creation to a FACTORY, which (in theory, though seldom in practice) could also be used to create objects from scratch.
One other case that drives people to combine FACTORY and REPOSITORY is the desire for find or create functionality, in which a client can describe an object it wants and, if no such object is found, will be given a newly created one. This function should be avoided.
A table row should contain an object, perhaps along with subsidiaries in an AGGREGATE. A foreign key in the table should translate to a reference to another ENTITY object. The necessity of sometimes deviating from this simple directness should not lead to total abandonment of the principle of simple mappings.
Questions
I wonder why he recommends that the Repository be the primary interface for reconstituting objects - rather than letting the Factory be the primary.