Graduate Program KB

Using the Language: An Extended Example

Introducing the Cargo Shipping System

  • Initial Requirements
    1. Track key handling of customer Cargo
    2. Booking cargo in advance
    3. Automatically sending invoices to customers when cargo reaches some point in handling

Figure7.1

  • No arrows yet, will be added later with associations
  • This model is used to organise domain knowledge and provides language for the team.
  • Customers can have multiple different roles with a specific cargo, e.g) shipper, receiver or payer
  • Handling events are actions that happen to the cargo, unloading, delivered, etc.
  • A cargo has a delivery goal which is a specification, follows the specification pattern in chapter 9
    • The cargo object could be responsible for the delivery specification but this will overload Cargo with more resposibilities
  • Carrier movement is the trip that a cargo takes from a location to another location
  • Model refinement is an iterative process and can be improved later on the developement

Isolating the Domain: Introducing the Applications

  • Applying layered Architecture to prevent responsibilities from being all over the place
  • 3 Client Functions for Application Layer
    1. Tracking: Access past or present handling of a particular cargo
    2. Booking: Allows new cargo to be registered
    3. Incident Logging: Records events of handling for cargo
  • These application classes are coordinators and should only ask questions.
  • Working out the answers for these questions are for domain layers

Distinguishing Entities and Value Objects

  • Entities:

    • Customer: Could be person or company, and has a unique ID assigned to them on the first sales contact
    • Cargo: Each cargo has their own tracking ID because you want to distinguish between cargoes
    • Handling Event and Carrier Movement: Reflects real world events, as a way to track individual events. The same cargo can't be loaded and unloaded at the same time
    • Location: 2 places with the same name are not the same. Could be used for domain specific concerns
    • Delivery History: Not interchangeable, and each cargo has their own history. Doesn't have own identity but borrows from cargo.
  • Value Objects

    • Delivery Specification: 2 Cargoes can go the same place and can share the same delivery specification. These cargoes also have their own delivery history which is used to satisfy the specification, but they don't share history.
    • Other attributes like role or timestamps have no use for history or continuity, because they are only used describe the object.

Designing Associations in the Shipping Domain

  • In the first diagram it is bidirectional associations
  • Traversal direction captures the insight into the domain Figure7.2
  • Anything with no arrows is bidirectional
  • Instead of every customer have a reference to every cargo they deal with, we make each cargo have customers
  • Then we can find cargoes by customers using a database query
  • The cargo is only responsible for managing its own identity, and should be cluttered with other responsibilities such as the delivery specification.
  • There is a circular reference in model. Cargo knows its delivery history, which then holds handling events, which points back to cargo.
  • We can workaround this in Java by making Delivery History a list object containing Handling events.
  • Or we can use a database lookup with cargo as the key, but is suited for infrequent lookups for history.
  • But if there are frequent lookups for history, it would be better to maintain a direct pointer.

Aggregate boundaries

  • Customer, Location, and Carrier Movement are entities shared by many cargoes, so they must be aggregate roots
  • Cargo is an obvious aggregate root, but what would the boundary be?
    • Could contain everything specific to a specific cargo
      • Delivery History: would not look up without also wanting the cargo
      • Delivery Specification: is a value object, so there's no issue including it
      • Handling Event: Might want to find all the operations to prepare for a particular Carrier Movement
        • The act of handling has meaning separate from a particular cargo
        • So the handling event should be its own aggregate root

aggregate-boundaries

Selecting repositories

  • Only an aggregate root can have a repository
  • Customer
    • User needs to select customers for particular roles (shipper, receiver, etc...) - need a Customer Repository
  • Location
    • User needs to select a location for the destination - need a Location Repository
  • Carrier Movement
    • User needs to look up the Carrier Movement a Cargo is being loaded onto - need a Carrier Movement Repository
  • Cargo
    • User needs to tell system what cargo is being loaded - need a Cargo repository
  • Handling event
    • Decided to implement as a collection in the first iteration
    • Don't yet have an application requirement to find out what is loaded on a Carrier Movement

repositories

Walking through scenarios

  • Check that we can solve application problems using the domain

Changing the destination of a cargo

  • Delivery Specification is a value object
  • Can easily throw away and make a new one

Repeat business

  • Customers tend to repeat similar shipments
  • Want to use previous Cargoes as prototypes for new ones
  • Application can find a previous Cargo in the repository and create a new one based on that
  • Prototype pattern
  • Cargo is an aggregate root - need to be careful not to violate constraints while copying
    • Delivery history
      • Create a new one - old one doesn't apply
    • Customer roles
      • Likely to play the same roles in the new cargo, copy references
    • Tracking ID
      • Need a new one
    • Delivery specification
      • Not mentioned in the book
      • Should copy it - the point of the prototype is for the user to not respecify it
  • Do not modify anything outside the aggregate boundary

