[−][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 anenum
, - define how to handle commands through the
handle
method, - specify the Aggregate's Domain Events through the
Event
associated type, usually anenum
, - define the transition logic from one state to another by an
Event
, through theapply
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 Event
s
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:
- Through the
AggregateRootBuilder
, useful for testing - Through a
Repository
instance
More on the Repository
in the module-level documentation.
Structs
AggregateRoot | An |
AggregateRootBuilder | Builder type for new |
Repository | Implementation of the Repository pattern for storing, retrieving
and deleting |
Traits
Aggregate | An Aggregate manages a domain entity |
AggregateExt | Extension trait with some handy methods to use with |
Optional | An |
Type Definitions
AggregateId | A short extractor type for the Aggregate |