liveBook · Manning

Aggregate root

As explained in Spring in Action, Sixth Edition

The designated domain object that serves as the entry point for access to the objects within an aggregate.

An aggregate root is the domain object used as the entry point to an aggregate. In a taco-ordering application, TacoOrder is the root: a Taco belongs to an order and does not exist outside that order’s context. Ingredient is different. Each Ingredient belongs to its own aggregate, and tacos refer to ingredients through ingredient references. section 3.1.3

That boundary shapes persistence. With JDBC, OrderRepository saves a TacoOrder and the Taco objects that go with it, rather than requiring a separate Taco repository. Saving the order also means inserting its tacos and the Ingredient_Ref rows that connect each taco to its ingredients. The implementation retrieves database-generated IDs so it can make those links. section 3.1.4

package tacos.data;
 
import java.util.Optional;
 
import tacos.TacoOrder;
 
public interface OrderRepository {
 
  TacoOrder save(TacoOrder order);
  
}

The OrderRepository interface exposes a save operation for TacoOrder, the aggregate root. section 3.1.4

A root-level operation is not automatic in every persistence approach. With the Spring Data R2DBC repositories shown here, tacos must first be saved separately, then the order that references them; reading an order yields taco IDs rather than complete Taco objects. A service in front of both repositories is proposed to provide the desired aggregate-style behavior. section 13.1.3

For MongoDB documents, the boundary appears in the mapping: TacoOrder is annotated with @Document and contains a list of tacos. Taco is saved as part of that order rather than as its own document, while Ingredient is a separate document. section 13.2.1

Listing 13.14 A TacoOrder class annotated for Mongo persistence
package tacos;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
 
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
 
import lombok.Data;
 
@Data
@Document
public class TacoOrder implements Serializable {
  private static final long serialVersionUID = 1L;
  
  @Id
  private String id;
  private Date placedAt = new Date();
  
  private User user;
 
  private String deliveryName;
 
  private String deliveryStreet;
 
  private String deliveryCity;
 
  private String deliveryState;
 
  private String deliveryZip;
 
  private String ccNumber;
 
  private String ccExpiration;
 
  private String ccCVV;
 
 
  private List<Taco> tacos = new ArrayList<>();
  
  public void addTaco(Taco taco) {
    tacos.add(taco);
  }
 
}

The TacoOrder class shows @Document on the root and a list that holds its tacos. section 13.2.1

How Spring in Action, Sixth Edition covers it

How deep it goes
Explains the boundary through JDBC save steps and MongoDB mapping, while identifying R2DBC repository behavior that needs a service-level workaround.
Examples
Uses a TacoOrder containing tacos, separate Ingredient objects, an OrderRepository interface, and a MongoDB document class.
What it assumes
Introduces the aggregate boundary in plain terms but assumes familiarity with domain objects when moving into repository implementation.
What you can do after
Build it from scratch: define a JDBC repository that saves an order with its tacos and ingredient references.
How it connects
Connects aggregate roots to domain-driven design, repositories, relational references, and MongoDB documents.

Also explained in

Compare how 3 books explain aggregate root →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Spring in Action, Sixth Edition. The section links point to the parts of the book it draws on.