Domain driven design is about focusing on the complexity that is intrinsic to the business domain itself.
Domain driven design is intended for domains with complex business logic, rather than techinical complexity. It is about how to model the domain-specific logic.
Distinguish the core domain, unique to the business, from the supporting subdomains that are generic.
Consists of a set of patterns for building enterprise applications from the domain model out.
Aims to create models of the problem domain, and leave generic problems for later (things like persistence, GUI, and messaging). You need to understand the part of the problem that is unique to your business.
This is not just about making a diagram. All diagrams are views of the model: your business code. At the same time, the ulimate goal of domain driven development is to provide a solution to the business, not to write code.
Focus on one subdomain at a time. Divide and conquer.
A common application architecture is Layered Architecture.
The UI Layer relies on the Business Logic Layer which relies on the Data Layer. All three layers rely on the Intrastructure.
The recommended domain driven design architecture is the Onion Architecture. This is only appropriate for large, long-lived applications, or for applications with complex business behavior.
Onion architecture uses interfaces for behavior contracts, and forces externalization of the infrastructure.
The Domain Model (objects) lies in the center. The Domain Services rely on the Domain Model. The Application Services rely on the Domain Services. This makes up the Application Core.
The UI, Tests, and Infrastructure are all satellites to the Application Code. These outer layer concerns are expected to change often, far more often that the business behavior.
The basic idea of Onion is that Layer X can rely on more-central layers, but never of more-outer layers.
The domain model layer (or just domain layer) is the heart of the business application.
The domain layer is for representing concepts of the business, information about the business situation, and business rules.
A bounded context defines where a model is valid.
For example, an application includes an appointment scheduler and billing. The model "Client" in the scheduler just includes "Name". The model "Client" in billing includes "Credit Card" and "Address". Both "Client" models are part of a different bounded context.
By explicitly dividing these contexts, you can use focused models instead of sharing a single too-large model.
All models are only valid without their contexts. Do not try to make a multi-context model.
A sub domain is a piece of the problem space. A bounded context is a piece of the solution space. Usually, they will correspond to each other.
A diagram showing the relationships among all the bounded contexts, and how each is defined.
Usually shared utilities, like user authentication. Any "cross cutting concerns".
The key differentiator of your client's business. This functionality cannot be outsourced, and must be executed well.
Separate applications or features your software must support or interact with.
A domain model is made up of many object.
An anemic model is focused on the state of its objects. The object don't have much behavior.
For example, objects with many fields/properties and just basic CRUD logic.
The objects contain the business logic directly.
This is recommended in domain driven design.
It is critical to communicate extensively with the domain experts (your clients) throughout the project.
One draw back is that your clients need a lot of time and willingness to talk to you about the project throughout it.
It is critical to communicate using the langauage of your clients. Do not attempt to make them learn your language, they won't. Learn the terminology your clients use, and use that terminology in your documentation, in your database, and in your code.
Always seek confirmation from the client about your understanding of the business. If their terminology is ambiguous, reach a shared agreement with them about what terms mean what.
You may have a different ubiquituous language for different bounded contexts.
Aggregates have the responsibility of ensuring their internal state is always valid. Therefore, data validation must be handled in the Domain. (It may also be handled at higher levels, but it must occur at this lowest level.)
[Microsoft: Domain model layer validations]
"Validations are usually implemented in domain entity constructors or in methods that can update the entity."
How to implement validations?
1) raise an Exception if an error is found
2) use the Specification Pattern to return true/false for IsValid
3) use the Notification Pattern to return a list of all errors found
"
This book provides a framework for making design decisions and a techincal vocabulary for discussin domain design. It is a synthesis of widely accepted best practices along with Evans' insights and experiences.
"
Domain driven design facilitates the iterative invention/discovery of domain models that simplify software development.
A poor domain model can doom a project as well as a lack of technical expertise can.
"
When complexity gets out of hand, developers can no longer understand the the software well enough to change or extend it easily and safely.
"
"
The premise of this book is twofold:
1. For most software projects, the primary focus should be on the domain and domain logic.
2. Complex domain designs should be based on a model.
Domain driven design is both a way of thinking and a set of priorities, aimed at accelerating software projects...
"
This book will discuss both design and process, and their intersection.
To implement domain driven design,
1. Development must be iterative.
2. Developers and domain experts must have a close relationship throughout the entire project.
"
XP works best for developers with a sharp design sense. The XP process assumes that you can improve a design by refactoring, and that you will do this often and rapidly. But past design choices make refactoring either itself easier or harder. The XP process attempts to increase team communication, but model and design choices clarify or confuse communication.
"
Individuals can benefit from reading this book. It will really shine when whole teams read and implement it together.
A model represents a portion of reality or an idea of interest.
The domain of a software project is the subject area to which the user applies the software.
Models are tools for grappling with the breadth and complexity of knowledge of the domain required to implement the software project.
"An appropriate model makes sense of information and focuses it on a problem."
"A domain model is not a particular diagram; it is the idea that the diagram is intended to convey."
"[A domain model] is not just the knowledge in the domain expert's head; it is a rigorously organized and selective abstraction of that knowledge."
"Domain modeling...is loosely representing reality to a particular purpose...a domain modeler chooses a particular model for its utility."
Model Utility:
(1) Model and implementation shape each other. This keeps the model relevant and ensures the analysis that went into it applies to the final product. You should be able to interpret the code based on your understanding of the model.
(2) Ubiquitous Language. The language of the model is used by technical and non-technical people alike. This facilitates communication.
(3) The model is distilled knowledge. It distinguishes elements of most interest. It is a shared understanding of the domain.
"The binding of model and implementation makes experience with early versions of the software applicable as feedback into the modeling process."
"The heart of software is its ability to solve domain-related problems for its user. All other features...support this basic purpose."
Extended example of building a brand new domain model in brainstorming sessions with domain experts.
- Use the language of the domain experts
- Focus on the important parts of the model, based on the current task
- Iterate the design by discussing how processes will use the model
Ingredients of Effective Modeling:
(1) Binding the model and the implementation.
(2) Cultivating a language based on the model.
(3) Developing a knowledge-rich model. Include behavior, rules, etc. It's more than a data schema.
(4) Distilling the model. Don't include what you don't need now.
(5) Brainstorming and experimenting. Rapid iteration with pencil/paper models.
"As the team went through scenarios, the spoken expressions themselves provided a quick viability test of a proposed model, as the ear could quickly detect either the clarity and ease or the awkwardness of expression."
Knowledge Crunching is the sifting, combining, recombining, and distillation of domain knowledge into a concise and useful model that can be applied to your particular problems.
"Knowlege crunching is not a solitary activity. A team of developers and domain experts collaborate..."
You need rapid and constant feedback between developers and domain experts.
Developers need to take an active interest in learning about the domain they are programming for.
It's not just about making Noun Objects (Document, Customer, Order, etc).
Business is all about abstract concepts like Responsibility, Policy (Rule), and step by step Processes.
Example of making domain knowledge clear: Business rule allows shipping to be overbooked to %110.
//bad, hides domain knowledge in a guard clause
public int makeBooking(Cargo cargo, Voyage voyage)
{
double maxBooking = voyage.capacity() * 1.1;
if((voyage.bookedCargoSize() + cargo.size()) > maxBooking)
return -1;
voyage.addCargo(cargo);
return 1;
}
//good, raises domain knowledge up, makes it visible
public class OverbookingPolicy : Policy
{
public bool isAllowed(Cargo cargo, Voyage voyage)
{
return (cargo.size() + voyage.bookedCargoSize()) <= (voyage.capacity() * 1.1);
}
}
public int makeBooking(Cargo cargo, Voyage voyage)
{
if(!OverbookingPolicy.isAllowed(cargo, voyage))
return -1;
voyage.addCargo(cargo);
return 1;
}
Make it easy to connect the code with the requirements.
Make it easy for non-techincal people to understand the high-level code.
Deep Models are models which go beyond surface-level understanding of a domain. They can be surprising, and require many iterations over lots of time to be discovered.
Frustration from your domain experts may mean that you are missing the point or the important part of their business, even if they can't put it into words yet.
"Knowledge crunching is an exploration, and you can't know where you will end up."
"The model is a set of concepts built up in the heads of people on the project, with terms and relationships that reflect domain insight. These terms and interrelationships provide the semantics of a language that is tailored to the domain while being precise enough for technical development."
The model is not limited to what can be expressed by UML diagrams. It pervades every medium of communication: diagrams, requirements, documents, the code, and (especially) verbal communication.
Ubiquitous Language is the terminology used by both domain experts and developers to discuss this particular project and the domain problems it solves. It is a shared team language that develops and changes over time. A shared language means that far less knowledge is lost through translation.
The ubiquitous language must serve all needs, or it will naturally die off. It must express distinctions in the domain, and it must be consistent and precise enough for computers.
- It includes the names of classes and prominent operations.
- It includes terms for discussing business rules.
- It uses the names of patterns applied to the domain model.
- Should be usable when domain experts talk among themselves.
- Should be usable when developers discuss system design.
It takes conscious effort to create and maintain a ubiquitous language.
- Developers must keep their documentation and code and conversation up to date with the language.
- Domain experts must keep their documents and conversation up to date with the language.
The domain model can be the backbone of a ubiquitous language.
"The model relationships become the combinatory rules all languages have. The meanings of words and phrases echo the semantics of the model."
You can't just use the common jargon of the domain experts, because it contains contradictions, overlaps, and imprecise language.
You can't just use technical jargon, because it lacks the semantics of the domain.
- Developers should call out imprecision and ambiguity in domain jargon.
- Domain experts should call out incorrect or inadequate usage of domain jargon.
"Persistent use of the ubiquitous language will force the model's weaknesses into the open...To create a supple, knowledge-rich design calls for a versatile, shared team language, and a lively experimentation with language that seldom happens on software projects...The team will experiment and find alternatives to awkward terms or combinations...As gaps are found in the language, new words will enter the discussion...It is vital that we play around with words and phrases, harnessing our linguistic abilities to the modeling effort..."
Changes to the ubiquitous language are changes to the domain model.
Code will need to be refactored - and it is worth the time-investment to do so.
- rename classes, rename methods, rewrite processes
Putting effort into forming a language will make communication both faster and more precise.
Ex: Say "Routing Specification" instead of "the origin, destination, and arrival time and such..."
"Technical people often feel the need to shield the business experts from the domain model...Of course there are technical components of the design that may not concern the domain experts, but the core of the model had better interest them. Too abstract? How do you know that the abstractions are sound? Do you understand the domain as deeply as they do?...If sophisticated domain experts don't understand the model, there is something wrong with the model."
"...the process of groping towards a shared model begins. It may start out awkward and incomplete, but it will gradually get refined."
Diagrams are good at showing relationships, but not the full context and details of concepts.
Use diagrams along side full text as needed.
Use diagrams that illustrate important points. It is not useful to display so much detail that the diagram is illegible.
You simply cannot communicate the entire design of the project through UML diagrams.
- Too complete: so much detail that the main points are lost.
- Too incomplete: leaves out the larger context, behavior, and constraints.
Having a focused diagram up during discussions helps keep everyone on the same page.
"Diagrams are a means of communication and explanation, and they facilitate brainstorming. They serve these ends best if they are minimal...Comprehensive diagrams of the entire object model fail to communicate or explain; they overwhelm the reader with detail and they lack meaning...This leads us to simplified diagrams of the conceptually important parts of the object model that are essential to understanding the design...I prefer to turn things inside out - rather than a diagram annotated with text, I write a text document illustrated with selective and simplified diagrams."
Use non-standard diagrams when they are useful. UML is very generic and therefore not the best format for every domain.
"The model is not the diagram. The diagram's purpose is to help communicate and explain the model."
The code contains the complete details of the existing design. The diagrams do not need to attempt to replicate that.
"Although the behavior [of code] is unambiguous, that doesn't mean it is obvious...And developers are not the only people who need to understand the model...Well-written code can be very communicative, but the message it communicates is not guaranteed to be accurate...It takes fastidiousness to write code that doesn't just do the right things but also says the right things."
Written documents are useful records. It takes effort to keep them up to date. It is worth the effort for well selected, useful documents.
Documents should complement code and conversation.
"A document shouldn't try to do what the code already does well...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...The greatest value of a design document is to explain the concepts of the model, help navigating the detail of the code, and perhaps to give some insight into the model's intended style of use...If terms explained in a design document don't start showing up in conversations and code, the document is not fulfilling its purpose...People are either not reading it or not finding it compelling."
"The thrust of this book is that one model should underlie implementation, design, and team communication. Having separate models for these separate purposes poses a hazard."
Dangers of designing the model without considering implementation:
- The model has intricate relationships that cannot be implemented in code or stored in a database
- The model misses crucial discoveries about the domain that are made when implementation is taken into account
- The importance of different parts of the model will be misunderstood
If a model cannot be used to write code, then the code and model will diverge. The model will become irrelevant and the knowledge in it will likely be lost.
"...software that lacks a concept at the foundation of its design is, at best, a mechanism that does useful things without explaining its actions."
"An analysis must capture fundemental concepts from the domain in comprehensible, expressive ways...The design has to specify a set of components that can be constructed with the programming tools in use on the project that will perform efficiently in the target deployment environment and will correctly solve the problems posed for the application."
Model Driven Design: integrate the analysis model with code design to create a single model that serves both purposes.
"Each object in the design plays a conceptual role described in the model. This requires us to be more demanding of the chosen model, since it must fulfill two quite different objectives."
There are many possible ways to abstract any given domain. The task is to find one that works both as conceptual analysis and as code design.
"When a model doesn't seem to be practical for implementation, we must search for a new one. When a model doesn't faithfully express the key concepts of the domain, we must search for a new one. The modeling and design process then become a single iterative loop."
All of this effort will make the model relevent to ongoing development.
"The code becomes an expression of the model, so a change to the code may be a change to the model. Its effect must ripple through the rest of the project's activities accordingly."
"Development becomes an iterative process of refining the model, the design, and the code as a single activity...To make a Model Driven Design payoff, the correspondence must be literal, exact within the bounds of human error."
Model Driven Design is better supported by Object Oriented languages than by Procedural languages.
"Letting the bones show: why models matter to users"
A mismatch between the actual model of a system and the model that is presented to the user causes confusion and lost opportunites because the user will attempt incorrect intuitions about what is possible, and will not guess what is actually possible.
"When a design is based on a model that reflects the basic concerns of the users and domain experts, the bones of the design can be revealed to the user to a greater extent than with other design approaches. Revealing the model gives the user more access to the potential of the software and yields consistent, predictable behavior."
The common metaphor that programming is similar to manufacturing causes a lot of problems, because every level of programming involves design decisions, unlike workers on an assemblyline performing the same task repeatedly.
"...overseparation of responsibility for analysis, modeling, design, and programming interfers with Model Driven Design...The effectiveness of an overall design is very sensitive to the quality and consistency of fine-grained design and implementation decisions...Programmers are modelers, whether anyone likes it or not...Every developer must be involved in some level of discussion about the model and have contact with domain experts."
"Certain kinds of decisions keep the model and implementation aligned with each other, each reinforcing the other's effectiveness. This alignment requires attention to the details of individual elements."
"The design style in this book largely follows the principle of Responsibilty Driven Design...and Design By Contract."
"Elaborate models can cut through complexity only if care is taken with the fundementals, resulting in detailed elements that the team can confidently combine."
"The part of the software that specifically solves problems from the domain usually constitutes only a small portion of the entire software system...We need to decouple the domain objects from other functions of the system, so we can avoid confusing the domain concepts with other concepts related only to software technology or losing sight of the domain altogether in the mass of the system."
See Layered Architecture / Onion Architecture / Clean Architecuture.
Briefly, isolate the domain logic and don't make it dependent on any other part of the system.
Discussion of when it is appropriate to not use Layered Architecture: fast, simple projects with non-expert developers. See Smart UI Pattern.
"Associations between objects are simple to conceive and to draw, but implementing them is a potential quagmire...Associations illustrate how crucial detailed implementation decisions are to the viability of a Model Driven Design."
Patterns of model elements:
- Value Object
- Entity
- Service
An Entity has a identity that has continuity even as the state of the object changes, even if all its data changes. An Entity's identity may stretch across multiple systems and representations.
A Value Object merely describes an attribute, a value. They could all be immutable without affecting the system.
Services express actions or operations. Services perform operations upon request. These are the operations that don't make sense inside your Entities or Value Objects. These operations need to be done, but they don't correspond with State.
"A sense of the basic principles will help developers stay on course through the inevitable compromises."
Design Principle: "For every traversable association in the model, there is a mechanism in the software with the same properties."
"It is important to restrain relationships as much as possible...In real life, there are lots of many-to-many associations, and a great number are naturally bidirectional. The same tends to be true of early forms of a model as we brainstorm and explore the domain. But these general associations complicate implementation and maintentance. Furthermore, they communicate very little about the nature of the relationship...A bidirectional association means that both objects can be understood only together."
Options to replace a many-to-many relationship:
1) Impose a 1-to-many relationship on it
2) Reduce multiplicity by adding a condition/filter/qualifier to the relationship
3) Remove it entirely if it is non-essential
"This refinement actually reflects insight into the domain, as well as making a more practical design...It captures the understanding that one direction of the association is much more meaningful and important than the other."
Ex: Country <-> President is bidirectional, but we rarely ask "Which country was X president of?" and we rarly say "Y was president of both A and B countries." So we can simplify this relationship to Country -> President.
"An object should be distilled until nothing remains that does not relate to its meaning or support its role in interactions." (from ch 6)
Entity vs Value Object example:
At a bank, two transactions on the same day, for the same account, for the same amount are Entities because they are distinct from each other despite appearing similar.
The amount of those transactions is a Value Object, because there is no usefullness in distinguishing them from each other or tracking their indentities across time.
Design Principle: Provide all Entities with a way to compare their identity regardless of current state or history. Most commonly, this will be a unique key.
"Identity is not intrinsic to a thing in the world; it is a meaning superimposed because it is useful."
The common theme for these decisions is "what is useful to our purpose?"
"[When modeling Entities,] 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. Beyond that, look to remove behavior and attributes into other objects associated with the core Entity...Entities tend to fulfill their responsibilities by coordinating the operations of objects they own."
"[Value Objects] are the objects that describe things...An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object...We care about what they are, but not who/which they are."
Example of when an Address is a Value Object: The domain is a mail-order company. It needs your address for your parcel. But if your roommate also places an order, it is not important to realize that the two addresses are the same.
Example of when an Address is an Entity: The domain is an electrical company that runs lines to houses/businesses. They do need to realize that both roommates at the same addresses put in an order. (In this case, maybe the Entity is a "Dwelling" which has a Value Object "Address". There are many ways to organize a domain model.)
Value Objects can be made up of other objects. They do not have to be a primitives like a String or Int.
Value Objects can include references to Entities. Ex: a driving route (Value Object) that links two locations (Entities).
Value Objects are frequently transient (never saved in long-term memory).
Value Objects are frequently attributes of Entities.
"The attributes that make up a Value Object should form a conceptual whole. For example, street, city, and postal code shouldn't be separate attributes of a Person object. They are part of a single, whole Address, which makes a simpler Person, and more coherent Value Object."
Why Value Objects should be Immutable:
- Memory can be conserved by sharing Value Objects without risking data corruption.
- Ex: PersonA and PersonB share a Name. PersonA edits their Name by instantiating a new Name object, so that PersonB's Name is not affected.
- A lot of memory can be used up by repeated Value Objects. For instance, imagine every Electrical Outlet in a Floor Plan was a unique object instead of one shared object.
- Value Objects can be passed as arguments without worrying about unintended side effects.
- Ex: PersonA passes their Name into a method. They know the method will not edit the Name because it cannot.
Not all the constraints we define in the model can be enforced with the programming language we are using. In these cases, you must rely on conventions, good naming standards, and good communication.
The associations between Value Objects should be kept simple for the same reasons as for Entities.
With the addendum that there is never a reason to have a bi-directional association between Value Objects. If you're sure you need one, they are probably actually Entities.
"Some concepts from the domain aren't natural to model as objects."
Services are for domain operations that have no proper Entity to belong to.
Services frequently orchestrate interactions between many Entities at once.
Services must be stateless - they can change the state of the system, but must not keep any internal state.
Services are named for verbs instead of for nouns.
- Operation names should still be a part of the ubiquitous language.
- Parameters and results should be domain objects.
"Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping towards procedural programming. But when we force an operation into an object that doesn't fit the object's definition, the object loses its conceptual clarity and becomes hard to understand or refactor...Complex operations can easily swamp a simple object, obscuring its role."
"Services should be used judiciously and not allowed to strip the Entities and Value Objects of all their behavior."
Where is the division between Application Services and Domain Services?
Ex: Given banking software
- ExportReportToCSV is an Application Service because it involves concepts (like file format) that do not exist in the banking domain and does not involve any business rules.
- TransferMoneyBetweenAccounts is a Domain Service because it heavily involves banking domain concepts and business rules.
Domain Services can become a useful medium-grained layer between the application and the fine-grained Entity objects of the domain.
They can form a set of use cases, and can keep business rules from creeping into the application layer.
Modules (aka Packages aka Assemblies) are both a technical division of code, and a conceptual division of the design.
If the domain is divided into multiple Modules, take care that the resulting Modules have low coupling between them and high cohesion within them.
Modules that are divided well will reduce the mental strain of working in the Modules, because they will have low coupling between them, so you can focus on one small part of the code at a time.
"Modules and the smaller elements should coevolve, but typically they do not. Modules are chosen to organize an early form of the objects. After that, the objects tend to change in ways that keep them in the bounds of the existing Module definition...Even developers who refactor a lot tend to content themselves with Modules conceived early in the project...Refactoring Modules is more work and more disruptive than refactoring classes, and probably can't be as frequent. 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."
"Like everything else in a domain-driven design, Modules are a communications mechanism. The meaning of the objects being partitioned needs to drive the choice of the Modules. When you place some classes together in a Module, you are telling the next developer who looks at your design to think about them together."
If you must make trade-offs between conceptual cohesion and technical cohesion, choose greater conceptual clarity. The technical difficulties can be handled when the story the code tells is understandable.
Some frameworks or infrastructures impose module divisions on a project. Do not allow this to complicate your domain. It is more important to have an easily comprehended domain than to follow a particular framework's rules.
Object-oriented programming is a good choice because:
- Objects and relationships correlate well with the domain model.
- It has been proven over time to be easily comprehended by both technical and non-technical people.
- It is widely understood, so you can find experienced programmers.
- There are many support and integration tools already in existance.
A good bit of talk about the usefullness of sticking with Model Driven Design, despite technical obstacles.
"Although a Model Driven Design does not have to be object oriented, it does depend on having an expressive implementation of the model constructs...If the available tool does not facilitate that expressiveness, reconsider the choice of tools...An unexpressive implementation negates the advantage of the extra paradigm."
Rules of thumb for mixing non-object elements into an object-oriented system:
- Don't fight the implementation paradigm. "There's always another way to think about a domain. Find model concepts that fit the paradigm."
- Lean on the ubiquitous language. "Even when there is no rigorous connection between tools, very consistent use of language can keep parts of the design from diverging."
- Don't get hung up on UML. "Sometimes the fixation on a tool, such as UML diagramming, leads people to distort the model to make it fit what can easily be drawn."
- Be skeptical. "Is the tool really pulling its weight? Just because you have some rules, that doesn't necessarily mean you need the overhead of a rules engine."
"Before taking on the burden of mixed paradigms, the options within the dominant paradigm should be exhausted...Even though some domain concepts don't present themselves as obvious objects, they often can be modeled within the paradigm."
Challenges of managing long-lived objects:
- Maintaining integrity throughout the life-cycle.
- Preventing the model from getting swamped by the complexity of managing the life-cycle.
Patterns:
- Aggregates
- Factories
- Repositories
(I'm used to "Aggregate" meaning either a collection, like a list or dictionary, or an operation against a collection, like average or minimum.)
(Would the name "Composite" work better?)
An Aggregate is a cluster of associated objects that are treated as a single unit in regards to edits.
Each Aggregate has a root and a boundary.
This boundary gives hard rules about how much of the data could be affected by editing the Aggregate. This is enormously helpful in maintaining data integrity in a complex project.
The Aggregate root is a single, specific Entity.
The root is the only object in the Aggregate that outside objects are allowed to reference.
Non-root Entities within the Aggregate only require local ids (unique within the Aggregate).
Ex: the domain is a car repair shop.
A car is an Entity and the root of its Aggregate.
The car knows which tires it has, and their rotation history.
The system will never make a global search for a tire. It will only search of a car and ask it about its tires.
If a tire is removed from a car, the system will not care that that particular tire ends up in the junk yard or on another car.
Thus, the tires are part of the car Aggregate.
However, engine blocks have their own serial numbers and are commonly moved from car to car. Therefore, the engine block might be the root of its own Aggregate.
An Invariant is a consistency rule that must be maintained whenever data changes.
Invariants will involve relationships among members of an Aggregate. There rules are expected to stay up-to-date with each transaction.
Invariants that span Aggregates are expected to be out-of-sync sometimes.
Aggregate rules:
- The root Entity has a globally unique id.
- The root Entity is responsible for maintaining Invariants for its Aggregate.
- Non-root Entities in the Aggregate have locally unique ids.
- Objects outside the Aggregate may only refernce the root Entity.
- The root Entity can hand object references out, but they are expected to be short-term, transient references.
- Only root Entities can be obtained directly with database queries.
- If the root Entity is deleted, then everything in the Aggregate must be deleted.
(MongoDb and document-oriented databases in general are already Aggregate-oriented. Aggregate == Document.)
"Much of the power of objects rests in the intricate configuration of their internals and their associations...Problems arise from overloading a complex object with responsibility for its own creation."
Factories are responsible for creating other objects.
Factories encapsulate the creation of complex Entities or Aggregates.
Factories provide a way to divide the creation of an object from the operations that are the purpose of the object.
Each creation method is atomic and enforces all Invariants of the Entity/Aggregate it returns.
Creation methods do not require the client to know what concrete classes will be returned.
"Because cars are never assembled and driven at the same time, there is no value in combining both of these functions into the same mechanism. Likewise, assembling a complex compound object is a job that is best separated from whatever job that object will have to do when it is finished."
"Complex object creation is the responsibility of the domain layer, yet that task does not belong to the objects that express the model...Object creation and assembly usually have no meaning in the domain; they are a necessity of the implementation...We are adding elements [Factories] to the design that do not correspond to anything in the model, but they are nonetheless part of the domain layer's responsibility."
Some Factory design patterns are Factory Method, Abstract Factory, and Builder.
Factory methods frequently exist in the root Entity of the Aggregate affected, or in a highly-related Entity that already contains much of the information needed for the Factory.
A dedicated Factory object or Service should be created if there is no natural Entity to add the Factory Method to.
Use a constructor instead of a Factory when:
- The class is not part of a hierarchy and is not polymorphically used by implementing an interface.
- The client does care which concrete class is returned (such as when using the Strategy Pattern, or when performance is sensitive to implementation selection).
- The client already has all the information needed to create the object.
- The constructor is simple.
"Avoid calling constructors within constructors of other classes. Constructors should be dead simple...The threshold for choosing to use a little Factory Method isn't high."
Does the Invariant logic belong in the Factory or the Entity? It should stay in the Entity.
The Factory can rely on the Entity to validate that its Invariant rules have been satisfied.
- There are exceptions for Aggregate-level Invariant rules. (Such as rules that only apply during creation, and are enforced by the immutabilty of the data after that.)
- But never move Entity Invariant logic into a Factory Method that lives in another object.
Factories can also be used to reassemble a flattened object that was saved to persistent storage.
How to acquire the reference to an object?
- Create the object
- Traverse a series of references to the object
- Query a database to find the object
Reconstitution: restoring an object from a database to memory (distinct from Instantiation when the Entity was first created)
Problems with writing to much save/load logic:
- Your mind is more concerned with the technology than with your domain model.
- Your tempted to bypass object operations and edit the database directly.
- Your tempted to query exactly the data you need instead of traversing to it from an Aggregate root.
- Domain logic becomes embedded in database queries.
The Repository pattern encapsulates all data storage and retrieval concerns so that you can continue concentrating on your domain.
A Repository is not limited to CRUD operations. It encompasses specially designed queries for specific business needs.
"A Repository represents all objects of a certain type as a conceptual set...It acts like a collection, except with more elaborate querying capability. Objects of the appropriate type are added and removed, and the machinery behind the Repository inserts them or deletes them from the database."
"A Repository lifts a huge burden from the client, which can now talk to a simple, intention-revealing interface, and ask for what it needs in terms of the model. To support all this requires a lot of complex techinical infrastructure, but the interface is simple and conceptually connected to the domain model."
Principle: For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type. Provide Repositories only for Aggregate roots that actually need direct access.
A Repository may manage one abstract or parent type, rather than using one Repository for each concrete type.
Developers will still need to understand how the Repository works, so they can take performance issues into account.
The client application will still be in charge of deciding when to commit transactions, as the client understands the full context of the operation.
Keep your Factories and Repositories separate. The Reposistory can use the Factory, but don't merge them together. The Factory is in charge of an object's beginning. The Repository is in charge of an object's middle-of-life.
"The most common non-object component of primarily object-oriented software systems is the relational database. This reality presents the usual problems of a mixture of paradigms. But the database is more intimately related to the object model than are most other components."
- If the database is just for storing the objects, it is worth limiting the object model to what the database can store so that the mapping between them is simple and stays in sync as the object model is refactored.
- If the database is just for storing objects, do not let any other system edit the database. This is likely to break the object invariant rules.
- The database may be partially denormalized to support the object model or performance goals.
- If the data comes from a legacy system, then the database model will be different and separate from the object model.
- The ubiquitous language can help tie the object and the relational components together to a single model.
"The tradition of refactoring that has increasingly taken hold in the object world has not really affected relational database design much. What's more, serious data migration issues discourage frequent changes. This may create a drag on the refactoring of the object model, but if the object model and the database model start to diverge, transparency can be lost quickly."
"Cutting [the database model and the object model] loose from each other is a seductive path. It is often taken unintentionally, when the team fails to keep the database current with the model. If the separation is chosen consciously, it can result in a clean database schema - not an awkward one full of compromises conforming to last year's object model."
Example of building and modifying a domain model.
Example of using the ubiquitous language to talk about user stories.
Example of moving behavior out of one object into its own object to clarify important domain concepts.
Example of turning a many-to-many relationship into a many-to-one by being more specific.
Example of differentiating Entities from Value Objects.
Example of determining the direction of relationships.
Example of when to use bidirectional relationship.
Example of when to use a circular relationship.
Example of drawing in the Aggregate Boundaries.
Example of deciding which Aggregates need Repositories.
Example of how discussing User Stories in terms of the Domain Model informs all of these decisions.
Example of using Constructors and Factories.
Example of refactoring the Design Model as implementation reveals annoyances and/or implementation cannot satisfy User Stories with the current Domain Model.
Example of replacing a persistent object with a derived (transient) object.
Some objects that exist in the Domain Model and the Object Model may not exist in the Database Model. They might be the result of a query.
This example shows how to remove the difficulties of implementing a circular reference by making a Domain Object transient instead of persisted.
A sign of a good design: User Stories that make changes only within one Aggregate Boundary.
Example of common errors in partitioning Modules:
- Partitioning by Entity/Value Object/Service
- Partitioning by Persistent/Transient
- Partitioning by anything that does not take into account the meaning of the objects
Example of Module names becoming part of the Ubiquitous Language.
Example of adjusting to new requirements.
Example of integrating with another system.
Example of naming a Service to be informative.
Example of designing an Anti-Corruption Layer.
"[I've] laid down a foundation for maintaining the correspondence between model and implementation...the real challenge is to actually find an incisive model, one that captures subtle concerns of the domain experts and can drive practical design."
"...a model that captures a deep understanding of the domain...should make the software more in tune with the way the domain experts think and more responsive to the user's needs."
About useful models:
- sophisticated domain models are achievable and worth the trouble.
- they are seldom developed except through an iterative process of refactoring, including close involvement of the domain experts with developers interested in learning about the domain.
- they may call for sophisticated design skills to implement and to use effectively.
"Refactoring is the redesign of software in ways that do not change its functionality...Rather than making elaborate up-front design decisions, developers take code through a continuous series of small, discrete design changes, each leaving existing functionality unchanged while making the design more flexible or easier to understand."
"But nearly all the literature on how to refactor focuses on mechanical changes to the code that make it easier to read or to enhance at a very detailed level...it is primarily a technical view of the quality of a design."
"The refactorings that have the greatest impact on the viability of the system are those motivated by new insights into the domain or those that clarify the model's expression through the code...The goal is not only can a developer understand what the code does; he or she can also understand why it does what it does and can relate that to the ongoing communication with the domain experts."
It is not possible to compile a cookbook of domain-level refactorings, as they are each so particular to one project.
"Modeling is as inherently unstructured as any exploration...Refactoring to deeper insight should follow wherever learning and deep thinking lead...Modeling and design call for creativity."
"The traditional way of explaining object analysis involves identifying nouns and verbs in the requirements documents and using them as the initial objects and methods. This explanation is recognized as an oversimplification...[but] initial models usually are naive and superficial, based on shallow knowledge."
Example of how a model changed from (Ship, Container) to (Vessel Voyage, Bill of Lading, etc).
"A deep model provides a lucid expression of the primary concerns of the domain experts and their most relevant knowledge while it sloughs off the superficial aspects of the domain."
"Versatility, simplicity, and explanatory power come from a model that is truly in tune with the domain."
"A model-driven design stands on two legs. A deep model makes possible an expressive design. At the same time, a design can actually feed insight into the model discovery process when it has the flexibility to let a developer experiment and the clarity to show a developer what is happening...This half of the feedback loop is essential, because the model we are looking for is not just a nice set of ideas: it is the foundation of the system."
"You will usually depend on creativity and trial and error to find good ways to model the concepts you discover, but sometimes someone has laid down a pattern you can follow." (see Analysis Patterns)
"The returns from refactoring are not linear...some of the most important insights come abruptly and send a shock through the project."
Most refactorings will be small. Occasionally, an insight will be revealed that enables a massive change to the model.
"Each refinement of code and model gives developers a clearer view. This clarity creates the potential for a breakthrough of insights...A rush of change leads to a model that corresponds on a deeper level to the realities and priorities of the users...Versatility and explanatory power suddenly increase even as complexity evaporates."
"This sort of breakthrough is not a technique; it is an event...Don't become paralyzed trying to bring about a breakthrough. The possibility usually comes after many modest refactorings."
Sounds like enlightment being an accident, which meditation makes more likely.
Example of a real project experiencing a breakthrough.
Model (Investment, Load Investment, Loan Adjustment) becomes (Share Pie, Share, Percent Pie, Amount Pie).
"Suddenly, on the basis of this new way of looking at the domain, we could run through every scenario we had ever encountered relatively effortlessly, much more simply than ever before...And our model diagrams made perfect sense to the business experts, who had often indicated that the diagrams were 'too technical' for them."
Having a brilliant insight will require a lot of changes to the code. This will have to be scheduled like any other task.
It will probably not be possible to complete the changes as a series of small refactorings - rather you'll need some large single-edits.
"When the prospect of a breakthrough to a deeper model presents itself, it is often scary. Such a change has higher opportunity and higher risk than most refactorings."
"To set the stage for a breakthrough, concentrate on knowledge crunching and cultivating a robust Ubiquitous Language. Probe for important domain concepts and make them explicit in the model. Refine the design to be suppler. Distill the model. Push on these more predictable levers, which increase clarity - usually a precursor of breakthroughs."
"Don't hold back from modest improvements, which gradually deepen the model, even if confined within the same general conceptual framework...Don't be paralyzed by looking too far forward."
"A deep model has power because it contains the central concepts and abstractions that can succinctly and flexibly express essential knowledge of the users' activities, their problems, and their solutions."
The model progresses when a concept that turned up in discussions becomes an explicit object or relationship in the model.
"The process starts with recognizing the implied concepts in some form, however crude."
How to identify these implicit concepts?
- listen to the language of the team
- is there a succint term that encapsulates something complicated?
- do the experts keep correcting your word choice?
- do the users use words that are not in your model at all?
- scrutinize awkwardness in the design
- scrutinize seeming contradictions in the statements of experts
- search the literature of the domain - read the book
- experiment with the model
"Hearing a new word produces a lead, which you follow up with conversation and knowledge crunching, with the goal of carving out a clean, useful concept."
Example: a result that is compiled from multiple objects, may turn out to be an important object in its own right.
Example: all the work is being done, but the placement of the actions in objects feels awkward.
Example: continuing the Shipping project.
"The place to dig is the most awkward part of your design. The place where procedures are doing complicated things that are hard to explain. The place where every new requirement seems to add complexity."
Example: project about earning interest on assets.
"Pesky contradictions, which we encounter all the time when digging into program requirements, can be great clues to deeper models...Some are just variations in terminology or are based on misunderstanding. But there is a residue where two factual statements by experts seem to contradict."
"It is not practical to reconcile all contradictions, and it may not even be desirable."
"The examples I've given don't convey the amount of trial and error involved. I might follow half a dozen leads in conversation before finding one that seems clear and useful enough to try out in the model...A modeler/designer cannot afford to get attached to his own ideas."
"All these changes of direction are not just thrashing. Each change embeds deeper insight into the model. Each refactoring leaves the design more supple, easier to change the next time, ready to bend in the places that turn out to need to bend."
What to make explicit?
- Constraints
- Policies
- Processes
- Specifications
Examples of Specifications implementations and patterns (Chemical Warehouse Packer).
"The ultimate purpose of software is to serve users. But first, that same software has to serve developers...People have to work with this stuff [the code]. But will they want to?"
"Duplication starts to appear as soon as a developer isn't confident of predicting the full implications of a computation...Duplication is forced when design elements are monolithic, so that the parts cannot be recombined."
The worse code is to work with, the slower the project will progress. To enable continuous fast delivery, you must maintain clean code with a supple design.
"Supple design is the complement to deep modeling. Once you've dug out implicit concepts and made them explicit, you have the raw material."
"A lot of overengineering has been justified in the name of flexibility. But more often than not, excessive layers of abstraction and indirection get in the way...Look at the design of software that really empowers the people who handle it; you will usually see something simple. Simple is not easy."
"To be open to change, a design must be easy to understand, revealing the same underlying model that the client developer is drawing on. It must follow the contours of a deep model of the domain, so most changes bend the design at flexible points. The effects of its code must be transparently obvious, so the consequences of a change will be easy to anticipate...There is no formula for designing software like this."
Useful patterns that help create a Supple Design:
- Ubiquitous Language
- Model-Driven Design
- Intention-Revealing Interfaces
- Side-Effect-Free Functions (see Nullipotent Functions)
- Assertions
- Stand Alone Classes
- Conceptual Contours
- Closure of Operations
Abstractions that do not clearly state their effects are not valuable, because you must read within the abstraction to understand what it is doing. Developers should not need to dig into the internals of objects/modules, but they frequently do because the interface is not communicative.
"We are always fighting cognitive overload: If the client developer's mind is flooded with detail about how a component does its job, his mind isn't clear to work out the intricacies of the client design...If a developer must consider the implementation of a component in order to use it, the value of encapsulation is lost."
When purpose is inferred by another developer later, they may infer that a coincidental side effect is promised functionality, and write their code to rely on functionality that becomes transitory.
"To obtain the value of explicity modeling a concept in the form of a class or method, we must give these program elements names that reflect those concepts. The names of classes and methods are great opportunities for improving communication between developers, and for improving the abstraction of the system...All public elements of a design together make up its interface, and the name of each of those elements presents an opportunity to reveal the intention of the design...Type names, Method names, and Argument names all combine to form an Intention-Revealing Interface."
"Name classes and operations to describe their effect and purpose, without reference to the means by which they do what they promise...These names should conform to the Ubiquituous Language so that team members can quickly infer their meaning...Pose the question, but don't present the means by which the answer shall be found."
(see Declarative Languages)
Ongoing example: Paint Mixer.
This book uses terms Commands/Modifiers (functions with side-effects) and Queries/Functions (functions without side-effects, i.e. Nullipotent functions).
This book follows the standard usage of Side-Effect in programming: a Side-Effect is any change to the system, whether it was intentional or not.
Nullipotent functions are safe to call and combine.
Nullipotent functions are easier to test.
Nullipotent functions lower risk.
Separating functionality into Nullipotent functions where possible will improve the design of code by making it clear which operations are safe.
This decreases the cognitive load on developers and increases their power in the system.
It is suggested that functions with side-effects should not return domain data. (Considering this, it brings up performance questions.)
Using Value Objects as immutable objects helps you make more of your functions into Nullipotent functions, because operations will return a new object instead of modifying an existing one.
"Place as much of the logic of the program as possible into [nullipotent] functions...Structuraly segregate [non-nullipotent] commands 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."
"The developer using the high-level command must understand the consequences of each underlying command. So much for encapsulation...And because object interfaces do not restrict side effects, two subclasses that implement the same interface can have different side effects. The developer using them will want to know which is which to anticipate the consequences. So much for abstraction and polymorphism...The necessity of tracing concrete execution defeats abstraction."
Assertions:
- make side effects explicit
- part of Design By Contract
- Post Conditions describe the side effects of the operation
- Pre Conditions describe what must be true for the Post Conditions to be trusted
- Class Invariants make Assertions about the state of the object at the end of any operation
- Assertions to include the effects of delegation (down stream side effects)
- this could require more validation during refactoring - if one down stream side effect is removed, you must update all Assertions up the stack and verify that this was the only source of that side effect
"All these assertions describe state, not procedures, so they are easier to analyze."
"If you trust the guarantee of a Post Condition, you don't have to worry about how a method works."
"If Assertions cannot be coded directly in your programming language, write automated unit tests for them. Write them into documentation...Seek models with coherent sets of concepts, which lead a developer to infer the intended Assertions..."
Conceptual Contours
"There is a logical consistency deep in most domains, or else they would not be viable in their own sphere...There is a rhyme and reason somewhere, or else modeling would be pointless. Because of this underlying consistency, when we find a model that resonates with some part of the domain, it is more likely to be consistent with other parts that we discover later...This is one reason why repeated refactorings eventually leads to suppleness."
"The conceptual contours emerge as the code is adapted to newly understood concepts or requirements."
"The twin fundamentals of high cohesion and low coupling play a role in design at all scales, from individual methods up through classes and modules to large-scale structures. These two principles apply to concepts as much as to code...To avoid slipping into a mechanistic view of them, temper your technical thinking by frequently touching base with your intuition for the domain...Find the conceptually meaningful unit of functionality, and the resulting design will be both flexible and understandable."
"Observe the axes of change and stability through successive refactorings and look for the underlying conceptual contours that explain these shearing patterns."
"The goal is a simple set of interfaces that combine logically to make sensible statements in the uiquitous language, and without the distraction and maintenance burden of irrelevant options."
"Encountering a requirement that forces extensive changes in the breakdown of the objects and methods is a message: our understanding of the domain needs refinement."
Example: continues with Accruals/Loans.
"This ease of extension did not come because she anticipated the change. Nor did it come because she made a design so versatile it could accommodate any conceivable change. It happened because in the previous refactoring, the design was aligned with underlying concepts of the domain."
Standalone Classes
"Interdependencies make models and designs hard to understand. They aslso make the hard to test and maintain. And interdependencies pile up easily."
"Every association is a dependency, and understanding a class requires understanding what it is attached to...the type of every argument...every return value..."
"Both modules and aggregates are aimed at limiting the web of interdependecies."
One big reason to limit interdepencies is to not overload the developer mentally. Massive webs of interdependent objects are hard to understand and reason about.
"Implicit concepts contribute to this [mental] load even more than explicit references."
A standalone class is a class with zero dependencies (except for primitives and basic library functions, which do not add to mental load).
- allowing dependencies on primitives, such as integers, does not mean ignoring the meaning of those variables
- see Paint Mixing example, and pulling Pigment Color out into its own class
"Every dependency is suspect until proven basic to the concept behind the object. This scrutiny starts with the factoring of the model concepts themselves. Then it requires attention to each individual association and operation. Model and design choices can chip away at dependencies, often to zero."
Dependencies on classes within the same module are less of a burden than dependencies on external classes.
When two classes are tightly coupled, even more coupling can actually clarify their relationship.
"The goal is not to eliminate all dependencies, but to eliminate all nonessential ones."
Closure of Operations
This term comes from mathematics. For example, addition is an operation closed over the set of real numbers. 1 + 1 = 2. Add any two real numbers together, and the result will also be a real number.
"Wherever it fits, define an operation whose return type is the same as the type if its arguments...This pattern is most often applied to the operations of a Value Object."
"A closed operation provides a high-level interface without introducing any dependency on other concepts."
"These techniques require fairly advanced design skills to apply and sometimes even to write a client. The usefulness of a Model Driven Design is sensitive to the quality of the detailed design and implementation decisions, and it only takes a few confused developers to derail a project from the goal."
Declarative Design
"This term means many things to many people, but usually it indicates a way to write a program, or some part of a program, as a kind of executable specification...This can be done through a reflection mechanism or at compile time through code generation...This approach allows another developer to take the declaration at face value."
"Generating a running program from a declaration of model properties is a kind of Holy Grail of Model Driven Design."
Problems with pure Declarative Design
- the declarative language does not do everything you need, and the framework is difficult or impossible to extend
- code generation destroys manually-written code
- in response to these limitations, developers dumb down the model and application
Problems with Rule Engines
- most systems are not purely Declarative because they include "control predicates" to support performance tuning. this introduces side-effects.
- adding/removing/reordering rules causes incorrect results
"Many declarative approaches can be corrupted if the developers bypass them intentionally or unintentionally. This is likely when the system is difficult to use or overly restrictive."
"The greatest value I've seen delivered has been when a narrowly scoped framework automates a particularly tedious and error-prone aspect of the design, such as persistence and object-relational mapping...The best of these unburden developers of drudge work while leaving them complete freedom to design."
Domain Specific Languages
"...client code is written in a programming lanuage tailored to a particular model of a particular domain. For example, a language for shipping systems might include terms such as CARGO and ROUTE, along with syntax for associating them...The program is then compiled, often into a conventional object-oriented language, where a library of classes provides implementations for the terms in the language."
Problems with Domain Specific Languages
- to refine the model, the developer must edit the programming language - designing and editing a programming language is an advanced skill
- loses the value of an application and model implemented in the same language
Declarative Style
The more you use intention-revealing interfaces, side-effect-free functions, and assertions, the closer you are to being Declarative.
Returning to the Chemical Packing example to look at Specification patterns
- design that uses trees of objects for combining AND/OR/NOT logic (a basic object-oriented design)
- design that uses a flat array of objects interpreted as a logic tree (in response to memory constraints)
How to get started on making a design more supple
- carve out a subdomain and work just on that part
- draw on established formalisms (such as Accounting or Boolean Logic)
Detailed example of refactoring Loans/Shares
"Complex logic is encapsulated in specialized Value Objects with side-effect-free functions...State-modifying operations are simple and characterized with Assertions...Model concepts are decoupled; operations entangle a minimum of other types...Familiar formalisms make the protocol easy to grasp."
(From Fowler's "Analysis Patterns") "Analysis Patterns are groups of concepts that represent a common construction in business modeling. It may be relevant to only one domain or it may span many domains."
These patterns are starting points, based on the trial and error of others. They are not expected to be complete, finished designs appropriate to every project.
"To discuss model idea out of that context makes them harder to apply and risks opening the deadly divide between analysis and design, which is antithetical to Model Driven Design...The principle and application of analysis patterns can be explained better by example than through abstract description."
"In this chapter, I will give two examples of developers making use of a small, representative sample of models fro the chapter 'Inventory and Accounting' in Fowler 1997...The point is to illustrate their integration into the Domain Driven Design process."
Example: Earning interest with accounts
"Sometimes there are parts of our programs that we don't even suspect have the potential to benefit from a domain model. They may have started very simply and evolved mechanistically. They seem like complicated application code, rather than domain logic. Analysis patterns can be particularly helpful in showing us these blind spots."
Example: Posting rules
"When you are lucky enough to have an analysis pattern, it hardly ever is the answer to your particular needs. Yet it offers valuable leads in your investigation, and it provides cleanly abstracted vocabulary."
To reiterate - analysis patterns are excellent sources of vocabulary.
Warning - do not change the usage of well-defined terms.
"It should also give you guidance about implementation consequences that will save you pain down the road."
"Sometimes the result doesn't even obviously relate to the analysis pattern itself, yet was stimulated by the insights from the pattern."
"What is the difference between a design pattern and a domain pattern?"
(From 'Design Patterns' by Gamma/etc) "Point of view affects one's interpretation of what is and isn't a pattern. One person's pattern can be another person's primitive building block. For this book we have concentrated on patterns at a certain level of abstraction. Design patterns are not about designs such as linked lists and hash tables that can be encoded in classes and reused as is. Nor are they complex, domain-specific designs for an entire application or subsystem. The design patterns in this book are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context."
"Some, not all, of the patterns in 'Design Patterns' can be used as domain patterns. Doing so requires a shift in emphasis...because they correspond to general concepts that emerge in many domains...On one level, they are techinical design patterns in the code. On the other level, they are conceptual patterns in the model."
"A sample of specific patterns from 'Design Patterns' will show how a pattern conceived as a design pattern can be applied in the domain model...it will clarify the distinction between a technical design pattern and a domain pattern."
Example: Strategy aka Policy pattern
- back to the Shipping project
It's a design pattern when implemented in the code.
It's also a domain pattern when it makes explicit an important distinction in a domain process.
Example: Composite pattern
- back to the Shipping project
"A design pattern should be applied only when it is needed."
"Flyweight is a good example of a design pattern that has no correspondence to the domain model...This is an implementation option available for Value Objects and not for Entities...Contrast this with Composite, in which conceptual objects are composed of other conceptual objects. In that case, the pattern applies to both model and implementation, which is an essential trait of a domain pattern."
Things to focus on:
- Live in the domain
- Keep looking at things a different way
- Maintain an unbroken dialog with domain experts
"Seeking insight into the domain creates a broader context for the process of refactoring."
How it starts:
- There is some complexity or awkwardness in the code, and the root of the problem is identified as the Domain
- The code is tidy, but the model language is disconnected from the domain experts
- The code is tidy, but new functionality is not fitting in naturally
- You've learned something new about the domain
"Seeing the trouble spot is often the hardest and most uncertain part. After that, developers can systematically seek out the elements of a new model."
"The initiators of the change pick a couple of other developers who are good at thinking through that kind of problem, who know that area of the domain, or who have strong modeling skills. If there are subtleties, they make sure a domain expert is involved."
- Brainstorm
- Sketch diagrams
- Walk through scenarios
Key to this process:
- Self Determination - a small team self-assembles for a few days only
- Scope and Sleep - a few short meeting spaced over a few days only
- Exercising the Ubiquitous Language
Don't reinvent the wheel - pull ideas from any source available.
"As the pieces are fit together, model concerns and design concerns must be dealt with in parallel...Design patterns can often be employed in the domain layer when they fit both an implementation need and the model concept."
"Software isn't just for users. It's also for developers." See intention-revealing interfaces, supple design, etc.
"If you wait until you can make a complete justification for a change, you've waited too long...Your project is already incurring heavy costs, and the postponed changes will be harder to make..."
"Continuous refactoring has come to be considered a best practice, but most project teams are still too cautious about it. They see the risk of changing code and the cost of developer time to make a change; but what's harder to see is the risk of keeping an awkward design and the cost of working around that design...Developers who want to refactor are often asked to justify the decision. Although this seems reasonable, it makes an already difficult thing impossibly difficult..."
Refactor when:
- the design does not express the team's understanding of the domain
- important concepts are only implicit
- you see an opportunity to make an important part of the design suppler
"This aggressive attitude does not justify any change at any time."
Don't refactor:
- the day before a release
- just to show off
- when the domain experts disagree with the model
- just to follow a strict rule
Refactoring tends to occur in waves, rather than steadily. These intense times might look more like crisis than opportunity.
"As systems grow too complex to know completely at the level of individual objects, we need techniques for manipulating and comprehending large models."
"[Such design] decisions must be made at team level or even negotiated between teams. These are the decisions where design and politics often intersect."
The challenge is to keep a single, integrated, company-wide model while also breaking in into manageable pieces that can be assigned to different teams.
"A monolithic, all-encompassing domain model will be unwieldy and loaded with subtle duplications and contradictions. A set of small, distinct subsystems glued together with ad hoc interfaces will lack the power to solve enterprise-wide problems and allows consistency problems to arise at every integration point."
Be wary of re-purposing existing classes to new uses. Especially if the usage is only a /little/ different (as this is the most tempting). It is better to make two distinct classes with clearly defined usages.
Explicitly defining Bounded Contexts will help you keep these almost-duplicated classes apart. Every team may have their own "Charge" class inside their own Bounded Context, and each one can be used differently without confusion.
"Although we seldom think about it explicitly, the most fundamental requirement of a model is that it be internally consistent; that its terms always have the same meaning, and that it contain no contradictory rules...The internal consistency of a model, such that each term is unambiguous and no rules contradict, is called Unification."
"But the world of large systems development is not the ideal world. To maintain that level of unification in an entire enterprise system is more trouble than it is worth...Total unification of the domain model for a large system will not be feasible or cost effective."
"It is necessary to allow multiple models to develop in different parts of the system, but we need to make careful choices about which parts of the system will be allowed to diverge and what their relationship to each other will be...None of this happens by itself or through good intentions. It happens only through conscious design decisions and institution of specific processes."
The cost of having multiple models:
- limits integration
- makes communication more difficult
- is inelegent
The cost of combining all models into one:
- if you refactor too quickly, you'll introduce errors
- the coordination effort can be too onerous to get anything else done
- teams may make compromises that damage their projects
- either by limiting their own functionality
- or by making the final model too complicated
- political differences can affect the model
- management priorities can affect the model
It's interesting to see "office politics" given as a reason to divide a model.
"Through a combination of proactive decisions about what should be unified and pragmatic recognition of what is not unified, we can create a clear, shared picture of the situation...We can set about making sure that the parts we want to unify stay that way, and the parts that are not unified don't cause confusion or corruption...We need to chose our strategy consciously and then follow our strategy consistently."
High level overview:
- map the current terrain of the project
- divide the total modal up into Bounded Contexts
- a high level Context Map documents all the Bounded Contexts and how they relate to each other
- practice Continuous Integration to keep all the Bounded Contexts working together
- some closely linked contexts will have a Shared Kernel, others will split apart and go their Seperate Ways
Bounded Contexts
"Cells can exist because their membranes define what is in and out and determine what can pass."
"Different models apply in different contexts."
- integrating with code outside your control probably calls for a different model
- even small projects can generate conflicts over which part of the model is shared and which is not
"Multiple models are in play on any large project...Failure to keep things straight is ultimately revealed when the running code doesn't work right, but the problems starts in the way teams are organized and the way people interact."
"A model applies in a Context. The context may be a certain part of the code, or the work of a particular team. For a model invented in a brainstorming session, the context could be limited to that particular conversation...The model context is whatever set of conditions must apply to in order to be able to say that the terms in a model have a specific meaning."
"Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas...Keep the model strictly consistent within these bounds, but don't be distracted or confused by issues outside...By drawing an explicit boundary, you can keep the model pure, and therefore potent, where it is applicable."
"In other contexts, other models apply, with differences in terminology, in concepts and rules, and in dialects of the Ubiquitous Language...Integration across the boundaries necessarily will involve some translation, which you can analyze explicitly."
(How is the language Ubiquitous if it has dialects?)
Example: Booking Context (back to the Shipping company)
- if teams do not coordinate carefully and consciously, they will de facto be working in different Contexts
- if the database schema design is driven by the model design, then the database schema is in the same Context
- "This Bounded Context is made up of all those aspects of the system that are driven by this particular model..."
- for the teams in the Bounded Context, they gain clarity
- for the teams outside the Bounded Context, they gain freedom
Recognizing conceptual splinters within a Bounded Context:
- code interfaces that don't match up
- unexpected behavior
- confusion in the language
Duplicate concepts: you have two model elements (with implementation details) that represent the same concept. Every time one changes, the other has to also.
False cognates: you have one model element being used in two different ways. Usually this happens when two different people/teams are making changes to the same object, and they have slightly different ideas about what that object should do. This leads to difficult-to-discover bugs.
Continuous Integration
"When a number of people [more than two] are working in the same Bounded Context, there is a strong tendency for the model to fragment...Having defined a Bounded Context, we must keep it sound."
"Sometimes developers do not fully understand the intent of some object or interaction modeled by someone else, and they change it in a way that makes it unusable for its original purpose. Sometimes they don't realize that the concepts they are working on are already embodied in another part of the model and they duplicate (inexactly) those concepts and behavior...It is very hard to maintain the level of communication needed to develop a unified system of any size...We need ways of increasing communication and reducing complexity...We also need safety nets that prevent overcautious behavior..."
"Many XP practices are aimed at this specific problem of maintaining a coherent design that is being constantly changed by many people. XP in its purest form is a nice fit for maintaining model integrity within a single Bounded Context."
"...It is essential to have some process of Continuous Integration...[meaning] that all work within the context is being merged and made consistent frequently enough that when splinters happen they are caught and corrected quickly."
Levels of Continuous Integration:
- The integration of model concepts though communication with team members. Constantly practice your Ubiquitous Language.
- The integration of the implementation through an automated merge/build/test process.
Successful Continuous Integration Processes:
- have a step-by-step, reproducible merge/build technique
- have automated testing suites
- have rules that put an upper limit on how long changes can remain unintegrated
- constantly exercise their Ubiquitous Language in discussions of the model and application
"Continuous Integration is essential only within a Bounded Context. Design issues involving neighboring Contexts, including translation, don't have to be dealt with at the same pace."
Context Map
"When connections must be made between different Contexts, they tend to bleed into each other."
"A Context Map is the overlap between project management and software design."
"The Context Map charts the territory, giving the big picture of the Contexts and their connections...The relationships between a Bounded Context and its neighbors require care and attention...You can reduce confusion by defining the relationship between the different Contexts and creating a global view of all the model contexts on the project."
"The natural course of events is for the boundaries to follow the contours of team organization. People who work closely will naturally share a model context...Physical office space can have an impact too...But the interrelationship between team organization and software model and design is still not prominent enough."
"Code reuse between Bounded Contexts is a hazard to be avoided."
"Identify each model in play on the project and define its Bounded Context. This includes implicit models of non-object-oriented subsystems...Name each Bounded Context, and make the name part of the Ubiquitous Language...Describe the points of contact between the models, outlining explicit translation for any communication and highlighting any sharing...Map the existing terrain. Take up transformations later...Whatever form the map takes, it must be shared and understood by everyone on the project."
"Within each Bounded Context, you will have a coherent dialect of the Ubiquitous Language...You can speak unambiguously about the model of any part of the design by making your Context clear."
"[Don't let necessary repair] lead to wholesale reorganization. Until you have an unambiguous Context Map that places all your work into some Bounded Context, with explicit relationships between all connected models, change only the outright contradictions."
Example: Shipping Application with two Contexts
- a new Bounded Context is created so that a difficult operation can use its own data structures and algorithms
- translations at boundaries should be easily testable, and the teams on each side should collaborate on building the test suite
"Model contexts always exist, but without conscious attention they may overlap and fluctuate."
"Contact points with other Bounded Contexts are particularly important to test...Tests help compensate for the subtleties of translation and the lower level of communication that typically exists at boundaries."
Organizing and Documenting Context Maps
- each Bounded Context should have a name that is part of the Ubiquitous Language
- everyone has to know where the Boundaries are; therefore, everyone will know what Context a piece of code is a part of
Ubiquitous Language example:
- instead of saying "George's team's stuff is changing, so we're going to have to change our stuff that talks to it"
- say "the Transport Network model is changing, so we're going to have to change the translator for the Booking context"
The following are some design patterns for how two Bounded Contexts can relate to each other.
Shared Kernel
A Shared Kernel is a Context that is shared by multiple other Contexts. A common example is an infrastructure framework (created by an internal team) which is used by several other teams. Another common example is the Core Domain.
"Designate some subset of the domain model that the two teams agree to share. Of course this includes, along with this subset of the model, the subset of code or of the database design associated that part of the model. This explicitly shared [kernel] has special status, and shouldn't be changed without consultation with the other team."
"It is a careful balance. The shared kernel cannot be changed as freely as other parts of the design. Decisions involve consultation with another team. Automated test suites must be integrated because all tests of both teams must pass when changes are made."
"The goal is to reduce duplication...and make integration between the two subsystems relatively easy."
Customer/Supplier Development Teams
"Often one subsystem essentially feeds another; the 'downstream' component performs analysis or other functions that feed back very little into the 'upstream' component, and all dependencies go one way...The two subsystems commonly serve very different user communities, who do different jobs, where different models may be useful...Upstream and downstream subsystems separate naturally into two Bounded Contexts."
"Translation is easier for having to operate in one direction only."
Possible problems:
- "The freewheeling development of the upstream team can be cramped if the downstream team has veto power over changes, or if procedures for requesting changes are too cumbersome."
- "The upstream team may even be inhibited, worried about breaking the downstream system."
- "The downstream team can be helpless, at the mercy of upstream priorities."
- "Downstream needs things from upstream, but upstream is not responsible for downstream deliverables."
"The process can be organized to balance the needs of the two user communities and schedule work on features needed downstream...On an XP project, there already is a mechanism in place for doing just that: the iteration planning process...Whatever analogous method serves to balance the concerns of different users can be expanded to include the downstream application's needs."
"Establish a clear customer/supplied relationship between the two teams...In planning sessions, make the downstream team play the customer role to the upstream team...Negotiate and budget tasks for downstream requirements so that everyone understands the commitment and schedule."
"Jointly develop automated acceptance tests that will validate the interface expected...This testing will free the upstream team to make changes without fear of side effects downstream...Automating the acceptance tests is a vital part of this customer relationship."
"The relationship must be that of customer and supplier, with the implication that the customer's needs are paramount...[not] the poor-cousin relationship that often emerges, in which the downstream team has to come begging to the upstream team for its needs."
Example: Yield Analysis vs Booking
- teams using different implementation technologies points you more toward Customer/Supplier than Shared Kernel
- analysis vs operations teams often fall into this Customer/Supplier relationship
"Customer/Supplier teams are more likely to succeed if the two teams work under the same management, so that ultimately they do share goals, or where they are in different companies that actually have those roles."
Conformist
"When two development teams have an upstream/downstream relationship in which the upstream has no motivation to provide for the downstream team's needs, the downstream team is helpless...The downstream project will be delayed until the team ultimately learns to live with what it is given."
Options:
- abandon use of the upstream altogether (go your Separate Ways)
- use the upstream, but develop your own model to fix awkwardness or lack on encapsulation - take full responsibility for the translation (see Anti Corruption Layer)
- use the upstream, and use their model (Conformist)
"Eliminate the complexity of translation between Bounded Contexts by slavishly adhering to the model of the upstream team...Choosing conformity enormously simplifies integration...[And] you will share a Ubiquitous Language with your supplier team."
"It is very unappealing emotionally, which is why we choose it less often than we probably should."
"Where the Shared Kernel is a collaboration between two teams that coordinate tightly, Conformist deals with integration with a team that is not interested in collaboration."
Anti Corruption Layer
"Now we'll take a final step to an even more pessimistic view of the relationship, assuming neither cooperation nor a usable design on the other side..."
"New systems almost always have to be integrated with legacy or other systems, which have their own models. Translation layers can be simple, even elegant, when bridging well-designed Bounded Contexts with cooperative teams...But when the other side of the boundary starts to leak through, the translation layer may take on a more defensive tone."
"The models of legacy systems are usually weak, and even the exception that is well developed may not fit the needs of the current project. Yet there may be a lot of value in the integration, and sometimes it is an absolute requirement."
"The answer is not to avoid all integration with other systems...I've been on projects where people enthusiastically set out to replace all the legacy, but this is just too much to take on at once."
"Integrating with existing systems is a valuable form of reuse."
"When systems based on different models are combined, the need for the new system to adapt to the semantics of the other system can lead to a corruption of the new system's own model...It seems clear enough that errors will result if you take some data from one system and misinterpret it in another."
A common mistake is to assume that you understand how to interpret the data from another system.
"Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models."
"The public interface of the Anti Corruption Layer usually appears as a set of Services, although occasionally it can take the form of an Entity."
The other system's model can be completely abstracted, and the Anti Corruption Layer can present an interface perfectly aligned with the current system's model.
See also Facades and Adapters, which do not Translate, but can be useful pieces of an Anti Corruption Layer.
"The Facade belongs in the Bounded Context of the other system. It just presents a friendlier face specialized for your needs."
"An Adapter is a wrapper that allows a client to use a different protocol than that understood by the implementer of the behavior."
Example: Legacy Booking Application
- using an Anti Corruption Layer between new and legacy development, and slower replacing pieces of the legacy system with new development
"Although China might not have become so distinct a culture without the Great Wall, the Wall's construction was immensely expensive and bankrupted at least on dynasty, probably contributing to its fall. The benefits of isolation strategies must be balanced against their costs...There is a time to be pragmatic and make measured revisions to the model, so that it can fit more smoothly with foreign ones."
Separate Ways
"Integration can be very valuable, but it is always expensive. We should be sure it is really needed..."
"We must ruthlessly scope requirements. Two sets of functionality with no indispensable relationship can be cut loose from each other."
Problems with integration:
- coordinating teams
- forced compromises - "The simple specialized model that can satisfy a particular need must give way to the more abstract model that can handle all situations."
- limits technology, based on what can integrate together
"Just because features are related in a use case does not mean they must be integrated."
"Therefore, declare a Bounded Context to have no connection to the others at all, allowing developers to find simple, specialized solutions within this small scope...The features can still be organized in middleware or the UI layer, but there will be no sharing of logic, and an absolute minimum of data transfer through translation layers - preferably none."
Example: An Insurance Project Slims Down
- an overwhelmed project simplifies by identifying features that can be delivered as standalone development, that only comes together in the UI menu
"Taking Separate Ways forecloses some options. Although continuous refactoring can eventually undo any decision, it i shard to merge models that have developed in complete isolation."
Open Host Service
"When a subsystem has to be integrated with many others, customizing a translator for each can bog down the team...If there is any coherence to the subsystem, it is probably possible to describe it as a set of Services that cover the common needs of other subsystems."
"Define a protocol [clean enough to be understood and used by multiple teams] that gives access to your subsystem as a set of Services. Open the protocol so that all who need to integrate with you can use it."
If one subsystem needs to integrate with many other systems that are within your control, flip the relationship so that it is the other subsystems that must integrate with you.
If one team has very specific integration needs, write a custom translator for them, and keep your core protocol simple and clean.
Published Language
"The translation between the models of two Bounded Contexts requires a common language."
"When two domain models must coexist and information must pass between them, the translation process itself can become complex and hard to document and understand."
"If we are building a new system, we will typically believe that our new model is the best available, and so we will think in terms of translating directly into it. But sometimes we are enhancing a set of older systems and trying to integrate them. Choosing one messy model over the other may be choosing the less of two evils...Another situation: when businesses want to exchange information with one another, how do they do it? Not only is it unrealistic to expect one to adopt the domain model of the other, it may be undesirable for both parties."
"A domain model is developed to solve problems for its users; such a model may contain features that needlessly complicate communication with another system."
"If the model underlying one of the applications is used as the communications medium, it cannot be changed freely to meet new needs..."
"The Open Host Service uses a standardized protocol for multi-party integration. It employs a model of the domain for interchange between systems, even though the model may not be used internally by those systems."
"By publish, I simply mean that the language is readily available to the community that might be interested in using it, and is sufficiently documented to allow independent interpretations to be comparable."
"Industry groups have begun to form for the purpose of defining a single standard DTD [Document Type Definition] for their industry so that, say, chemical formula information or genetic coding can be communicated between many parties."
Example: A published language for chemistry
- see Chemical Markup Language (CML), a dialect of XML
- XML (and JSON) are sort of published meta-languages
Unifying the Elephant
"If no integration is required, then it doesn't matter that the models are not unified."
"If they require some integration...they will get a lot of value from merely recognizing that they don't agree. This way, at least they don't unknowingly talk as cross-purposes."
"Unifying multiple models almost always means creating a new model."
"...It is the exception when two models purely describe different parts of the whole...Matters are more difficult when two models are looking at the same part in a different way."
"Successful model unification, to a large extent, hinges on minimalism. An elephant trunk is both more and less than a snake, but the less is probably more important than the more. Better to lack the water-spewing ability than to have an incorrect poison-fang feature."
"Recognizing multiple, clashing domain models is really just facing reality...By explicitly defining a context within which each model applies, you can maintain the integrity of each and clearly see the implications of any particular interface you want to create between the two."
Model Context Strategy
"It is important always to draw the Context Map to reflect the current situation at any given time."
It is recommended that teams decide among themselves where to place the boundaries of Bounded Contexts. If the teams don't make those decisions, though, they must at least know what decisions have been made.
"Decisions about whether to expand or to partition Bounded Contexts should be based on the cost-benefit trade-off between the value of independent team action and the value of direct and rich integration."
"In practice, political relationships between teams often determine how systems are integrated. A technically advantageous unification may be impossible because of reporting structure. [Or] management may dictate an unwieldy merger."
Favoring larger Bounded Contexts:
- flow between user tasks is smoother when it is all unified
- team communication is easier with 1 unified model
- it's easier to understand 1 model than 2 models plus the mapping between them
- translating between 2 models can be difficult
Favoring smaller Bounded Contexts:
- lower communication overhead between developers
- continuous integration is easier
- you can use more specific, concrete models instead of versatile, abstract ones
- your can simplify the Ubiquitous Language within a context by putting boundaries between specialized groups that have their own jargon
"Deep integration of functionality between different Bounded Contexts is impractical."
You should generally leave legacy systems and external systems outside your Bounded Context.
Don't assume that each legacy system or external system is 1 Bounded Context. Each system may be made up of many contexts.
"A Bounded Context is defined by an intention to unify the model within certain boundaries."
Integrating with an external system:
- consider Separate Ways, i.e. don't integrate at all. "Would it be sufficient to give the user easy access to both systems? Integration is expensive and distracting, so unburden your project as much as you can."
- consider Conformist, if you are merely extending an existing system. "If you decide on a Conformist design, you must do it wholeheartedly."
- consider Anti Corruption Layer "where your interface to the other system is small, or where the other system is very badly designed."
"The software your project team is actually building is the 'system under design'...You can define Bounded Contexts within this zome and apply Continuous Integration within each to keep them unified."
How many Bounded Contexts should your project use?
"A single Bounded Context for the entire system under design...This would be a likely choice for a team of fewer than ten people working on highly interrelated functionality."
As the team grows larger, you'll divide the space into more and more Bounded Contexts.
"Generally speaking, there is a correspondence of one team per Bounded Context...One team can maintain multiple Bounded Contexts, but it is hard (though not impossible) for multiple teams to work on one together."
Special Needs
"Different groups within the same business have often developed their own specialized terminologies...These local jargons may be very precise and tailored to their needs. Changing them (for example, by imposing a standardized, enterprise-wide terminology) requires extensive training and analysis to resolve the differences...Even then, the new terminology may not serve as well as the finely tuned version they already had."
Specialized jargons is a good reason to create a new Bounded Context.
The costs of catering to special needs:
- loss of communication because the language is not shared
- the overhead of integration/translation
- some duplication of effort
- "it can become an argument against change and a justification for any quirky, parochial model...how valuable is the particular jargon of this user group?"
"Sometimes a deep model emerges that can unify these distinct languages and satisfy both groups. The catch is that deep models emerge later in the life cycle, after a lot of development and knowledge crunching, if at all. You can't plan on a deep model; you just have to accept the opportunity when it arises, change your strategy, and refactor."
Deployment
"Coordinating the packaging and deployment of complex systems is one of those boring tasks that are almost always a lot harder than they look."
"The choice of Bounded Context strategy has an impact on the deployment...The feasibility of a deployment plan should feed back into the drawing of the context boundaries."
Bounded Contexts that integrate together have to take each other into account when deploying. Which versions have been tested together? Both the code and the data migrations.
With lengthy data migrations and with distributed systems, you often will have two versions of a system in production at once, and they must work together until the deployment is complete.
"The Bounded Context relationships can point you toward the hot spots. The translation interfaces have been marked out."
Discussion of how to slowly convert one type of boundary to another.
"Distillation is the process of separating the components of a mixture to extract the essence in a form that makes it more valuable and useful. A model is a distillation of knowledge. With every refactoring to deeper insights, we abstract some crucial aspect of domain knowledge and priorities."
Strategic distillation of a domain model:
- Aids all team members in grasping the overall design of the system and how it fits together
- Facilitates communication by identifying a core model of manageable size to enter the Ubiquitous Language
- Guides refactoring
- Focuses work on areas of the model with the most value
- Guides outsourcing, use of off-the-shelf components, and decisions about assignments
Core Domain
"A system that is hard to understand is hard to change. The effect of a change is hard to foresee."
"When developers confine their work to specific modules, it further reduces knowledge transfer...smooth integration of the system suffers, and flexibility in assigning work is lost...duplication crops up...so the system becomes even more complex."
"The harsh reality is that not all parts of the design are going to be equally refined...To make the domain model an asset, the model's critical core has to be sleek and fully leveraged to create application functionality."
"Highly skilled developers tend to gravitate to technical infrastructure [projects]...Such parts of the system seem interesting to computer scientists, and are perceived to build transferable professional skills and provide better resume material. The specialized core, that part of the model that really differentiates the application and makes it a business asset, typically ends up being put together by less skilled developers who work with DBAs to create a data schema and then code feature-by-feature without drawing on any conceptual power in the model at all."
(Describing a project) "Most of the strong talent was happily working on database mapping layers and messaging interfaces while the business model was in the hands of developers new to object technology...The single exception, an experienced object developer working on a domain problem, devised a way of attaching comments to any of the long-lived domain objects...Unfortunately, [these features] were peripheral."
"The planning process must drive resources to the most crucial points in the model and design...To do that, these points must be identified and understood by everyone during planning and development."
Core Domain: the parts of the model that are distinctive and central to the purposes of the application
"The Core Domain is where the most value should be added in your system...Make the Core small."
"Apply top talent to the Core Domain, and recruit accordingly...Spend the effort in the Core to find a deep model and develop a supple design - sufficient to fulfill the vision of the system...Justify investment in any other part by how it supports the distilled Core."
"Whenever a choice has to be made (due to time limitations) between two desirable refactorings, the one that most affects the Core Domain should be chosen first."
"The patterns in this chapter make the Core Domain easier to see and use and change."
Choosing the Core
"We are looking at those parts of the model particular to representing your business domain and solving your business problems."
"The Core Domain you choose depends on your point of view...One application's Core Domain is another application's generic supporting component."
Ex: a currency trading application will need a more elaborate view of money than a basic budgeting application.
"...the identification of the Core Domain should evolve through iterations...The importance of a particular set of relationships might not be apparent at first. The objects that seem obviously central at first may turn out to have supporting roles."
Who does the work?
"The most technically proficient members of the project teams seldom have much knowledge of the domain. This limits their usefulness and reinforces the tendency to assign them to supporting components, sustaining a vicious circle in which lack of knowledge keeps them away from the work that would build domain knowledge."
"It is essential to...assemble a team matching up a set of strong developers who have a long-term commitment and an interest in becoming repositories of domain knowledge with one or more domain experts who know the business deeply...It is usually not practical to hire short-term, outside design expertise for the nuts and bolts of creating the Core Domain, because the team needs to accumulate domain knowledge, and a temporary member is a leak in the bucket."
"Domain design is interesting, technically challenging work when approached seriously, and developers can be found who see it this way."
"It is unlikely that the Core Domain can be purchased."
1) a generic solution with resale value is almost antithetical to the idea of a Core Domain
2) you lose the value of having complete control over your Core Domain
If an off-the-shelf framework satisfies part of your design, it indicates that it is not part of the Core Domain.
"One way or another, creating distinctive software comes back to a stable team accumulating specialized knowledge and crunching it in to a rich model. No shortcuts. No magic bullets."
Distillation Techniques (ordered from minimally invasive on up)
Generic Subdomains
"Some parts of the model add complexity without capturing or communicating specialized knowledge...The model clogs up with general principles everyone knows or details that belong to specialties which are not your primary focus but play a supporting role. Yet, however generic, these other elements are essential to the functioning of the system and the full expression of the model."
"There is a part of your model that you would like to take for granted. It is undeniably part of the domain model, but it abstracts concepts that would probably be needed for a great many businesses."
Ex: many businesses need a system for tracking expenses, or for a managerial hierarchy.
"Often a great deal of effort is spent on peripheral issues in the domain...While such components must work, they are not the conceptual core of the system."
"Identify cohesive subdomains that are not the motivation for your project. Factor out generic models of these subdomains and place them in separate modules. Leave no trace of your specialties in them...Give their continuing development lower priority than the Core Domain, and avoid assigning your core developers to the tasks...Also consider off-the-shelf solutions or published models for these Generic Subdomains."
Off-the-shelf Solutions
Pros
- less code for you to write and maintain
- code is probably mature and well tested by many other businesses
Cons
- you have to evaluate many of these products to choose one (or none)
- it probably does contain bugs, and you can't just fix them yourself
- integration may be more trouble than it's worth
- it can introduce platform requirements, such as compiler versions
"Off-the-self subdomain solutions are worth investigating, but they are usually not worth the trouble."
"The more generic the subcomponent, and the more distilled its own model, the better the chance that it will be useful."
Published Design or Model
Pros
- more mature than your new design
- includes insights from many people
- instant, high-quality documentation
Cons
- may not cover your use cases
- may be far over-engineered for your use cases
"This works best when there is a widely distributed model, such as the ones in "Analysis Patterns" by Fowler."
"When the field already has a highly formalized and rigorous model, use it...Accounting and physics are two examples that come to mind. Not only are these very robust and streamlined, but they are widely understood by people everywhere, reducing your present and future training burdens."
Outsource the Implementation
Pros
- your team can focus on the Core Domain
- allows for more development without permanently increasing the team size or losing domain knowledge when the temps leave
- forces an interface-oriented design and encourages the subdomain remain generic
Cons
- your team still needs to contribute to the interface design, coding standards, etc
- incurs significant overhead when handing off the finished work
- code quality will vary
"Automated tests can play an important role in outsourcing...The implementers should be required to provide unit tests for the code they deliver...A really powerful approach is to specify or even write automated acceptance tests for the outsourced components."
In-House Implementation
Pros
- easy integration
- you get exactly the functionality you need
- can be worked on by contractors
Cons
- ongoing maintenance and training
- it is easy to underestimate the time and cost of this approach
"Generic subdomains are the place to try to apply outside design expertise, because they do not require deep understanding of your specialized Core Domain, and they do not present a major opportunity to learn that domain...Confidentiality is less of a concern..."
Example: A Tale of Two Time Zones
"Twice I've watched as the best developers on a project spent weeks of their time solving the problem of storing and converting times with time zones."
One project used basic functionality for as long as possible. Once they clearly saw that they needed more time zone conversion support, they assigned a skilled (but temporary) programmer to the task. He researched existing solutions and, starting from a mature solution, back-filled the rest of the functionality that was needed.
The other project assigned a junior dev to the task before even figuring out the requirements. Not knowing exactly what was needed, the dev set out to handle everything. This was such a difficult task that a senior dev was assigned to help. They wrote complex code with no consumer to verify what was needed, until the project (in general) was canceled. It's possible that complex time zone conversions would never have been needed.
"We technical people tend to enjoy definable problems like time zone conversion, and we can easily justify spending our time on them. But a disciplined look at priorities usually points to the Core Domain."
"Generic doesn't mean reusable...Assuming that you are implementing the code yourself, in-house or outsourced, you should specifically not concern yourself with the reusability of that code. This would go against the basic motivation of distillation: that you should be applying as much of your effort to the Core Domain as possible and investing in supporting Generic Subdomains only as necessary...You do not have to develop the model in its full generality. You can model and implement only the part you need for your business."
So I think "generic" here means "not specialized for our domain", this is supported later when he recommends not incorporating industry-specific objects into the generic model.
"Reuse does happen, but not always code reuse. The model reuse is often a better level of reuse, as when you use a published design or model."
Project Risk Management
"Agile processes typically call for managing risk by tackling the riskiest tasks early. XP specifically calls for getting an end-to-end system up and running immediately...The end-to-end system mitigates risk only to the extent that it is an embryonic version of the challenging parts of the actual system. It is easy to underestimate the domain modeling risk."
"Projects face risks from both sides, with some projects having greater technical risks and others greater domain modeling risks...[The domain modeling risk] can take the form of unforeseen complexity, inadequate access to business experts, or gaps in key skills of the developers...The Core Domain is high risk because it is often unexpectedly difficult and because without it, the project cannot succeed."
"Therefore, except when the team has proven skills and the domain is very familiar, the first-cut system should be based on some part of the Core Domain, however simple."
"The next two patterns...show how the use of supplemental documents can, with a very minor investment, improve communication and awareness of the Core Domain and focus development effort."
Domain Vision Statement
"At the beginning of the project, the model usually doesn't even exist, yet the need to focus its development is already there. In later stages of development, there is a need for an explanation of the value of the system that does not require an in-depth study of the model."
"The critical aspects of the domain model may span multiple Bounded Contexts, but by definition these distinct models can't be structured to show their common focus."
"Many project teams write vision statements for management...Usually the vision statement document is abandoned after the project gets funding..."
"A Domain Vision Statement...focuses on the nature of the domain model and how it is valuable to the enterprise. It can be used...during all phases of development to guide resource allocation. to guide modeling choices, and to educate team members."
"If the domain model serves many masters, this document can show how their interests are balanced."
How to write the Domain Vision Statement:
- write a one page description of the Core Domain, and the value it brings to the company
- only write about the unique/distinct parts of the Domain
- show how the domain model serves and balances diverse interests
- write this early, and keep revising it as you gain new insight
Belongs in the Domain Vision Statement:
- description of things the model needs to represent (Ex: passenger priorities, and membership in special programs)
- description of data that can be provided (Ex: such that audit trails can be provided)
- description of things excluded from the model (Ex: resources required for the process will not be included)
- technical constraints the model must meet (Ex: support efficient routing)
- usability constraints on the model (Ex: the representation of the state of the factory should be comprehensible to human managers)
(pg 416 for full examples)
Doesn't Belong in the Domain Vision Statement:
- anything about the frontend
- anything about framework choices
- anything about architecture choices
- anything about hardware choices
"A Domain Vision Statement gives the team a shared direction."
"A Domain Vision Statement identifies the Core Domain in broad terms, but it leaves the identification of the specific Core model elements up to the vagaries of individual interpretation. Unless there is an exceptionally high level of communication on the team, the Vision Statement alone will have little impact."
Highlighted Core
"Significant structural changes to the code are the ideal way of identifying the Core Domain, but they are not always practical in the short term...Such mahor code changes are difficult to undertake without the very view the team is lacking."
Before you have the Core Domain refactored the way you want it, you need an intermediate way to describe your goal, in more detail than the Domain Vision Statement.
"Even at an advanced stage, a few carefully selected diagrams or documents provide mental anchor points and entry points for the team."
"Any technique that makes it easy for everyone to know the Core Domain will do."
Highlighted Core: The Distillation Document
"A distillation document is not a complete design document. It is a minimalist entry point that delineates and explains the Core Domain and suggests reasons for closer scrutiny of particular pieces."
"The reader is given a broad view of how the pieces fit and guided to the appropriate part of the code for more details."
"Write three to seven sparse pages that describe the Core Domain and the primary interactions among Core Domain elements."
"Write the document to be understood by the nontechnical members of the team."
"It can be as simple as a list of the most essential conceptual objects. It can be a set of diagrams focused on those objects, showing their most critical relationships. It can walk through the fundamental interactions at an abstract level or by example. It can use UML class or sequence diagrams, nonstandard diagrams particular to the domain, carefully worded textual explanations, or combinations of these."
Risks:
- the document is not maintained
- the document is not read
- the document just adds to the complexity of existing documentation
"The best way to limit these risks is to be absolutely minimalist."
"Staying away from mundane detail and focusing on the central abstractions and their interactions allows the document to age more slowly, because this level of the model is usually more stable."
Highlighted Core: The Flagged Core
"On my first day on a project at a major insurance company, I was given a copy of the domain model, a two hundred page document...I went through the document and, with the help of a business analyst who knew a great deal about the insurance industry in general and the requirements of the application we were to build in particular, I identified the handful of sections that presented the essential, differentiating concepts we needed to work with...I provided a navigation of the model that clearly showed the Core and its relationship to supporting features...A new prototyping effort started from this perspective, and quickly yielded a simplified application that demonstrated some of the required functionality."
Whatever form your primary model takes, highlight the Core Domain within it.
"Make it effortless to know what is in or out of the Core."
"If the distillation document outlines the essentials of the Core Domain, then it serves as a practical indicator of the significance of a model change. When a model or code change affects the distillation document, it requires consultation with other team members."
Cohesive Mechanisms
"Hiding complex algorithms in methods with intention revealing names separates the 'what' from the 'how'. This technique makes a design simpler to understand and use."
Sometimes an algorithm is so large and complex that even this is not enough to keep it organized, and clearly reveal its intent.
"A large number of methods that provide algorithms for resolving the problem obscure the methods that express the problem...This proliferation of procedures is a symptom of a problem in the model."
"The first solution to seek is a model that makes the computation mechanism simple. But now that then the insight emerges that some part of the mechanism is itself conceptually coherent...We are not talking about some kind of catch-all calculator. But extracting the coherent part should make the remaining mechanism easier to understand."
"Partition a conceptually Cohesive Mechanism into a separate lightweight framework. Particularly watch for formalisms or well-documented categories of algorithms...[This leaves] a more expressive Core Domain that uses the mechanism through the interface in a more declarative style."
"The model of the Core Domain or Generic Subdomain formulates a fact, rule, or problem. A Cohesive Mechanism resolves the rule or completes the computation as specified by the model."
This separates the Domain from the method used to solve a particular problem.
This allows the complex calculations to be unit tested in isolation.
Ex: A Mechanism in an Organization Chart
Separating well-understood graph theory from application-specific organization chart functionality.
What's the different between a Generic Subdomain and a Cohesive Mechanism?
- a Cohesive Mechanism is totally separated from the Domain model - it just solves a specific algorithmic problem
- a Generic Subdomain is still part of the Domain, but is less important and specialized than the Core Domain
Sometimes a Cohesive Mechanism is actually proprietary information, and is still part of the Core Domain.
When the Cohesive Mechanism provides "a key part of the value of the software."
Ex: Full Circle: Organization Chart Reabsorbs Its Mechanism
Even when a Mechanism is reabsorbed into a Domain, you haven't wasted effort. "The result is usually a deeper model tha more clearly differentiates facts, goals, and Mechanisms."
Distilling to a Declarative Style
"The value of distillation is being able to see what you are doing: cutting to the essence without being distracted by irrelevant detail."
"Important parts of the Core Domain may be able to follow a declarative style, when the supporting design provides an economical language for expressing the concepts and rules of the Core while encapsulating the means of the computing or enforcing them."
"Cohesive Mechanisms are by far the most useful when they provide access through an Intention Revealing Interface, with conceptually coherent Assertions and Side-Effect-Free Functions."
"An exceptional payoff comes when part of the Core Domain itself breaks through to a deep model and starts to function as a language that can express the most important application scenarios flexibly and concisely."
"When a supple design reaches maturity, it provides an easily understood set of elements that can be combined unambiguously to accomplish complex tasks or express complex information, just as words are combined into sentences."
Segregated Core
The "Segregated Core" is the most "Core" part of the "Core Domain". (see Segregating the Core of a Cargo Shipping Model for this clarification)
"You are unlikely ever to find good homes for everything in the Domain model that is not Core."
"Refactor the model to separate the Core concepts from supporting players (including ill-defined ones) and strengthen the cohesion of the Core while reducing its coupling to other code. Factor all generic or supporting elements into other objects and place them into other packages, even if this means refactoring the model in ways that separate highly coupled elements."
"This is basically taking the same principles we applied to Generic Subdomains but from the other direction...Eventually, more and more of the residue can be factored into Generic Subdomains, but in the short term any easy solution will do, just so the focus on the Segregated Core is retained."
Typical steps:
- Identify a Core subdomain
- Move related classes to a new Module, named for the concept that relates them
- Refactor code to sever data and functionality that are not directly expressions of the concept. Put the removed aspects into other packages. Don't waste time being perfect about where you put them.
- Refactor the newly Segregated Core Module to make its relationships and interactions simple and communicative. Minimize its relationships with other Modules.
- Repeat with more Core subdomains
"Segregating the Core will sometimes make relationships with tightly coupled non-Core classes more obscure or even more complicated, but that cost is outweighed by the benefit of clarifying the Core Domain and making it much easier to work on."
"...the greatest value-added of enterprise software comes from the enterprise-specific aspects of the model." So value a cohesive Core Domain over the cohesion of all other parts of the code.
"The time to chop out a Segregated Core is when you have a large Bounded Context that is critical to the system, but where the essential part of the model is being obscured by a great deal of supporting capability."
"As with many strategic design decisions, an entire team must move to a Segregated Core together...This step requires a team decision process and a team disciplined and coordinated enough to carry out the decision...The challenge is to constraint everyone to use the same definition of the Core while not freezing that decision."
"...new insights must be shared with the team on an ongoing basis, but an individual (or programming pair) cannot act on those insights unilaterally. Whatever the process is for joint decisions, whether consensus or team leader directive, it must be agile enough to make repeated course corrections."
Ex: Segregating the Core of a Cargo Shipping Model
Look at the Domain Vision Statement to locate the Core Domain classes. Move everything ancillary to the Vision Statement out of the Core Domain.
The "Segregated Core" is the most "Core" part of the Domain. (I was confused through this whole section about whether the Segregated part was more or less important.)
Some insights are gained during this process, so you may end up with a slightly different set of classes at the end.
Example of dividing Customer from CustomerAgreement (what the Customer wants).
Could you call the two resulting parts the "Segregated Core" and the "Ancillary Core"? Which together form the "Core Domain"?
He also uses the phrase "supporting" several times, so maybe "Supporting Core" instead of "Ancillary Core".
"Recognizing useful, meaning Modules is a modeling activity. Developers and domain experts collaborate in strategic distillation as part of the knowledge crunching process."
Abstract Core
"Even the Core Domain model usually has so much detail that communicating the big picture can be difficult."
"We usually deal with a large model by breaking it into narrower subdomains that are small enough to be grasped and placing them in separate Modules. This reductive style of packaging often works to make a complicated model manageable...But sometimes creating separate Modules can obscure or even complicate the interactions between the subdomains."
"When there is a lot of interaction between subdomains in separate Modules, either many references will have to be created between Modules, which defeats much of the value of the partitioning, or the interaction will have to be made indirect, which makes the model obscure."
"Consider slicing horizontally rather than vertically. Polymorphism gives us the power to ignore a lot of the detailed variation among instances of an abstract type...If most of the interactions across Modules can be expressed at the level of polymorphic interfaces, it may make sense to refactor these types into a special Core Module."
"This is a valuable technique only when the polymorphic interfaces correspond to fundamental concepts in the domain."
"Identify the most fundamental concepts in the model and factor them into distinct classes, abstract classes, or interfaces. Design this abstract model so that it expresses most of the interactions between significant components. Place this abstract overall model in its own Module...Most of the specialized classes will now reference the Abstract Core Module but not the other specialized Modules."
"The Abstract Core gives a succinct view of the main concepts and their interactions."
"The process of factoring out the Abstract Core is not mechanical...Modeling an Abstract Core requires a deep understanding of the key concepts and the roles they play in the major interactions of the system. In other words, it is an example of refactoring to deeper insight. And it usually requires considerable redesign."
Deep Models Distill
"The goal is a design that makes the model obvious, a model that expresses the domain simply."
"A deep model distills the most essential aspects of a domain into simple elements that can be combined to solve the important problems of the application."
Where to Start Refactoring?
Common advice:
- Start anywhere, because it all has to be refactored.
- DDD: This is impractical. You don't have time to refactor the entire project.
- Start wherever it is hurting you now. Refactor as you need to to complete your current tasks.
- DDD: This treats symptoms instead of the root causes.
DDD Advice
- Find the root of problem. If it involves the Core Domain, especially if it is a tangled mess you don't want to touch, then bite the bullet and fix it.
- When given free reign, focus on the Core Domain first. Work outward to the Supporting Domain and Generic Subdomains.
"This is how to get the most bang for your refactoring buck."
Example of a project with good module breakdown, but so large and complex that this was not sufficient. After much brainstorming and discussion, they devised a set of conceptual layers to organize their project into that made it clear where each part of the project belonged.
"Even with a modular breakdown, a large model can be too complicated to grasp...The modules chunk the design into manageable bites, but there may be too many of them...Modularity does not necessarily bring uniformity to the design. Object to object, package to package, a jumble of design decisions may be applied, each defensible but idiosyncratic."
"In a large system without any overarching principle that allows elements to be interpreted in terms of their role in patterns that span the whole design, developers cannot see the forest for the trees."
"We need to be able to understand the role of an individual part in the whole without delving into the details of the whole."
"A Large Scale Structure is a language that lets you discuss and understand the system in broad strokes. A set of high-level concepts or rules, or both, establishes a pattern of design for an entire system. This organizing principle can guide design as well as aid understanding."
"Devise a pattern of rules or roles and relationships that will span the entire system and that allows some understanding of each part's place in the whole - even without detailed knowledge of the part's responsibility."
"Structure may be confined to one Bounded Context but will usually span more than one...providing the conceptual organization to hold together all the teams and subsystems involved in the project."
"You can't represent most Large Scale Structures in UML, and you don't need to...Most Large Scale Structures shape and explain the model and design but do not appear in it."
"Large Scale Structure can save a project, but an ill-fitting Structure can severely hinder development."
Evolving Order
"To avoid anarchy, projects impose architectures that constrain development in various ways."
"...when architectures start venturing into the arena of the application and domain model, they can create problems of their own. They often prevent the developers from creating designs and models that work well for the specifics of the problem."
"An up-front imposition of a Large Scale Structure is likely to be costly. As development proceeds, you will almost certainly find a more suitable Structure, and you may even find that the prescribed Structure is prohibiting you from taking a design route that would greatly clarify or simplify the application."
"Design free-for-alls produce systems no one can make sense of as a whole, and they are very difficult to maintain. But architectures can straitjacket a project with up-front design assumptions that take too much power away from the developers/designers of particular parts of the application...developers will dumb down the application to fit the structure, or they will subvert it and have no structure at all..."
"The problem is not the existence of guiding rules, but rather the rigidity and source of those rules."
"Let this conceptual Large Scale Structure evolve with the application...Don't over-constrain the detailed design and model decisions that must be made with detailed knowledge."
"Individual parts have natural or useful ways of being organized and expressed that may not apply to the whole, so imposing global rules makes these parts less ideal."
"Choosing to use a Large Scale Structure favors manageability of the model as a whole over optimal structuring of the individual parts...Therefore, there will be some compromise between unifying structure and freedom to express individual components in the most natural way."
"A really nice fit of structure to domain and requirements actually makes detailed modeling and design easier, by helping to quickly eliminate a lot of options."
This chapter speaks about abstract concepts. Hard to distill it.
"Unlike the Context Map, a Large Scale Structure is optional. One should be imposed when costs and benefits favor it, and when a fitting Structure is found...[good fit means the structure] greatly clarifies the system without forcing unnatural constraints on model development...an ill-fitting Structure is worse than none..."
System Metaphor
The metaphor of a "firewall" has been very useful to defining what features a class of products include, and to help new developers quickly understand the firewall's purpose. But the metaphor is inexact, and also causes some misunderstandings.
Other metaphors: system "layers", a "kernel" at the center of a design
"Software designs tend to be very abstract and hard to grasp. Developers and users alike need tangible ways to understand the system and share a view of the system as a whole."
Systems are shaped by their metaphors because developers will make design decisions based on the metaphor.
"A System Metaphor is a loose, easily understood, Large Scale Structure that is harmonious with the object paradigm."
"Because the System Metaphor is only an analogy to the domain anyway, different models can map to it in an approximate way, which allows it to be applied in multiple Bounded Contexts, helping to coordinate work between them."
"A persuasive Metaphor introduces the risk that the design will take on aspects of the analogy that are not desirable for the problem at hand, or that the analogy, while seductive, may not be apt."
"The System Metaphor should both facilitate communication about the system and guide development of it."
"...a useful Metaphor doesn't present itself on most projects..."
XP programmers use the term "naive metaphor" for the domain model itself. "One trouble with this term is that a mature domain model is anything but naive."
Responsibility Layers
"Layers are partitions of a system in which the members of each partition are aware of and are able to use the services of the layers 'below', but unaware of and independent of the layers 'above'...[Layers] are one of the most successful architectural design patterns."
"In a model with a natural stratification, conceptual layers can be defined around major responsibilities, uniting the two powerful principles of layering and responsibility-driven design...These responsibilities must be considerably broader than those typically assigned to individual objects..."
"Look at the conceptual dependencies in your model and the varying rates and sources of change of different parts of your domain. If you identify natural strata in the domain, cast them as broad abstract responsibilities. These responsibilities should tell a story of the high-level purpose and design of your system. Refactor the model so that the responsibilities of each domain object, Aggregate, and Module fit neatly within the responsibility of one layer."
Example: Layering a Shipping System
Seeing the stratification of the domain: it is easy to discuss A without B, but it is hard to discuss B without A.
The domain is divided into two layers. The decisions on where to put each class are guided by both technical and business concerns.
The layering provides guidance for refactoring towards a deeper model.
The final layers are Capability, Operations, and Decision Support.
"Now, this isn't necessarily a better design than the other. They both have pros and cons. But if everyone on a project makes decisions in a consistent way, the design as a whole will be much more comprehensible, and that is worth some modest trade-offs on detailed design choices."
Choosing Appropriate Layers
"Finding good Responsibility Layers, or any Large Scale Structure, is a matter of understanding the problem domain and experimenting. If you allow Evolving Order, the initial starting point is not critical, although a poor choice does add work."
"As layers get switched out, merged, split, and redefined, here are some useful characteristics to look for and preserve."
- Storytelling: The layers should communicate the basic realities or business priorities of the domain. Choosing a Large Scale Structure is mostly a business modeling decision, not a technical one.
- Conceptual Dependency: The concepts of the upper layers should have meaning against the backdrop of the lower layers. The lower layer concepts should be meaningful standing alone.
- Conceptual Contours: If the objects of different layers should have different rates of change or different sources of change, the layer accommodates the shearing between them.
Some possible layers that are useful in many domains:
- Potential (aka Capability): What can we do? Regardless of what we will actually do. The resources of the organization, including its people, and the way those resources are organized are the core of the Potential layer.
- Operation: What is being done? What have we made of these Potentials? In this layer, we are trying to see our own efforts and activities. What we are selling, rather than what enables us to sell.
- Decision Support: What action should be taken, or policy set? This layer is for analysis and decision making. When projects seek to guide or assist users, or to automate decision making, this is an additional set of responsibilities above Operations.
- Policy: What are the rules and goals? Rules and goals are mostly passive, but constrain the behavior of other layers.
- Commitment: What have we promised? Commitments emerge and change as a part of ongoing business activities (like Operations) but also constrain and guide decisions (like Policy).
Expected rate of change to each layer:
Decision: almost never
Policy: slow
Operation: rapid
Potential: moderate
Commitment: moderate
"It is best to keep the layering system simple; going beyond four or possibly five becomes unwieldy."
"Ultimately, you have to use your intuition, start somewhere, and let the Order Evolve."
Knowledge Level
"A Knowledge Level is a group of objects that describes how another group of objects should behave."
"Knowledge Level untangles things when we need to let some part of the model itself be plastic in the user's hands yet constrained by a broader set of rules. It addresses requirements of software with configurable behavior, in which the roles and relationships among Entities must be changed at installation or even at runtime."
Example: Employee Payroll and Pension
Consider a domain where the roles and relationships of objects car vary greatly within one organization over a short period of time. How can the software support this without becoming too complex?
"Neither the fully general objects nor the highly customized ones serve the users' needs." Fully general objects allow the users too much freedom, while highly customized ones must be constantly maintained by the developers.
"Nested in our model is another model that is about our model. A Knowledge Level separates that self-defining aspect of the model and makes its constraints explicit."
This is an application of Reflection - software which is self-aware. This is a meta level.
"Significantly, the pattern is not called a knowledge "layer". As much as it resembles layering, Reflection involves mutual dependencies running in both directions."
Note that the Knowledge Level will not use the reflection built into a programming language. It is operating at a higher level than that.
"The pattern should be used sparingly...Reflection and Knowledge Levels can be intoxicating."
"If the Knowledge Level becomes complex, the system's behavior becomes hard to understand for developers and users alike. The users (or superusers) who configure it will end up needing the skills of a programmer..."
Example: Employee Payroll and Pension II
Designing a Knowledge Level.
(The author must be aware that his language in this chapter is less well defined than before, because he says "large-scale structure" instead of "LARGE-SCALE STRUCTURE". When he has a well-defined phrase, he puts in all caps.)
"At first glance, Knowledge Level looks like a special case of Responsibility Layers, but it is not...dependencies run in both directions between the levels..."
"Knowledge Level can coexist with most other Large Scale Structures, providing an additional dimension of organization."
Pluggable Component Framework
"Distill an Abstract Core of interfaces and interactions and create a framework that allows diverse implementations of those interfaces to be freely substituted. Likewise, allow any application to use those components, so long as it operates strictly through the interfaces of the Abstract Core."
"High-level abstractions are identified and shared across the breadth of the system; specialization occurs in Modules. The central hub of the application is an Abstract Core within a Shared Kernel."
Downsides to a Pluggable Component Framework:
- It is a difficult pattern to apply.
- Applications have limited options.
- It is so expensive to refactor the Core that it's essentially frozen.
"A Pluggable Component Framework should not be the first Large-Scale Structure applied on a project, nor the second."
Example: The SEMATECH CIM Framework
"The interesting thing about it is the definition of a Pluggable Component Framework, which allows people to develop software independently and smoothly integrate them into immense systems."
"How can thousands of people work independently to create a quilt of more than 40,000 panels? A few simple rules provide a Large-Scale Structure for the AIDS Memorial Quilt, leaving the details to the individual contributors...Notice how the rules focus on the overall mission, the features of a component that make integration practical, and the ability to handle the quilt in larger sections."
How Restrictive Should a Structure Be?
"...there is a lot of choice about how restrictive to make the rules."
Discussion of using Events to avoid two-way communication between Layers.
- is this still Layers? using the Events avoids a code dependency, but there is still a conceptual dependency
"A more restrictive structure increases uniformity, making the design easier to interpret. If the Structure fits, the rules are likely to push developers toward good designs."
"On the other hand, the restrictions may take away flexibility that developers need...So you have to fight the temptation to build frameworks and regiment the implementation of the Large Scale Structure."
"The most important contribution of the Large Scale Structure is conceptual coherence, and giving insight into the domain."
"Each Structural rule should make development easier."
Refactoring Toward a Fitting Structure
Structures should not be applied upfront, they should be allowed to grow naturally, as your understanding of the project deepens.
"The team should not saddle itself with a structure conceived of early on, when no one understood the domain or the requirements very well."
"That evolution means that your final structure will not be available at the start, and that means that you will have to refactor to impose it as you go along. This can be expensive and difficult, but it is necessary."
Minimalism
"One key to keeping the cost down is to keep the Structure simple and lightweight. Don't attempt to be comprehensive. Just address the most serious concerns and leave the rest to be handled on a case-by-case basis."
Communication and Self-Discipline
"The entire team must follow the Structure in new development and refactoring. To do this, the Structure must be understood by the entire team."
"Without consistent adherence by the many people involved, Structures have a tendency to decay."
Why is it easy to not follow the Structure?
- It is not usually explicit in the code.
- Functional tests do not rely on it.
- It is often abstract.
"It is critical to incorporate it into the Ubiquitous Language of the project, and for everyone to exercise the Language relentlessly."
Restructuring Yields Supple Design
"Any change to the Structure may lead to a lot of refactoring. The Structure is evolving as system complexity increases and understanding deepens."
"Obviously that is a lot of work [but] this isn't quite as bad as it sounds. I've observed that a design with a Large Scale Structure is usually much easier to transform than one without."
"Part of the answer is that it easier to rearrange something when you can understand its current arrangement...Partly it is that the discipline that it took to maintain the earlier Structure permeates all aspects of the system...But there is something more, I think, because it is even easier to change a system that has had two previous Structures."
"Ever-increasing knowledge is embedded into them and the principal axes of change have been identified and made flexible, while stable aspects have been simplified."
Distillation Lightens the Load
Continue distilling the Domain Model (breaking out Generic Subdomains and such) so that this is less Model that needs Structuring.
The Structure itself may be refactored, such as redefining what Layers your Model is split into.
Combining Large Scale Structures and Bounded Context
(would "superstructure" be a good ubiquitous language term for "large scale structure"?)
"The three basic principles of strategic design (context, distillation, and large scale structure) are not substitutes for each other; they are complementary and interact in many ways...For example, a large scale structure can exist within one Bounded Context, or it can cut across many of them and organize the Context Map."
An example of fitting a legacy system, crossing multiple Responsibility Layers, into your Large Scale Structure: you can access the legacy system through facades which neatly fit into your Responsibility Layers.
Combining Large Scale Structures and Distillation
"The Large Scale Structure may be an important part of the Core Domain...For example, distinguishing the layering of potential, operations, policy, and decision support distills an insight that is fundamental to the business problem addressed by the software."
Assessment First
1) Draw a Context Map
2) Refine your Ubiquitous Language
3) Identify the Core Domain and write a Domain Vision Statement
4) Assess your technology stack. Will it help or hinder a Model Driven Design?
5) Do the developers have the technical skills they need?
6) Are the developers interested in and knowledgeable about the domain?
Who Sets the Strategy?
Handing down an architectural plan before development even begins does not work well. But for a strategic design to benefit the project, it must be followed consistently.
Emergent Structure From Application Development
"A self-disciplined team made up of very good communicators can operate without central authority and follow Evolving Order to arrive at a shared set of principles, so that order grows organically, not by fiat...This is the typical model for an Extreme Programming team."
A design leader - who keeps everyone consistently using the same strategic design - can emerge spontaneously. They don't design everything; they help keep everyone using the design that the team as decided on. They are a hands-on developer.
Each team will need several developers capable of contributing to the design.
A Customer Focused Architecture Team
"When a strategy will be shared among several teams, some centralization of decision making does seem attractive."
"An architecture team can act as a peer with various application teams, helping to coordinate and harmonize their Large Scale Structures as well as Bounded Context boundaries and other cross-team technical issues...The failed model of the ivory tower architect is not the only possibility."
Six Essentials For Strategic Design Decision Making
1) Decisions must reach the entire team
If the architecture team is out of touch with the application teams, a de facto architectural team will form within the application teams.
Whichever strategies disseminate through the developers, that is the one that will be used.
2) The decision process must absorb feedback
"Unlike technical infrastructure and architectures, strategic design does not itself involve writing a lot of code, although it influences all development...What it does require is involvement with the application development teams."
You must really understand the domain and technology stack to create a successful design. Hands-on experience is the best experience.
3) The plan must allow for evolution
"Effective software development is a highly dynamic process...When the highest level of decisions is set in stone, the team has fewer options when it must respond to change."
"While a harmonizing principle can be valuable, it must grow and change with the ongoing life of the development project, and it must not take too much power away from the application developers, whose job is hard abough as it is."
"With strong feedback, innovations emerge as obstacles are encountered in building applications and as unexpected opportunities are discovered."
4) Architecture teams must siphon off all the best and brightest
"Design at this level calls for sophistication that is probably in short supply."
"Managers tend to move the most technically talented developers into architecture teams and infrastructure teams, because they want to leverage the skills of these advanced designers...Such teams almost never include the developer who perhaps has weaker design skills but who has the most extensive experience in the domain."
"But building good applications takes design skills...Even if a strategy team creates a great strategic design, the application team won't have the design sophistication to follow it."
"It is essential to have strong designers on all application teams. It is essential to have domain knowledge on any team attempting strategic design."
5) Strategic design requires minimalism and humility
Minimalism is essential to strategic design. "Even the slightest ill fit has a terrible potential for getting in the way."
"The architects' enthusiasm for their primary responsibility makes them more likely to get carried away...One good idea leads to another, and we end up with an overbuilt architecture that is counterproductive."
"Instead, we have to discipline ourselves to produce organizing principles and core models that are pared down to contain nothing that does not significantly improve the clarity of the design."
"Almost everything gets in the way of something, so each element had better be worth it. Realizing that your best idea is likely to get in somebody's way takes humility."
6) Objects are specialists; developers are generalists
"The essence of good object design is to give each object a clear and narrow responsibility and to reduce interdependence to an absolute minimum. Sometimes we try to make interactions on teams as tidy as they should be in our software."
"A good project has lots of people sticking their nose in other people's business...Developers play with frameworks. Architects write application code. Everyone talks to everyone."
"People love to find ways to chop up tasks so that design experts don't have to know the business and domain experts don't have to understand technology...Overspecialization takes the steam out of Domain Driven Design."
The Same Goes For Technical Frameworks
Everything above applies to designing frameworks, too.
"There is one particular attitude that will surely ruin a framework."
Don't Write Frameworks For Dummies
"Team divisions that assume some developers are not smart enough to design are likely to fail because they underestimate the difficulty of application development...Attempts to coddle them will only put up barriers between them and the tools they need...This attitude also poisons the relationship between teams."
"Encapsulating irrelevant technical detail is completely different from the kind of prepackaging I'm disparaging. A framework can place powerful abstractions and tools in developers' hands and free them from drudgery."
Beware The Master Plan
References Christopher Alexander's writing about why master plans for physical architecture fail. "In practice, master plans fail - because they create totalitarian order, not organic order. They are too rigid; they cannot easily adapt to the natural and unpredictable changes that inevitably arise in the life of a community...At best, the order that results from such a process is banal."
"Alexander and his colleagues advocated instead a set of principles for all community members to apply to every act of piecemeal growth, so that organic growth emerges, well adapted to circumstances."
"Although it is very satisfying working on a cutting-edge project and experimenting with interesting ideas and tools, for me it is a hollow experience if the software does not find productive use."
"The true test of success is how the software serves over a period of time."
Talks about what happened on all those projects that have been used as examples throughout the book.
"No project will ever employ every technique in this book. Even so, any project committed to Domain Driven Design will be recognizable in a few ways. The defining characteristic is a priority on understanding the target domain and incorporating that understanding into the software."
Looking Forward
"Business software does not have to be a bolted-together mess. Wrestling a complex domain into a comprehensible software design is an exciting challenge for strong technical people."
"Tools that help us think or avoid distraction are good. Efforts to automate what must be the product of thought are naive and counterproductive."
"We can write software that is a pleasure to use and a pleasure to work on, software that doesn't box us in as it grows but creates new opportunities and continues to add value for its owners."