+
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 new, but he brings a common vocabulary and organization to design that is helpful.

  1. 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 persistance, 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.

  4. 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?

  5. 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 Aggreagate 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 consistancy.
      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 Persistance 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
  6. 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.

  7. 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();
        }
    }
    }
  8. 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 ulitmate 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.

  9. 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 persistance, just to Interfaces for those objects.

  10. 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
  11. 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.

  12. Preserving Model Integrity

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

    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.

    2. 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.

    3. Context Map

      How do all the contexts in a large project relate? This should be described in a Context Map.

    4. 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.

    5. 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.

    6. Conformist

      When the Customer-Supplier relationship breaks down, sometimes the Customer must simply conform to the code/database schema of the Supplier.

    7. 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.

    8. 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.

    9. 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.

    10. Distillation

      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 the core domain.

  13. 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.

  14. 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 couragous and make big changes.

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


Click to read reviews or buy Domain Driven Design Quickly
Domain Driven Design Quickly
by Abel Avram
Click to read reviews or buy Domain Driven Design
Domain Driven Design
by Eric Evans
+
+