Object creation

Cargo

  • Create with an empty delivery history and null delivery specification
    • Even from a prototype?
  • Two-way association between Cargo and Delivery History
    • Incomplete without each other
    • Need to be constructed together
    • Cargo is the aggregate root, so its constructor or factory can create the Delivery History
    • Delivery History's constructor can take the Cargo

Handling event

  • Is an entity - all attributes that define identity must be in the constructor
  • Uniquely identified by Cargo, completion time, event type
    • Need to be passed
  • Non-identifying attributes
    • Can be added to an entity later - don't have any here
  • Could be convenient and more expressive to have factory methods for each event type
    • Frees client from knowledge of implementation
  • Cycle from Cargo -> Delivery History -> Handling Event -> Cargo
    • Delivery history has a collection of Handling Events
    • Back-pointer to Cargo needs to be created or the objets are inconsistent handling-event

Alternative design of the Cargo aggregate

  • Need frequent refactoring to take advantage of new insights to improve the model and the design
  • We need to update Delivery History when adding a Handling Event
    • Involves the Cargo aggregate
    • Issues with concurrent access, fail or delay the transaction if someone else is modifying Cargo
  • Adding a Handling Event needs to be quick and not cause contention
  • Could replace the collection of events with a query
    • Don't need to lock Cargo
    • Probably already using a query in the implementation to handle the collection
    • Can also optimise frequent queries, e.g. most recent events
    • Leaves Delivery History with no state - was a collection, now just a query
  • Object database
    • If accesses are frequent collection could be faster than query

Modules

modules-infrastructure

  • Objects have been grouped by type: Entity, Value, Service
  • Low cohesion, objects in a modules not conceptually related
  • High coupling, associations run between modules modules-domain
  • Should package based on cohesive concepts, e.g. Customer, Shipping, Billing
  • What do we want to communicate?
  • Module names become part of the Ubiquitous Language
  • Sales and marketing deal with customers, operations deal with shipping, back office handles billing

Introducing a new feature

  • Sales division use other software to manage client relationships, sales projections, etc...
  • Feature: yield management, allocate how much cargo of a specific type to attempt to book based on various factors
    • Goals of how much will be sold of each type
    • More profitable businesses will not be crowded out by less profitable
    • Avoiding over and under booking
  • Want to integrate with shipping software
  • When a booking is entered it needs to be checked against allocations to see if it should be accepted
  • Booking application needs to request how much Cargo of the type has been booked from the Cargo Repository
  • Also needs to request how much can be booked from the Sales Management System

Connecting the systems

  • Sales Management System was not written with the same model
  • Don't want to pollute ubiquitous language with terms from the system
  • Create a class to translate between the model and the language of the sales management system
    • Only features the application needs
  • Anticorruption layer
  • Define a service for each of the functions we need from the other system
  • Implement with a class whose name reflects its role in the system - Allocation Checker
  • If we need other integration, like using the system's customer database
    • Create another translator with services

Segmenting the business

  • Need to add the concept of a "type" of Cargo to the domain
  • Could just use category keywords from sales management system
  • But better to involve a domain expert to work out the new concept
  • Sometimes an analysis pattern can give ideas
  • Enterprise segment
    • Set of dimensions that define a way of breaking down a business
    • Add Enterprise Segment class to the domain model as a value object derived for each Cargo
    • Allocation Checker translates between Enterprise Segments and the sales management system
    • Cargo repository provides a query for count of Cargoes which match the Enterprise Segment
  • Problems:
    1. Checking the business rule belongs in the domain layer
    2. Unclear how the Booking Application derives the Enterprise Segment
    • Both these responsibilities belong in the Allocation Checker

Performance tuning

  • Can use Allocation Checker to solve performance problems with the connected system
  • Could be significant overhead in communicating with Sales Management System on another server
    • Two message exchanges for each check
      1. Deriving the enterprise segment for the cargo
      2. Checking allocation
    • Always need to check the current allocation
    • But the enterprise segment is based on relatively static data
      • Can cache
      • Tradeoff: more complicated, need to invalidate

A final look

  • Why not give Cargo the responsibility of deriving the Enterprise Segment
    • Specific to allocation, may be other Segments for other parts of the business
    • Segment could change if the sales strategy is redefined
      • Cargo would need to know about Allocation Checker - outside it's responsibility
    • Could give the Cargo a Strategy from the Allocation Checker
      • More complex, could do later if needed