Domain Driven Design - kylecoberly/knowledge GitHub Wiki

Something being open-ended isn't the same as being random.

Knowledge Crunching

  • Accountants crunch numbers, modelers crunch knowledge
  • Models are simplifications and abstractions, and necessarily leave things out that aren't core
  • Models should create shared language between technical and non-technical people
  • It's not just finding nouns- important business rules also need to be encoded in the model
  • Modeling is ongoing work. As you learn more, your model will add, remove, and change.

Ubiquitous Language

Use the same language for requirements, design, and implementation. If a non-technical expert can't understand your model, it's not a very good model. The model isn't the diagram, the diagram is a representation of part of the model.

Every translation between domain and technical experts dulls communication.

It's not just nouns, but also the nature of the interactions that need to be modeled.

Using the language of the model constantly in all communication will push the weaknesses of the model to the front. Any time a domain expert thinks the language of the model is awkward or people are using the language awkwardly or inconsistently, it's a sign that the model needs tweaking. Changes to the language everyone speaks should be reflected as changes to the domain model.

Use spoken language a way to rephrase the domain langauge many different ways. Aim for conciseness. People are naturally more playful with spoken language. Try to describe different scenarios out loud using the domain language.

The more people who use the language of the model, the more weaknesses will be discovered with it.

Technical experts and domain experts will always have some language of their own, but there shouldn't be alternate vocabularies for the same things.

Diagrams are meant to facilitate discussion. They shouldn't be considered comprehensive or authoritative, and they should have as little detail as possible.

If you use written documents, they shouldn't be redundant with code. They should illustrate simple versions of high-level ideas, or explain difficult domain concepts visually. They should "work for a living", meaning that no one should update a diagram just to update it; it should be updated as it's used.

Binding Model and Implementation

Models need to be expressed in code to be useful. If the modelers and the coders are different people, these are bound to get out of sync.

Modeling pretty much needs to be done in an OO language because the language needs to directly support modeling. Objects can't just be fancy data structures, they need to capture behavior too.

Separate "analysis" models include extra detail that distracts, and inevitably misses important abstractions. Furthermore, domains are at least partially understood through the process of working with them.

It's difficult to make a model reflect both the domain and the software design, but it's worth the effort because it makes the model relevant.

Isolating the Domain

Take out both UI and infrastructure considerations from domain models.

A Model Expressed In Software

  • Associations: Associations should say something about the relationship and how its used. For example, you can make "President" and "Country" bi-directional, but you would never say "Which country was George Washington the president of?" Therefore, this should be a unidirectional relationship. If you constrain it by "date range", you can simplify the association even further.
  • Entities: An object that has a unique ID and represents the same thing over time. Keep the entity thin and simple, and use collaborating objects for its complexity.
  • Value Objects: An object that doesn't have a unique ID and can be used interchangeably with another object that has the same attributes. You can even have one instance and just pass references to it (Flywheel). Must either be immutable or never be shared.
  • Services: Stateless verbs in the domain that aren't really entities or value objects. Still use domain language.
  • Modules: Logical groupings of models. Should be small enough that you can wrap your head around everything in it.

When using a language, tool, paradigm, etc., continually ask yourself if it's pulling its weight on the project.

The Lifecycle of a Domain Object

Aggregates

You can control the complexity between parts of a model by treating all of the objects as one unit that can only be accessed from the outside by a single aggregate root. Start by defining a boundary and a root. Nothing outside the boundary can directly reference anything other than the root. Things within the boundary can reference each other and other roots. When you delete the root, you delete everything else in the aggregate at the same time.

Factories

Wrap creation of aggregates with factories. Clients should know as little as possible about the internal structure of the objects they instantiate. When possible, put the factory method in the aggregate, otherwise it can be a standalone class. Clients should depend on the type, and shouldn't need to know the actual class.

When a class is the type, isn't complicated, and its internals are actually relevant to clients, it's OK to just use constructors directly.

When designing a factory, you also need to determine what happens with errors in the factory.

It's OK for factories to enforce rules ("invariants") between objects within them.

Repositories

Factories make new objects, repositories retrieve old ones. Keep data retrieval and database concerns out of your models; have them query repositories for fully-reconstituted objects instead. Repositories can either retrieve an object from memory or fetch it from the database if it doesn't have it (the client shouldn't know which one happened). Repositories can also use factories to reconstitute the object. You'll often need separate repositories for each aggregate root, although you can abstract similar items (especially if they share a superclass). These also simplify testing. Use your ubiquitous language in the database too.

Breakthrough

Models are refactored just like code is. Your early attempts are usually naive, and as you better understand the problem by working with it, you need up to update the model. Eventually, you'll come across a major breakthrough in how the model helps express the problem. This may require huge changes to the code, but it's worth it for the extra expressiveness you gain.

Making Implicit Concepts Explicit

  • Listen to the language experts use. Do they use succinct terms that hide a lot of complexity? Do they keep correcting your word choice? Are their phrases you use that make them feel like you get it?
  • Scrutinize the parts of the model that are the most awkward, or seem to break the fastest when requirements change
  • Contemplate contradictions. When two experts say conflicting things, look for the overlap.
  • Read books and look at the abstractions the industry already uses

Object concepts that are non-obvious:

  • Constraints. When there's an invariant (you can't fill a bucket more than its capacity), that can become a part of the model. Move constraint logic into independent methods to keep the actual task method more focused. This also allows it to grow and become complex independently from the actual operation. You'll know a constraint has outgrown its object when it requires data that doesn't naturally fit with the object, there are similar rules in multiple objects, or there are a lot of conversations around the constraints but the implementations are tucked into procedures.
  • Processes. While you generally don't model processes as objects, they may central enough to the design that they warrant from having their own objects (like strategies). Is the process something domain experts talk about?
  • Specifications. These are FP predicates that you can represent as objects.