Characterization

Careful characterization of the classes is an important activity when doing Domain-Driven Design. Sometimes it is fairly obvious in what category a particular class belongs, other times it is not as easy to sort out the different Building Blocks of a Model-Driven Design.

The trickiest ones to classify are typically Entities, Aggregates, Value Objects and Domain Events. When possible, we tend to favor Value Objects over Entities or Domain Events, because they require less attention during implementation. Value Objects can be created and thrown away at will, and since they are immutable we can pass them around as we wish. We must be much less cavalier with Entities as identity and life-cycle have to be carefully managed. Below is a short walkthrough of key classes in the application and the motivation behind their implementation choice. All but Domain Event are thoroughly described in Eric Evans' book. A short introduction to Domain Events can be found here: http://martinfowler.com/eaaDev/DomainEvent.html

Entities

Cargo: Cargo has both a clear identity and a life-cycle with state transitions that we care about, so it's an entity. Many cargo instances will exist in the system simultaneously. The different instances have the same origin and destination, they may even contain the same kind of things, but it is important for us to be able to track individual cargo instances. In our case the cargo's identity is its tracking id. The tracking id is assigned upon creation and is never changed. This is the handle used to track the cargo's progress, e.g. from the tracking website.

The cargo's delivery state will change during it's lifetime. It's transport status will start out as NOT_RECEIVED, i.e. booked but not yet handed over to the shipping company at the port, and in the normal case ends its life as CLAIMED (note that this is a property of the cargo's Delivery}, tracking the current state the cargo. During a cargo's lifetime it may be assigned new destinations, its itinerary may be changed many times and it's Delivery will be recalculated as new HandlingEvent}s are received.

Voyage: Voyage is a vessel's trip from orgin to destination, typically made up of several segments, (CarrierMovments). In the sample app Voyage consists of a Schedule with the different CarrierMovements in it and has a very clear notion of identity, VoyageNumber, This id could be something like a flight number for air shipments or a vessel voyage number for a ship, it is not the name or the identification of the actual vessel. To see this domain concept in a different context, try searching the current vessel schedule for Emma Maersk at http://www.maerskline.com/ (please note that we are not associated with Maersk in any way).

Value Objects

Leg: Leg consists of a starting point and an ending point (to Location and from Location), and a reference to a voyage. A leg has no sense of identity; two legs with the same from Location, end Location and Voyage are in our model completely interchangeable. We implement Leg as an immutable Value Object.

Itinerary: An Itinerary consists of a list of Legs, with the load Location of the first Leg in the list as the starting point of the Itinerary and the unload Location of the last Leg as the final destination. The same reasoning applies to Itineraries as to Legs, they do not have identity and are implemented as Value Objects. Now, a Cargo can certainly have its Itinerary updated. One way to accomplish this would be to keep the original Itinerary instance and update the legs in the Itinerary's list, in this case the Itinerary must be mutable and has to be implemented as an Entity. With the Itinerary as a Value Object, as in the case of the sample application model and implementation, updating it is a simple operation of acquiring a complete new Itinerary from the RoutingService and replacing the old one. Implementation of a Cargo's Itinerary management is much simplified by having the Itinerary as a Value Object.

Domain Event

Some things clearly have identity but no life-cycle, or an extremely limited life-cycle with just one state. We call these things Domain Events and they can be viewed as hybrid of Entities and Value Objects. In the sample application HandlingEvent is a Domain Event that represent a real-life event such as a Cargo being loaded or unloaded, customs cleared etc. They carry both a completion time and a registration time. The completion time is the time when the event occurred and the registration time is the time when the event was received by the system. The HandlingEvent id is composed of the cargo, voyage, completion time, location and type (LOAD, UNLOAD etc).

Aggregates

In real life most things are connected, directly or indirectly. Mimicking this approach when building large software systems tend to bring unnecessary complexity and poor performance. DDD provides tactics to help you sort these things out, aggregates being one of the most important ones. Aggregates help with decoupling of large structures by setting rules for relations between entities. Aggregates can also have properties, methods, and invariants that doesn't fit within one single class. Java and other OO-languages typically miss specific language constructs to handle aggregates and in the sample application responsibilities that belong to an aggregate is most often implemented in the aggregate root.

cargo: cargo is the central aggregate in the sample application. Cargo is the aggregate root and the aggregate also contains the Value Objects Delivery, Itinereray, Leg and a few more classes.

handling: handling is another important aggregate. It contains the HandlingEvents that are registered throughout a cargo's progress from NOT_RECEIVED to CLAIMED. The HandlingEvents have a relation to the Cargo for which the event belongs, this is allowed since Cargo is an aggregate root.

The main reason for not making HandlingEvent part of the cargo aggregate is performance. HandlingEvents are received from external parties and systems, e.g. warehouse management systems, port handling systems, that call our HandlingReportService webservice implementation. The number of events can be very high and it is important that our webservice can dispatch the remote calls quickly. To be able to support this use case we need to handle the remote webservice calls asynchronously, i.e. we do not want to load the big cargo structure synchronously for each received HandlingEvent. Since all relationships in an aggregate must be handled synchronously we put the HandlingEvent in an aggregate of its own and we are able processes the events quickly and at the same time eliminate dead-locking situations in the system.

Repositories

With the aggregates and their roots defined its fairly trivial to define the Repositories. The Repositories work on aggregate roots and in the sample application there is one Repository per aggregate root, the CargoRepository is responsible for finding and storing Cargo aggregates. The finders return Cargo instances or lists of Cargo instances.

The Repository interfaces are part of the domain layer, their implementations are part of the infrastructure layer. E.g. CargoRepository has an Hibernate implementation in the infrastructure layer: CargoRepositoryHibernate.

Services

There are two basic kinds of services in the sample application:

  • Domain services encapsulate domain concepts that just are not naturally modeled as things.
  • Application services constitute the application, or service, layer.

Domain services

Domain services are expressed in terms of the ubiquitous language and the domain types, i.e. the method arguments and the return values are proper domain classes. Sometimes, only the service interface (what the service does) is part of the domain layer, but the implementation (how the service does it) is part of the infrastructure layer. This is analogous to how repository interfaces are part of the domain layer, but the Hibernate implementations are not.

A good example of that is the RoutingService, which provides access to the routing system and is used to find possible routes for a give specification. The implementation communicates with another team's context and translates to and from an external API and data model.

On the other hand, if the service is possible to implement strictly using the domain layer, both the interface and the implementation could be part of the domain layer.

Application services

The application services in the sample application are responsible for driving workflow and coordinating transaction management (by use of the declarative transaction management support in Spring). They also provide a high-level abstraction for clients to use when interacting with the domain. These services are typically designed to define or support specific use cases. See BookingService or HandlingEventService.

In some situations, e.g. when dealing with graphs of lazy-loaded domain objects or when passing services' return values over network boundaries, the services are wrapped in facades. The facades handle ORM session management issues and/or convert the domain objects to more portable Data Transfer Objects) that can be tailored to specific use cases. In that case, we consider the DTO-serializing facade part of the interfaces layer. See BookingServiceFacade for an example.