Domain Driven Design

domain

"Fundamentally, Domain Driven Design is the principle that we should be focusing on the deep issues of the domain our users are engaged in, that the best part of our minds should be devoted to understanding that domain, and collaborating with experts in that domain to wrestle it into a conceptual form that we can use to build powerful, flexible software." -Eric Evans

Introduction: Eric Evans in his excellent book, Domain Driven Design, organizes common patterns and practices into a logical framework for developing software. Most of what he writes is not necessarily new, but he brings a common vocabulary and organization to design that is very helpful.

  1. Putting the Domain Model to Work
    1. What is a Domain

      Domain: A sphere of knowledge, influence, or activity.

      We need to concentrate on the domain of software - really learn the business environment of our software. Since the entire business domain is too large for our meager brains, we construct a model of the domain, a subset of the actual business domain, that is essential to our software.

      For many projects the primary problem in writing software is the complexity of the domain. The problem is not all the plumbing of GUIs and data persistence, but the domain.

      This is not true of all projects. Many smaller projects will not benefit from Domain Driven Design. The larger, more interesting projects need a clean, well conceived model.

    2. Modeling

      "Financial analysts crunch numbers... domain modelers are knowledge crunchers." - p13 DDD

      Definition for Model: A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to those aspects of that domain.

      Domain Driven Design is based on Model Driven Design which puts the Model at the heart of development.

      Key Aspects of Models:

      1. The Model should not be as realistic as possible. It should not contain aspects of the domain that don't end up in code.
      2. UML diagrams are not sufficient to convey the concepts behind a model. Use different diagrams types and written documents to describe a model. The diagrams and documents are not the model - the model is held in the heads of people.
      3. Developing an effective domain model takes many conversations between the software developers and the domain experts. This is an iterative process.
      4. Extensible software needs a deep domain model.
      5. Models of the same thing are different based on the requirements. Use a road map when touring in a car, use a subway map for traveling the underground. subway map
    3. Ubiquitous Language (I'm kinda of a big deal - really!)
      book
      1. To effectively communicate between business personal and software developers a common language is needed, a "Ubiquitous Language". The Ubiquitous Language should make sense to the domain experts. The software team needs to learn the language of the domain experts.
      2. A historical model is the use of a pidgin language. When two groups interact without sharing a common language, a pidgin language is often used. The new language is not very expressive, but powerful enough to conduct simple trade agreements.
      3. The terms used in the Ubiquitous Language need to be used in the software itself as object and method names.

        The Model Should Write the Code

        A project should not have an "Analysis Model" and a "Programming Model", but a single model. If the model cannot be efficiently turned into code, the model must be refactored.

        2009-09-27-2103-ModelWritesTheCode

        If you are working for Hogwarts School and they say "A wizard casts a spell", your code should look like this:

        wizard.cast(spell);
        

        not this:

        spellCaster.Implements(magicDocumentText);
        

        One more time: The Ubiquitous Language lives in your code.

  2. The Building Blocks of a Model-Driven Design
    1. Layering

      Cohesive software should be layered together and only depend on the layers beneath it.

      A layer depends only on the layers beneath it. When a layer communicates upward it does so through an interface.

      Advantages of Layered Systems:

      1. Components can evolve separately
      2. Layers can be deployed more easily on separate servers
      3. Objects in a layer can be more effectively tested using test doubles for the layers above and beneath
      4. Layers provide a natural separation for teams to divide the work
      5. Layers, like persistence, can be replaced easily when better software becomes available
      6. Layers are easier to understand and maintain

      (The "Smart UI". On small projects being developed by entry level programmers, using a "Smart UI", where all the layers are munged into one, makes sense because the overhead of doing a Model Driven approach is too costly.)

      Example Layering of Four Concepts:

      Layer Description
      User Interface Shows the user information and receives input. (aka 'View')
      Application Thin layer to coordinate application activity. No business logic or business object state is in this layer. (aka 'Controller')
      Domain All the business objects and their state reside here. Objects in the domain layer should be free of concern about displaying or persisting themselves. (aka 'Model')
      Infrastructure The objects dealing with housekeeping tasks like persistence. (aka 'Plumbing')

      A clean design starts like this:

      four layers

      Then, some of the younger programmers start to work on it:

      Kneading layers

      Then slowly over time you get this:

      OneLayerMottled

      What's wrong with mixing the layers just a bit?

    2. Object Types
      1. Entities - have an identity that persists over time. The identity is independent of the state of its attributes.

        Entity objects with the exact same attributes are different objects.


        Twins
      2. Value Objects - have no identity.
        1. If two value objects have the same attributes they are considered the same.
        2. If Value Objects are sharable they should be immutable.
        3. Value Objects may contain other Value Objects and references to Entity Objects.
        4. Value Objects should only have constructors and functions (no side-effects) since they are immutable. Money

          Are all pennies interchangeable?

      3. Service Objects

        Unfortunately Eric used the way-overloaded term "Service" for this next thing. To differentiate it from other services, like "web services", DDD services are sometimes referred to as "Domain Services".

        1. A Domain Service provides functionality when the service doesn't neatly fit into an object.
        2. A service has no internal state, so it can be called repeatedly with the same input and it always gives the same output.
        3. Services may span several objects.
        4. Putting Service functionality into domain objects creates too many dependencies between objects.
        5. Services can be in the domain, infrastructure, or application layer.
        6. Example:
          TransferService.Transfer(Account from, Account to, Money amount);
          

        Waiter

        A waiter is a form of service. You could get the food yourself, but you'd have to learn where the kitchen is, where are those trays, who is the cook. You'd have to learn a bunch of things which really aren't what you are about. It's easier to know a waiter.

      4. Modules.

        Put related software objects together in Modules. Two methods of grouping:

        1. Communicational cohesion - objects that operate on the same data
        2. Functional cohesion - objects that cooperate together to perform a task

        Individual software objects should have high cohesion and low coupling. This is also true of modules. Modules should have a few well-defined interfaces to the outside world and high cohesion.

        Modules should have low coupling. An object from Module A should not call four objects from Module B, that would be high coupling, instead it should make a single call to an interface on Module B that queries the needed four objects.

        Modules should have names from the Ubiquitous Language.


        Moche
      5. Aggregates - a domain pattern dealing with ownership and boundaries of objects.
        1. An aggregate is a group of related objects. An Aggregate has one root which is an Entity, called the "Aggregate Root".
        2. Access to an aggregate member must come only from its root. An outside object cannot hold a real reference to an aggregate member, only the local one provided by the root.
        3. When the root object is deleted from memory, all its aggregates are flushed as well since they can only be accessed from the root.
        4. If all the aggregate objects are deleted when the parent is deleted, that's a good sign you have a true aggregate. If the aggregate objects still have a meaningful life after their Aggregate Root is deleted, you have references, not an Aggregate Root.
        5. Aggregate Roots interact with Repositories, but the children of the Aggregate Root do not.
        6. Aggregates can impose rules on the objects it manages enforcing consistency.
        7. Being able to treat all members of an Aggregate Root as one is the advantage of this pattern. We can delete the root with confidence that all its members are deleted and we can persistent the root with confidence that all its children are persisted.

        Candy
      6. Factories - used to create new complex objects.

        Creation should be atomic. The factory gives the object an identity.

        carFactory.CreateCar(Engine engine, Body body, Transmission transmission, Interior interior);
        

        Factory
      7. Repositories
        1. Repositories do not create identity.
        2. Repositories are a Facade pattern over the complexity of getting objects from storage, usually a database.
        3. Repositories look like a collection to your application, like a List<T>.
        4. Repositories give our objects Persistence Ignorance (PI). The objects themselves have no idea how they will be stored.
        customerRepository.GetById(int id);
        customerRepository.GetAllByZipCode(string zipCode);
        customerRepository.GetByAddress(Address customerAddress);
        

        Coats
  3. Refactoring Toward Deeper Insight
    1. Making Implicit Concepts Explicit

      The code needs to tell the maintainer what the code is doing. Instead of hiding functionality deep in the bowels of the code, a good programmer makes the intent of the code clear by several means.

    2. Specifications

      Specification objects are value objects that return a true or false, taking an object as an argument. Specifications make the intent of the code clearer and allow for reuse. Specification objects are useful in these three scenarios:

      1. Validating that an object meets the specification.

        Is this customer eligible for special terms?

      2. Selecting from a collection

        Give me all the customers that are 60 days behind in payments

      3. Creating an object

        Create an object that obeys all these criteria...

      Example code of first two reasons

      using System;
      using System.Collections.Generic;
      using NUnit.Framework;
      
      namespace PlayingAround {
      class SpecificationsExample {
          internal class Animal {
              public string Name;
              public bool IsWarmBlooded { get; set;}
              public bool LaysEggs { get; set;}
              public bool HasHair { get; set;}
          }
          public class MammalSpecification {
              public static bool IsSatisfiedBy(Animal animal) {
                  return animal.IsWarmBlooded && ! animal.LaysEggs && animal.HasHair;
              }
          }
          private static void Main() {
              Animal horse = new Animal {Name = "horse", IsWarmBlooded = true, LaysEggs = false, HasHair = true};
              Animal turtle = new Animal { Name = "turtle", IsWarmBlooded = false, LaysEggs = true, HasHair = true };
              Assert.IsTrue(MammalSpecification.IsSatisfiedBy(horse));
              List<Animal> animals = new List<Animal> {horse, turtle};
              List<Animal> mammals = animals.FindAll(MammalSpecification.IsSatisfiedBy);
              Assert.IsTrue(mammals.Contains(horse) && mammals.Count == 1);
              Console.Out.Write("press return to exit.");Console.In.ReadLine();
          }
      }
      }
    3. Supple Design

      In Chapter 10, Eric Evans gives some advice on making a design more flexible.

      1. Pure Functions and Command Methods

        Pure Functions do not change state. They only query, do calculations, and return a value. Pure Functions can be called repeatedly and always return the same value when given the same inputs (technically this is called idempotence). Use as many Pure Functions as possible since they are simpler and easier to test.

        Commands are methods that change the state of an application. Although they can return a value, it is better to refactor the method to only change state.

        A good method should be either a Command or a Pure Function, but like oil and water, they don't mix.

      2. Low Coupling

        Like in humans, relationships between objects complicates things. Try to reduce the connections between objects. The ultimate goal would be to have a StandAlone object that has no dependencies on any other classes.

      3. Closure of Operations

        "Closure of Operations" means you can perform an operation and return the same type as its argument. For example,

        Person parent = GetParent(Person student);
        

        GetParent() takes a Person and returns a Person. This helps reduce the mental load on the developer since only one type of object is being used. It also helps to string method calls together in that trendy "fluent" style.

        "Closure of Operations" is usually done with Value objects.

      4. Put Complex Logic in Value Objects

        Moving complex logic and calculations into value objects with pure functions makes testing easier and modifications to the calculations simpler.

    4. Directory Structure Maps to the Design

      The directory structure of a project should mirror a clean division between Domain objects and other objects. The Domain object directory should not have references to libraries of supporting code like persistence, just to Interfaces for those objects.

    5. Analysis Patterns Believe it or not you are probably not the first person to write software in your domain. Books such as the one below can walk you through some common domains:
      Click to read reviews or buy Analysis Patterns: Reusable Object Models
      by Martin Fowler
    6. Ways to Clarify the Model.
      1. Constraints

        Constraints are a way of forcing an invariant. Making Constraints separate objects helps to clarify the intent and provide a flexible future.

      2. Processes

        These should be implemented as a Service object.

      3. Specification

        A test to determine if an object obeys a criteria.

  4. Strategic Design
    1. Preserving Model Integrity

      Unification is the term for a model's internal consistency.

      1. Continuous Integration

        As time goes on, the model will change. The model should be checked for errors as developers add new functionality to ensure no duplicate code is written and the objects maintain their purpose. Code integration should be done as soon as possible, ideally after each checkin. All the unit and integration tests should be run as frequently as needed.

      2. Shared Kernel

        On large projects, sometimes separate models intersect functionally and need to have shared code. This should be done very carefully with both teams notifying the other of changes in their shared code. The shared kernel is often the core domain objects.

      3. Customer-Supplier

        When two teams are competing for rights to modify a shared resource, one team should play the role of customer and the other the supplier. Changes should be carefully coordinated between the two teams.

      4. Conformist

        When the Customer-Supplier relationship breaks down, perhaps from economic or political reasons, the Customer must simply conform to the code/database schema of the Supplier.

      5. Anticorruption Layer

        When two systems that do not share a common model need to communicate, a translation service may need to be created that acts as a Facade and Adapter pattern to move data between models.

      6. Separate Ways

        When two systems have little in common, sometimes it's better to have them not tightly integrated, but have them go their separate ways with their own bounded context, with just a thin veneer like a GUI holding them together.

      7. Open Host Service

        When many systems need to communicate with a particular subsystem, instead of writing full translators for each system, create a generic set of services that can be used by all other systems with only minor modifications.

      8. Shared Language

        Sometimes by creatubg ir using a published language, communication between multiple applications can be easier.

    2. Deep Refactoring

      Sometimes in the life of a project you have a new eureka moment of understanding the underlying domain. It's time to do major refactoring. A clue for this need is when you customers don't understand you object model. Another clue is when the customers keep using a term that is not in your domain model.

    3. Constant Refactoring book

      As your knowledge of the domain increases you should refactor your code often. Meet as a team over a few days, sleep on the design changes, then be courageous and make big changes.

      As Miss Frizzle says on the Magic School Bus: "Take chances, make mistakes, get messy".

    4. Strategic Design:
      1. Bounded Context

        In large projects, multiple models may describe different parts of the system. Make sure the boundaries between the models is clear. Define the relationship between code bases and models. Know when to use which model.

        Within a bounded context splintering may occur. When two people have different meanings for the same term, these are called "false cognates". People may also duplicate code funtionality. This should be rigorously refactored.

        Each model needs to have "unification", be internally consistent and have unambiguous terms with no rules conflicting.

        How do all the contexts in a large project relate? This should be described in a Context Map in the form of diagrams, text, or conversations.

      2. Distillation

        Often in larger projects the core business objects are mixed in with other supporting objects in the same modules. To better focus on your core objects, separate the other objects into their own modules which have no knowledge of the core objects.

        The better developers tend to choose to work on the more technically interesting aspects of a system which add to their personal technical resume, leaving less experienced people to work on the core business objects. This can lead to disaster. Put your best developers on the core module.

        When a domain gets too large, refactor the model to make it smaller and put less critical business models into generic domains. Only have the central business objects in a small core domain. A few ways to do this:

        1. Generic Subdomains

          Put non-core objects that are vital to your software into their own generic subdomain. These objects are needed by your core objects, but can be organised into their own modules. This allows the generic modules to be worked on by other parties and not take the time of your core module developers.

          Eric gives an example of time zone calculations as a generic subdomain. The time zone code is critical to the application, but can be put into a module which has no awareness of the core business objects.

        2. Domain Vision Statement

          A Domain Vision Statement is a few paragraphs describing what is in the core domain and perhaps what is not. This can guide the partitioning of the system into core and supporting modules.

        3. Hilighted Core

          The core objects need to be highlighted to increase their visibility. This can be done by writing a few page description of them called the "Distillation Document", highlighting them in a longer document, or adding attributes into the code.

        4. Cohesive Mechanisms

          Cohesive mechanisms are similiar to Generic Subdomains in that they reduce the clutter in the core domain by moving the complexity to another place, but Cohesive Mechanisms work with complex operations and algorithms.

        5. Segregated Core
      3. Large Scale Structure

        Eric Evans discusses four ways to help manage the complexity of large projects.

        1. System Metaphor

          A system has an easily understood metophor that drives the design.

        2. Responsibility Layers

          Modules are placed into a few layers which are dependent on the layers below them. Modules in the same layer have a cohesive story and have a similiar rate of change.

        3. Knowledge Level

          A layer of software constrains other components on their behavior.

        4. Pluggable Component Framework

          A Framework that is accessed by interfaces can be replaced with a different framework easily.

  5. Books:
    Click to read reviews or buy Domain Driven Design
    by Eric Evans
    Click to read reviews or buy Domain Driven Design Quickly
    by Abel Avram
  6. Domain Driven Design Videos with Eric Evans:

Go to Home page. Kindly report errors, typos, or misspellings here.