Domain Service
On this page
- Available Methods
- Save Aggregate
- Save Domain Events
- Update Aggregate
- Get Aggregate
- Get In-Memory Aggregate
- Save Projection
- Get Projection
- Get In-Memory Projection
- Get Events
- Get Events From Sequence
- Get Events Up To Sequence
- Get Events Between Sequences
- Get Events From Date
- Get Events Up To Date
- Get Events Between Dates
- Get Latest Event Sequence
- Related
The IDomainService interface provides a high-level API for managing aggregates and domain events in an event-sourced system. It abstracts the complexities of event storage, retrieval, and aggregate reconstruction, allowing developers to focus on business logic.
Every store provider has its own implementation of the IDomainService interface. You can use it by injecting the interface into your handlers, services, or controllers.
Available Methods
- Save Aggregate
- Save Domain Events
- Update Aggregate
- Get Aggregate
- Get In-Memory Aggregate
- Save Projection
- Get Projection
- Get In-Memory Projection
- Get Events
- Get Events From Sequence
- Get Events Up To Sequence
- Get Events Between Sequences
- Get Events From Date
- Get Events Up To Date
- Get Events Between Dates
- Get Latest Event Sequence
Save Aggregate
Saves an aggregate to the event store with optimistic concurrency control, persisting all uncommitted domain events and updating the aggregate snapshot.
New aggregate
var streamId = new CustomerStreamId(customerId);
var aggregateId = new OrderAggregateId(orderId);
var aggregate = new OrderAggregate(orderId, amount: 25.45m);
var saveAggregateResult = await domainService.SaveAggregate(streamId, aggregateId, aggregate, expectedEventSequence: 0);
Update existing aggregate
var streamId = new CustomerStreamId(customerId);
var aggregateId = new OrderAggregateId(orderId);
var latestEventSequence = await domainService.GetLatestEventSequence(streamId);
var aggregateResult = await domainService.GetAggregate(streamId, aggregateId);
if (!aggregateResult.IsSuccess)
{
return aggregateResult.Error;
}
aggregate = aggregateResult.Value;
aggregate.UpdateAmount(amount: 15.00m);
var saveAggregateResult = await domainService.SaveAggregate(streamId, aggregateId, aggregate, expectedEventSequence: latestEventSequence);
Save Domain Events
Saves an array of domain events to the event store with optimistic concurrency control, bypassing aggregate persistence. This method is ideal for scenarios where events are generated outside traditional aggregate workflows.
var streamId = new CustomerStreamId(customerId);
var latestEventSequence = await domainService.GetLatestEventSequence(streamId);
var events = new @event[]
{
new OrderPlaced
{
OrderId = orderId,
Amount = 25.45m
},
new OrderShipped
{
OrderId = orderId,
ShippedDate = _timeProvider.GetUtcNow()
}
};
var saveEventsResult = await domainService.SaveEvents(streamId, events, expectedEventSequence: latestEventSequence);
Update Aggregate
Updates an aggregate with new events from its stream, applying any events that occurred after the aggregate’s last known state. If the aggregate does not exist, a new one is stored.
var streamId = new CustomerStreamId(customerId);
var aggregateId = new OrderAggregateId(orderId);
var updateAggregateResult = await domainService.UpdateAggregate(streamId, aggregateId);
Get Aggregate
Retrieves an aggregate from the event store, either from its snapshot or by reconstructing it from events.
The ReadMode parameter controls reconstruction — see Read Modes for the decision table. The four modes are:
- SnapshotOnly (default): Retrieves the aggregate from its snapshot only. If no snapshot exists, returns null.
- SnapshotWithNewEvents: Retrieves the aggregate from its snapshot if it exists and applies any new events that have occurred since the snapshot. If no snapshot exists, returns null.
- SnapshotOrCreate: Retrieves the aggregate from its snapshot if it exists; otherwise, reconstructs it from events. If no events exist, returns null.
- SnapshotWithNewEventsOrCreate: Retrieves the aggregate from its snapshot if it exists, applies any new events that have occurred since the snapshot, or reconstructs it from events if no snapshot exists. If no events exist, returns null.
If the aggregate does not exist, but domain events that can be applied to the aggregate exist, the aggregate snapshot is stored automatically if read mode is SnapshotOrCreate or SnapshotWithNewEventOrCreate. This is useful when the domain changes, and you need a different aggregate structure. Increase the version of the aggregate type to force a snapshot creation.
var streamId = new CustomerStreamId(customerId);
var aggregateId = new OrderAggregateId(orderId);
var aggregateResult = await domainService.GetAggregate(streamId, aggregateId, ReadMode.SnapshotOrCreate);
If the aggregate does not exist and read mode is SnapshotOnly (default), the method returns null even if events that can be applied to the aggregate exist.
var streamId = new CustomerStreamId(customerId);
var aggregateId = new OrderAggregateId(orderId);
var aggregateResult = await domainService.GetAggregate(streamId, aggregateId);
Get In-Memory Aggregate
Reconstructs an aggregate entirely from events without using snapshots, providing a pure event-sourced view of the aggregate state.
var streamId = new CustomerStreamId(customerId);
var aggregateId = new OrderAggregateId(orderId);
var aggregateResult = await domainService.GetInMemoryAggregate(streamId, aggregateId);
Optionally, you can specify a sequence number or a date to reconstruct the aggregate up to a specific point in time.
var aggregateResult = await domainService.GetInMemoryAggregate(streamId, aggregateId, upToSequence);
or
var aggregateResult = await domainService.GetInMemoryAggregate(streamId, aggregateId, upToDate);
Save Projection
Saves a projection (read model) as a snapshot. Unlike an aggregate, a projection produces no events, so saving it upserts only the snapshot — no event stream is written. Each store uses a dedicated projection type: EF Core persists a ProjectionEntity in its own DomainProjections table, while Cosmos persists a ProjectionDocument in the same container as aggregates (discriminated by documentType).
Build the projection by applying the events you care about, then save it.
var streamId = new CustomerStreamId(customerId);
var projectionId = new OrderSummaryProjectionId(customerId);
var eventsResult = await domainService.GetEvents(streamId);
var projection = new OrderSummaryProjection();
projection.Apply(eventsResult.Value);
var saveProjectionResult = await domainService.SaveProjection(streamId, projectionId, projection);
Get Projection
Retrieves a previously saved projection snapshot. Returns null when no snapshot has been saved for the projection id.
var streamId = new CustomerStreamId(customerId);
var projectionId = new OrderSummaryProjectionId(customerId);
var projectionResult = await domainService.GetProjection(streamId, projectionId);
Get In-Memory Projection
Reconstructs a projection entirely from events without persisting a snapshot. The projection equivalent of Get In-Memory Aggregate — useful for one-off reads, backfilling a report, or building an ad-hoc view where you don’t want to leave a snapshot behind. When no matching events are stored, a projection with Version = 0 is returned.
var streamId = new CustomerStreamId(customerId);
var projectionId = new OrderSummaryProjectionId(customerId);
var projectionResult = await domainService.GetInMemoryProjection(streamId, projectionId);
Optionally, you can specify a sequence number or a date to reconstruct the projection up to a specific point in time.
var projectionResult = await domainService.GetInMemoryProjection(streamId, projectionId, upToSequence);
or
var projectionResult = await domainService.GetInMemoryProjection(streamId, projectionId, upToDate);
Get Events
Retrieves all domain events from a specified stream, with optional filtering by event types and/or event properties.
var streamId = new CustomerStreamId(customerId);
var eventsResult = await domainService.GetEvents(streamId);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventsResult = await domainService.GetEvents(streamId, eventTypes);
Optionally, you can also filter the events by specific event properties (key/value pairs). All entries in the dictionary must match for an event to be included. Property and type filters can be combined.
var streamId = new CustomerStreamId(customerId);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var eventsResult = await domainService.GetEvents(streamId, eventTypes, eventProperties);
Get Events From Sequence
Retrieves domain events from a specified stream starting from a specific sequence number onwards, with optional filtering by event types and/or event properties.
var streamId = new CustomerStreamId(customerId);
var fromSequence = 5;
var eventsResult = await domainService.GetEventsFromSequence(streamId, fromSequence);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var fromSequence = 5;
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventsResult = await domainService.GetEventsFromSequence(streamId, fromSequence, eventTypes);
Optionally, you can also filter the events by specific event properties.
var streamId = new CustomerStreamId(customerId);
var fromSequence = 5;
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var eventsResult = await domainService.GetEventsFromSequence(streamId, fromSequence, eventTypes, eventProperties);
Get Events Up To Sequence
Retrieves domain events from a specified stream up to and including a specific sequence number, with optional filtering by event types and/or event properties.
var streamId = new CustomerStreamId(customerId);
var upToSequence = 10;
var eventsResult = await domainService.GetEventsUpToSequence(streamId, upToSequence);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var upToSequence = 10;
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventsResult = await domainService.GetEventsUpToSequence(streamId, upToSequence, eventTypes);
Optionally, you can also filter the events by specific event properties.
var streamId = new CustomerStreamId(customerId);
var upToSequence = 10;
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var eventsResult = await domainService.GetEventsUpToSequence(streamId, upToSequence, eventTypes, eventProperties);
Get Events Between Sequences
Retrieves domain events from a specified stream from and to specific sequence numbers, with optional filtering by event types and/or event properties.
var streamId = new CustomerStreamId(customerId);
var fromSequence = 5;
var toSequence = 10;
var eventsResult = await domainService.GetEventsBetweenSequences(streamId, fromSequence, toSequence);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var fromSequence = 5;
var toSequence = 10;
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventsResult = await domainService.GetEventsBetweenSequences(streamId, fromSequence, toSequence, eventTypes);
Optionally, you can also filter the events by specific event properties.
var streamId = new CustomerStreamId(customerId);
var fromSequence = 5;
var toSequence = 10;
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var eventsResult = await domainService.GetEventsBetweenSequences(streamId, fromSequence, toSequence, eventTypes, eventProperties);
Get Events From Date
Retrieves domain events from a specified stream starting from a specific date onwards, with optional filtering by event types and/or event properties.
var streamId = new CustomerStreamId(customerId);
var fromDate = new DateTime(2024, 6, 15, 17, 45, 48);
var eventsResult = await domainService.GetEventsFromDate(streamId, fromDate);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var fromDate = new DateTime(2024, 6, 15, 17, 45, 48);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventsResult = await domainService.GetEventsFromDate(streamId, fromDate, eventTypes);
Optionally, you can also filter the events by specific event properties.
var streamId = new CustomerStreamId(customerId);
var fromDate = new DateTime(2024, 6, 15, 17, 45, 48);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var eventsResult = await domainService.GetEventsFromDate(streamId, fromDate, eventTypes, eventProperties);
Get Events Up To Date
Retrieves domain events from a specified stream up to and including a specific date, with optional filtering by event types and/or event properties.
var streamId = new CustomerStreamId(customerId);
var upToDate = new DateTime(2024, 6, 15, 17, 45, 48);
var eventsResult = await domainService.GetEventsUpToDate(streamId, upToDate);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var upToDate = new DateTime(2024, 6, 15, 17, 45, 48);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventsResult = await domainService.GetEventsUpToDate(streamId, upToDate, eventTypes);
Optionally, you can also filter the events by specific event properties.
var streamId = new CustomerStreamId(customerId);
var upToDate = new DateTime(2024, 6, 15, 17, 45, 48);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var eventsResult = await domainService.GetEventsUpToDate(streamId, upToDate, eventTypes, eventProperties);
Get Events Between Dates
Retrieves domain events from a specified stream from and to specific dates, with optional filtering by event types and/or event properties.
var streamId = new CustomerStreamId(customerId);
var fromDate = new DateTime(2024, 6, 15, 17, 45, 48);
var toDate = new DateTime(2024, 6, 25, 12, 46, 22);
var eventsResult = await domainService.GetEventsBetweenDates(streamId, fromDate, toDate);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var fromDate = new DateTime(2024, 6, 15, 17, 45, 48);
var toDate = new DateTime(2024, 6, 25, 12, 46, 22);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventsResult = await domainService.GetEventsBetweenDates(streamId, fromDate, toDate, eventTypes);
Optionally, you can also filter the events by specific event properties.
var streamId = new CustomerStreamId(customerId);
var fromDate = new DateTime(2024, 6, 15, 17, 45, 48);
var toDate = new DateTime(2024, 6, 25, 12, 46, 22);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var eventsResult = await domainService.GetEventsBetweenDates(streamId, fromDate, toDate, eventTypes, eventProperties);
Get Latest Event Sequence
Retrieves the latest event sequence number for a specified stream, with optional filtering by event types and/or event properties. This method provides the current position in an event stream, essential for optimistic concurrency control and determining where to append new events in event sourcing operations.
var streamId = new CustomerStreamId(customerId);
var latestEventSequence = await domainService.GetLatestEventSequence(streamId);
Optionally, you can filter the events by specific event types.
var streamId = new CustomerStreamId(customerId);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var latestEventSequence = await domainService.GetLatestEventSequence(streamId, eventTypes);
Optionally, you can also filter the events by specific event properties.
var streamId = new CustomerStreamId(customerId);
var eventTypes = new Type[] { typeof(OrderPlaced), typeof(OrderShipped) };
var eventProperties = new Dictionary<string, string> { ["OrderId"] = orderId.ToString() };
var latestEventSequence = await domainService.GetLatestEventSequence(streamId, eventTypes, eventProperties);
Related
- Read Modes — what each of the four modes reconstructs
- Aggregates and Streams — what an aggregate, a stream and their identifiers are
- Projections — the read-model side of
SaveProjectionandGetProjection - Replay events in memory — reconstructing without persisting a snapshot
- Entity Framework Core Extensions — the same operations as
DbContextmethods