liveBook · Manning

Aggregate

As explained in Simple Object-Oriented Design: Create clean, maintainable applications

An aggregate is a cluster of related domain objects treated as a consistency boundary and accessed through a designated root.

An aggregate is a cluster of related domain objects treated as one consistency boundary. Its aggregate root is the only object clients access: it coordinates changes across the object tree so internal objects cannot be changed without its knowledge. section 3.5

This is more than hiding a list of children. Choose an aggregate when domain relationships carry invariants that must stay correct together. Pass and persist the whole root, and use one repository or data access object per root rather than one per internal database entity. section 3.5

Offering and enrollment

An Offering owns multiple Enrollments. Offering.enroll creates an Enrollment and reduces available spots; Offering.cancel finds and cancels the enrollment, then restores a spot. Direct enrollment cancellation could leave the available-spots count wrong, so clients change enrollment only through Offering. section 3.5.2 section 3.5

Listing 3.10 Offering as an aggregate root
class Offering {
 
  private int id;
  private Training training;
  private Calendar date;
  private List<Enrollment> enrollments;                      #1
  private int maximumNumberOfAttendees;
  private int availableSpots;
 
  public Offering(
   Training training,
   Calendar date,
   int maximumNumberOfAttendees) {
    // basic constructor
  }
 
  public void enroll(Employee employee) {                    #2
    if(!hasAvailableSpots())
      throw new OfferingIsFullException();
 
    Calendar now = Calendar.getInstance();
    enrollments.add(new Enrollment(employee, now));
    availableSpots--;
  }
 
  public void cancel(Employee employee) {                    #3
    Enrollment enrollmentToCancel = findEnrollmentOf(employee);
    if(enrollmentToCancel == null)
      throw new EmployeeNotEnrolledException();
 
    Calendar now = Calendar.getInstance();
    enrollmentToCancel.cancel(now);
 
    availableSpots++;
  }
 
  private Enrollment findEnrollmentOf(Employee employee) {   #4
    // loops through the list of enrollments and
    // finds the one for that employee
    // ...
  }
}

The listing makes the boundary concrete: Offering owns enrollments and exposes enroll and cancel operations that maintain its attendance invariant. section 3.5.2

Apply the boundary pragmatically:

  • Question whether a child truly belongs in the aggregate when no invariant requires coordinated updates; if not, split it into smaller parts. section 3.5.1
  • Consider eventual consistency and domain events when separate aggregates must be notified of changes. section 3.5.1
  • Break the boundary only after weighing a framework, performance, or boilerplate benefit against the maintenance cost and risk to invariants. section 3.5.1

How Simple Object-Oriented Design: Create clean, maintainable applications covers it

How deep it goes
Explains root-only access, repository boundaries, and a worked Offering–Enrollment design; defers deeper aggregate-root design.
Examples
Uses Basket–Item–Product and Offering–Enrollment relationships, including enrollment cancellation and available spots.
What it assumes
Assumes familiarity with entities, object relationships, persistence, and repositories; introduces aggregate-root rules directly.
What you can do after
Apply it by routing related changes and persistence through an aggregate root while evaluating exceptions pragmatically.
How it connects
Connects aggregates to domain-driven design, repositories, database integrity, invariants, eventual consistency, and domain events.

Also explained in

Compare how 3 books explain aggregate →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Simple Object-Oriented Design: Create clean, maintainable applications. The section links point to the parts of the book it draws on.