Aggregate stream
Concept
So far, we figured out that an Aggregate is the transaction and consistency boundary within the domain model.
The aggregate store (application-level abstraction) uses an event store (infrastructure) to store events in streams. Each aggregate instance has its own stream, so the event store needs to be capable to read and write events from/to the correct stream.
When appending events to a stream, the append operation for a single stream must be transactional to ensure that the stream is consistent. Eventuous handles commands using the command service, and one command handler is the unit of work. All the events generated by the aggregate instance during the unit of work are appended to the stream as the final step in the command handling process.
Stream name
By default, Eventuous uses the AggregateType.Name
combined with the aggregate id as the stream name. For example, the Booking
aggregate with id 1
has a stream name Booking-1
. That's what StreamName.For<Booking>(1)
returns.
However, you might want to have more fine-grained control over the stream name. For example, you might want to include the tenant id in the stream name. It's possible to override the default convention by configuring the stream name mapping. The stream map contains a mapping between the aggregate identity type (derived from AggregateId
) and the stream name generation function. Therefore, any additional property of the aggregate identity type can be used to generate the stream name.
For example, the following code registers a stream name mapping for the Booking
aggregate:
public record BookingId : AggregateId {
public BookingId(string id, string tenantId) : base(id) {
TenantId = tenantId;
}
public string TenantId { get; }
}
public class BookingService : CommandService<Booking, BookingState, BookingId> {
public BookingService(IAggregateStore store, StreamNameMap streamNameMap)
: base(store, streamNameMap: streamNameMap) {
// command handlers registered here
}
}
var streamNameMap = new StreamNameMap();
streamNameMap.Register<Booking, BookingState, BookingId>(
id => $"Booking-{id.TenantId}-{id.Id}"
);
builder.Services.AddSingleton(streamNameMap);
builder.Services.AddCommandService<BookingService>();