Domain Layer Stereotypes

We can view the domain layer as a collection of domain objects. Some of these objects are in the heap, others are in files of databases. Of course new domain objects can appear and obsolete ones can disappear.

We can classify domain objects into five categories: references, values, repositories, factories, and services.

(Of course domain objects can be categorized by the classes they instantiate. What I mean by "category" is a set of classes. Some sets of classes can be regarded as meta-classes. Meta-classes are part of UML and can be represented by defining and using stereotypes: <<reference>>, <<value>>, <<repository>>, <<factory>>, and <<service>>.)

References (aka: Entities)

A reference object refers to something in the domain such as an employee, transaction, or shipping order. (Note that an entity can represent an abstraction such as an event or order as well as a concrete entity such as a person.)

IDs

An entity is distinguished by its identity, not its attributes. For example, transferring $100 from my savings account to my checking account twice at the same moment (e.g. joint accounts with my spouse) is represented by two distinct transaction objects with the same attributes. Because an entity may move between applications and storage, it's not a good idea to equate identity with memory address. While not ideal, it's possible to have two entities referencing the same domain object (one in memory the other in a database, or one in the heap and the other on the stack). To solve this problem each entity should be equipped with a unique identity tag. Uniqeness is difficult to insure for objects that get passed around a lot between applications. Otherwise a static counter can be used:

class Employee {
   private static long nextID = 500; // initialize big to make company look big
   private String id; // read-only, no setter provided
   public Employee(...) {
      id = "Employee_" + nextID++;
      // etc.
   }
   // etc.
}
     

Aggregates

An aggregate is a collection if entity and value objects that represents a domain object together with its components and properties.

Letting clients access the components of an aggregate is risky. All access should be through a designated root object. (Usually this will represent the aggregate itself.)

Aggregates need to stay together. Saving, reading, deleting, or archiving the root of an aggregate saves, reads, deletes, or archives all of its components.

In the example below the components are only used to communicate with customers through root methods:

UML allows us to represent the relationship between aggregates and their components using the diamond notation on the aggregate's endpoint.

In UML we can distinguish between shared aggregation (hollow diamond—components may be shared between aggregates) and owned aggregation (aka composition, solid diamond—components are not to be shared.)

For example, if customers can share addresses we could model this as:

We try to avoid sharing inside of an aggregation. For this reason I have moved the Address class outside of the aggregation boundary.

Value Objects

In contrast with entities, value objects are distinguished by their attributes and therefore do not need to be unique. For example, in a graphics application a canvas contains many shapes and each shape has a color, which can be changed:

 

A color is determined by three integers: the amount of red (light), the amount of green, and the amount of blue. Two shapes have the same color if their colors have the same amounts of red, green, and blue, even if their color fields point to different instances of the Color class.

For example a canvas may contain three red shapes, but the object diagram looks like this:

Here's an implementation of Color:

class Color {
   private int red, green, blue;
   public Color(int red, int green, int blue) {
      this.red = red;
      this.green = green;
      this.blue = blue;
   }
   public boolean equals(Object other) {
      if (other == null) return false;
      if (!other.getClass().equals(this.getClass()) return false;
      Color otherColor = (Color)other;
      return (this.red == otherColor.red && this.green == otherColor.green && this.blue = otherColor.blue);
   }
   public int hashCode() { return "color" + red + green + blue).hashCode(); }
   // etc.
}

Notice that two colors with the same attributes are considered logically equal even though they may not be literally equal (as determined by the == operator).

We don't provide setters for a value object's fields. Changing a field amounts to changing the identity of the object.

Services

A domain service is a function that updates or transforms domain objects. For example, an online banking application might provide services for transferring cash to an external account. We could represent this service as an object, but unless we sometimes treat the service as data, then this is unintuitive. In C++ we could represent a service as a global function:

void transferFunds(Account source, Money amount, int destRoutingNumber, int destAcctNumber) { ... }

In Java we can create a special service class containing only static methods. (Such classes are also known as utility classes.) To avoid confusion we could declare the default constructor private, thus preventing the service class from being instantiated.

In Scala we can simply declare a singleton service object:

object Services {
   def transferFunds(...) { ... }
   // etc.
}

Entity Lifecycle

Here's the lifecycle of an entity:

Factories

Creating an object means initializing all fields so that all constraints are satisfied. For aggregates, this means creating and initializing all of the components. The details of construction don't really have much to do with the object's methods. The Factory Method Pattern separates logic from creation by creating a special factory object.

Here's a sketch of a factory for customers:

class CustomerFactory {
   private Phone makePhone(...) { ... }
   private Email makeEmail(...) { ... }
   private Address makeAddress(...) { ... }
   public Customer makeCustomer(...) { ... }
}

I could have implemented this as a utility class (all methods are static). In Scala I might have used a singleton. However, this would preclude polymorphically substituting a factory that creates customers with land line phones with one that creates customers with mobile phones. (Assuming land line and mobile are sub-classes of Phone.)

Repositories

A repository hides the details of how objects—specifically the roots of aggregates—are stored and archived (XML, binary, database, etc.)