Skip to content
Working with Aggregates

Working with Aggregates

Now that we have our aggregate store, we can begin working with User aggregates in our application.

Creating Aggregates and Appending Events

Create a new aggregate with New():

userID := uuid.Must(uuid.NewV4())

newUser := aggregateStore.New(userID)

We’ll also append an event to change the User’s name to “Juliette”. Appending only queues the event on the aggregate; nothing is persisted or applied until the aggregate is saved:

newUser.Append(UserNameChanged{NewName: "Juliette"})

Event Metadata

Events can carry key-value metadata, persisted alongside the event data. Attach metadata to specific events with AppendWithMetadata:

newUser.AppendWithMetadata(map[string]string{"request_id": requestID},
    UserNameChanged{NewName: "Juliette"},
)

To amend all of an aggregate’s pending events at once — the natural fit for ambient context like correlation or trace IDs, typically injected by a store decorator before it delegates the save — use MergeEventMetadata:

newUser.MergeEventMetadata(map[string]string{"correlation_id": correlationID})

Metadata keys prefixed estoria. are reserved for Estoria itself; a save whose events carry one fails before anything is appended.

Saving Aggregates

To save an aggregate, use Save():

_ = aggregateStore.Save(ctx, newUser, nil)

Saving appends the queued events to the aggregate’s event stream, then applies them to the in-memory state. If a save fails after its events were durably appended, the error carries the aggregatestore.ErrEventsAppended sentinel (check with errors.Is); recover by discarding the aggregate and reloading it.

Loading Aggregates

To load an aggregate, use Load():

loadedUser, _ := aggregateStore.Load(ctx, userID, nil)

The aggregate’s state can be accessed using .State():

user := loadedUser.State()

We can also see what version the aggregate is at (i.e. how many events have been applied to it):

fmt.Printf("user %s is at version %d", user.Name, loadedUser.Version())