[][src]Module eventually::aggregate

Module containing support for the Aggergate pattern.

What is an Aggregate?

An Aggregate is the most important concept in your domain.

It represents the entities your business domain is composed of, and the business logic your domain is exposing.

For example: in an Order Management bounded-context (e.g. a microservice), the concepts of Order or Customer are two potential Aggregates.

Aggregates expose mutations with the concept of commands: from the previous example, an Order might expose some commands such as "Add Order Item", or "Remove Order Item", or "Place Order" to close the transaction.

In Event Sourcing, the Aggregate state is modified by the usage of Domain Events, which carry some or all the fields in the state in a certain logical meaning.

As such, commands in Event Sourcing will produce Domain Events.

Aggregates should provide a way to fold Domain Events on the current value of the state, to produce the next state.

Aggregate in eventually

eventually supports the Aggregates through the Aggregate trait.

An Aggregate allows to:

  • specify the available commands through the Command associated type, usually an enum,
  • define how to handle commands through the handle method,
  • specify the Aggregate's Domain Events through the Event associated type, usually an enum,
  • define the transition logic from one state to another by an Event, through the apply method.

An Aggregate is essentialy an actor, which means it should contain all the dependencies it needs to execute commands (e.g. repositories, external clients, validators, etc...)

Let's take the Orders Management example:

use eventually::Aggregate;
use futures::future::BoxFuture;

// This is the state of our Order, which contains one or more items,
// and whether it has been placed or not.
#[derive(Default)]
struct OrderState {
    items: Vec<OrderItem>,
    placed: bool,
}

// This is a single order item, which contains the order SKU, the number
// of quantities of the same item and the price.
struct OrderItem {
    sku: String,
    quantity: u32,
    price: f32,
}

// Let's say we have a catalog of available items in our system.
//
// We want to make sure that each item that has been added to the Order
// is available in the catalog.
//
// We're going to use this trait in the Aggregate implementation,
// when handling events to add OrderItems.
trait OrderItemsCatalog {
    fn get_item(&self, sku: String) -> BoxFuture<OrderItem>;
}

// This is the list of all the commands that are available on our Aggregate.
enum OrderCommand {
    // Make the order final and place it to the production line.
    PlaceOrder,
    // Add an OrderItem to the Order, specifying a quantity.
    AddOrderItem {
        sku: String,
        quantity: u32,
    },
    // Remove an OrderItem from the Order: the item is removed
    // when the quantity in the Order is less or equal than
    // the quantity asked to be removed by the command.
    RemoveOrderItem {
        sku: String,
        quantity: u32,
    },
}

// This is the list of all the domain events defined on the Order aggregate.
enum OrderEvent {
    // The list of OrderItems is updated with the specified value.
    OrderItemsUpdated {
        items: Vec<OrderItem>,
    },
    // Marks the order as placed.
    OrderPlaced,
}

// This is our Aggregate actor, which will contain all the necessary
// dependencies for handling commands.
//
// In this case, it will only contain the OrderItemCatalog.
struct OrderAggregate {
    catalog: std::sync::Arc<dyn OrderItemsCatalog>,
}

// Implementation for the Aggregate trait.
impl Aggregate for OrderAggregate {
    type Id = u64;
    type State = OrderState;
    type Event = OrderEvent;
    type Command = OrderCommand;
    type Error = std::convert::Infallible; // This should be a meaningful error.

    fn apply(state: Self::State, event: Self::Event) -> Result<Self::State, Self::Error> {
        unimplemented!()
    }

    fn handle<'a, 's: 'a>(
        &'a self,
        id: &'s Self::Id,
        state: &'s Self::State,
        command: Self::Command,
    ) -> BoxFuture<'a, Result<Option<Vec<Self::Event>>, Self::Error>>
    where
        Self: Sized
    {
        unimplemented!()
    }
}

Note on State

An Aggregate's State type needs to implement the Default trait, to always have an initial state representation.

This is very important for functional folding of Events done by apply.

A common type used in State is Option<StateType>, where StateType doesn't usually implement Default. This is to represent a nullable state before the first command is received.

Given the common use-case, eventually has included support for the Optional aggregate trait, where you can use StateType directly in the State associated type.

Optional is compatible with the Aggregate trait through the as_aggregate method, or the optional::AsAggregate newtype adapter.

Interacting with Aggregates using AggregateRoot

Interaction with Aggregates is doable through an AggregateRoot.

An AggregateRoot represents a specific Aggregate instance, by managing its State and the access to submit commands.

Access to an AggregateRoot is obtainable in two ways:

  1. Through the AggregateRootBuilder, useful for testing
  2. Through a Repository instance

More on the Repository in the module-level documentation.

Structs

AggregateRoot

An AggregateRoot represents an handler to the Aggregate it's managing, such as:

AggregateRootBuilder

Builder type for new AggregateRoot instances.

Repository

Implementation of the Repository pattern for storing, retrieving and deleting Aggregates.

Traits

Aggregate

An Aggregate manages a domain entity State, acting as a transaction boundary.

AggregateExt

Extension trait with some handy methods to use with Aggregates.

Optional

An Option-flavoured, Aggregate-compatible trait to model Aggregates having an optional State.

Type Definitions

AggregateId

A short extractor type for the Aggregate Id.