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>>.)
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.)
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.
}
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.
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.
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.
}
Here's the lifecycle of an entity:
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.)
A repository hides the details of how objects—specifically
the roots of aggregates—are stored and archived (XML, binary, database, etc.)