Projection with FromEventSequence
The FromEventSequence() method allows you to specify which event sequence a projection should source events from. This is useful when you have multiple event sequences in your system and want to create projections that only process events from specific sequences.
Defining a projection with specific event sequence
Section titled “Defining a projection with specific event sequence”Use FromEventSequence() to specify the event sequence to source events from:
using Cratis.Chronicle.Projections;
public class DecFromEventSequenceOrderProjection : IProjectionFor<DecFromEventSequenceOrder>{ public void Define(IProjectionBuilderFor<DecFromEventSequenceOrder> builder) => builder .FromEventSequence("order-management") .AutoMap() .From<DecFromEventSequenceOrderCreated>() .From<DecFromEventSequenceOrderUpdated>() .From<DecFromEventSequenceOrderShipped>();}import io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionForimport io.cratis.chronicle.projections.Projection
@Projection(eventSequence = "order-management")class DecFromEventSequenceOrderProjection : IProjectionFor<DecFromEventSequenceOrder> { override fun define(builder: IProjectionBuilderFor<DecFromEventSequenceOrder>) { builder .from(DecFromEventSequenceOrderCreated::class) .from(DecFromEventSequenceOrderUpdated::class) .from(DecFromEventSequenceOrderShipped::class) }}import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;import io.cratis.chronicle.projections.Projection;
@Projection(eventSequence = "order-management")class DecFromEventSequenceOrderProjection implements IProjectionFor<DecFromEventSequenceOrder> { @Override public void define(IProjectionBuilderFor<DecFromEventSequenceOrder> builder) { builder.from(DecFromEventSequenceOrderCreated.class); builder.from(DecFromEventSequenceOrderUpdated.class); builder.from(DecFromEventSequenceOrderShipped.class); }}defmodule MyApp.Events.FromEventSequenceOrderCreated do use Chronicle.Events.EventType, id: "from-event-sequence-order-created"
defstruct [:customer]end
defmodule MyApp.ReadModels.FromEventSequenceOrder do use Chronicle.ReadModels.ReadModel, event_sequence: "order-management"
defstruct id: nil, customer: nil
from MyApp.Events.FromEventSequenceOrderCreated, set: [id: :event_source_id, customer: :customer]endimport { IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@projection()class DecFromEventSequenceOrderProjection implements IProjectionFor<DecFromEventSequenceOrder> { define(builder: IProjectionBuilderFor<DecFromEventSequenceOrder>): void { builder .fromEventSequence('order-management') .autoMap() .from(DecFromEventSequenceOrderCreated) .from(DecFromEventSequenceOrderUpdated) .from(DecFromEventSequenceOrderShipped); }}This projection:
- Only processes events from the “order-management” event sequence
- Ignores events from other sequences like “user-management” or “inventory-management”
- Uses the specified sequence for all event handling
Event sequence identification
Section titled “Event sequence identification”Event sequences can be identified using string names or EventSequenceId:
public static class DecFromEventSequenceEventSequences{ public const string OrderManagement = "order-management";}
public class DecFromEventSequenceOrderProjectionWithConstant : IProjectionFor<DecFromEventSequenceOrder>{ public void Define(IProjectionBuilderFor<DecFromEventSequenceOrder> builder) => builder // Using a constant instead of a raw string keeps the sequence identifier consistent // wherever it is referenced. .FromEventSequence(DecFromEventSequenceEventSequences.OrderManagement) .AutoMap() .From<DecFromEventSequenceOrderCreated>();}import io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionForimport io.cratis.chronicle.projections.Projection
object DecFromEventSequenceEventSequences { const val OrderManagement = "order-management"}
// Using a constant instead of a raw string keeps the sequence identifier consistent// wherever it is referenced.@Projection(eventSequence = DecFromEventSequenceEventSequences.OrderManagement)class DecFromEventSequenceOrderProjectionWithConstant : IProjectionFor<DecFromEventSequenceOrder> { override fun define(builder: IProjectionBuilderFor<DecFromEventSequenceOrder>) { builder.from(DecFromEventSequenceOrderCreated::class) }}import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;import io.cratis.chronicle.projections.Projection;
class DecFromEventSequenceEventSequences { // Using a constant instead of a raw string keeps the sequence identifier consistent // wherever it is referenced. public static final String ORDER_MANAGEMENT = "order-management";}
@Projection(eventSequence = DecFromEventSequenceEventSequences.ORDER_MANAGEMENT)class DecFromEventSequenceOrderProjectionWithConstant implements IProjectionFor<DecFromEventSequenceOrder> { @Override public void define(IProjectionBuilderFor<DecFromEventSequenceOrder> builder) { builder.from(DecFromEventSequenceOrderCreated.class); }}Elixir does not support this workflow yet.import { IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
const eventSequences = { orderManagement: 'order-management'};
@projection()class DecFromEventSequenceOrderProjectionWithConstant implements IProjectionFor<DecFromEventSequenceOrder> { define(builder: IProjectionBuilderFor<DecFromEventSequenceOrder>): void { builder // Using a constant instead of a raw string keeps the sequence identifier consistent // wherever it is referenced. .fromEventSequence(eventSequences.orderManagement) .autoMap() .from(DecFromEventSequenceOrderCreated); }}Read model definition
Section titled “Read model definition”The read model remains the same regardless of the event sequence:
public record DecFromEventSequenceOrder( string OrderNumber, string CustomerId, decimal TotalAmount, DecFromEventSequenceOrderStatus Status, DateTimeOffset CreatedAt, DateTimeOffset? ShippedAt);
public enum DecFromEventSequenceOrderStatus{ Created, Processing, Shipped, Delivered, Cancelled}enum class DecFromEventSequenceOrderStatus { Created, Processing, Shipped, Delivered, Cancelled}
data class DecFromEventSequenceOrder( val orderNumber: String = "", val customerId: String = "", val totalAmount: Double = 0.0, val status: DecFromEventSequenceOrderStatus = DecFromEventSequenceOrderStatus.Created, val shippedAt: String? = null)enum DecFromEventSequenceOrderStatus { Created, Processing, Shipped, Delivered, Cancelled}
class DecFromEventSequenceOrder { public String orderNumber = ""; public String customerId = ""; public double totalAmount = 0.0; public DecFromEventSequenceOrderStatus status = DecFromEventSequenceOrderStatus.Created; public String shippedAt = null;}defmodule MyApp.ReadModels.DecFromEventSequenceOrder do # status is one of :created, :processing, :shipped, :delivered, :canceled defstruct [ :order_number, :customer_id, :total_amount, :status, :created_at, :shipped_at ]endenum DecFromEventSequenceOrderStatus { Created = 'Created', Processing = 'Processing', Shipped = 'Shipped', Delivered = 'Delivered', Cancelled = 'Cancelled'}
class DecFromEventSequenceOrder { orderNumber = ''; customerId = ''; totalAmount = 0; status = DecFromEventSequenceOrderStatus.Created; createdAt = new Date(); shippedAt: Date | null = null;}Event definitions
Section titled “Event definitions”Events should be designed to work within the specific sequence context:
using Cratis.Chronicle.Events;
[EventType]public record DecFromEventSequenceOrderCreated( string OrderNumber, string CustomerId, decimal TotalAmount);
[EventType]public record DecFromEventSequenceOrderUpdated( string OrderNumber, decimal NewTotalAmount);
[EventType]public record DecFromEventSequenceOrderShipped( string OrderNumber, DateTimeOffset ShippedAt);import io.cratis.chronicle.events.EventType
@EventTypedata class DecFromEventSequenceOrderCreated( val orderNumber: String, val customerId: String, val totalAmount: Double)
@EventTypedata class DecFromEventSequenceOrderUpdated( val orderNumber: String, val newTotalAmount: Double)
@EventTypedata class DecFromEventSequenceOrderShipped( val orderNumber: String, val shippedAt: String)import io.cratis.chronicle.events.EventType;
@EventTyperecord DecFromEventSequenceOrderCreated( String orderNumber, String customerId, double totalAmount) {}
@EventTyperecord DecFromEventSequenceOrderUpdated( String orderNumber, double newTotalAmount) {}
@EventTyperecord DecFromEventSequenceOrderShipped( String orderNumber, String shippedAt) {}defmodule MyApp.Events.DecFromEventSequenceOrderCreated do use Chronicle.Events.EventType, id: "dec-from-event-sequence-order-created"
defstruct [:order_number, :customer_id, :total_amount]end
defmodule MyApp.Events.DecFromEventSequenceOrderUpdated do use Chronicle.Events.EventType, id: "dec-from-event-sequence-order-updated"
defstruct [:order_number, :new_total_amount]end
defmodule MyApp.Events.DecFromEventSequenceOrderShipped do use Chronicle.Events.EventType, id: "dec-from-event-sequence-order-shipped"
defstruct [:order_number, :shipped_at]endimport { eventType } from '@cratis/chronicle';
@eventType()class DecFromEventSequenceOrderCreated { orderNumber = ''; customerId = ''; totalAmount = 0;}
@eventType()class DecFromEventSequenceOrderUpdated { orderNumber = ''; newTotalAmount = 0;}
@eventType()class DecFromEventSequenceOrderShipped { orderNumber = ''; shippedAt = new Date();}How it works
Section titled “How it works”When using FromEventSequence():
- The projection subscribes only to the specified event sequence
- Events from other sequences are ignored, even if they match the event types
- The projection processes events in the order they appear in the specified sequence
- Event sequence numbers and ordering are maintained within that specific sequence
Multiple event sequences
Section titled “Multiple event sequences”You can create different projections for different event sequences:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record DecFromEventSequencePackageCreated(string PackageId);
[EventType]public record DecFromEventSequencePackageShipped(string PackageId, DateTimeOffset ShippedAt);
[EventType]public record DecFromEventSequencePackageDelivered(string PackageId, DateTimeOffset DeliveredAt);
public record DecFromEventSequenceShipping( string PackageId, DateTimeOffset? ShippedAt, DateTimeOffset? DeliveredAt);
// Projection for order management eventspublic class DecFromEventSequenceMultiOrderProjection : IProjectionFor<DecFromEventSequenceOrder>{ public void Define(IProjectionBuilderFor<DecFromEventSequenceOrder> builder) => builder .FromEventSequence("order-management") .AutoMap() .From<DecFromEventSequenceOrderCreated>(_ => _ .Set(m => m.Status).ToValue(DecFromEventSequenceOrderStatus.Created));}
// Projection for shipping events from a different sequencepublic class DecFromEventSequenceShippingProjection : IProjectionFor<DecFromEventSequenceShipping>{ public void Define(IProjectionBuilderFor<DecFromEventSequenceShipping> builder) => builder .FromEventSequence("shipping-management") .AutoMap() .From<DecFromEventSequencePackageCreated>() .From<DecFromEventSequencePackageShipped>() .From<DecFromEventSequencePackageDelivered>();}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionForimport io.cratis.chronicle.projections.Projection
@EventTypedata class DecFromEventSequencePackageCreated(val packageId: String)
@EventTypedata class DecFromEventSequencePackageShipped(val packageId: String, val shippedAt: String)
@EventTypedata class DecFromEventSequencePackageDelivered(val packageId: String, val deliveredAt: String)
data class DecFromEventSequenceShipping( val packageId: String = "", val shippedAt: String? = null, val deliveredAt: String? = null)
// Projection for order management events@Projection(eventSequence = "order-management")class DecFromEventSequenceMultiOrderProjection : IProjectionFor<DecFromEventSequenceOrder> { override fun define(builder: IProjectionBuilderFor<DecFromEventSequenceOrder>) { builder .from(DecFromEventSequenceOrderCreated::class) .from(DecFromEventSequenceOrderUpdated::class) .from(DecFromEventSequenceOrderShipped::class) }}
// Projection for shipping events from a different sequence@Projection(eventSequence = "shipping-management")class DecFromEventSequenceShippingProjection : IProjectionFor<DecFromEventSequenceShipping> { override fun define(builder: IProjectionBuilderFor<DecFromEventSequenceShipping>) { builder .from(DecFromEventSequencePackageCreated::class) .from(DecFromEventSequencePackageShipped::class) .from(DecFromEventSequencePackageDelivered::class) }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;import io.cratis.chronicle.projections.Projection;
@EventTyperecord DecFromEventSequencePackageCreated(String packageId) {}
@EventTyperecord DecFromEventSequencePackageShipped(String packageId, String shippedAt) {}
@EventTyperecord DecFromEventSequencePackageDelivered(String packageId, String deliveredAt) {}
class DecFromEventSequenceShipping { public String packageId = ""; public String shippedAt = null; public String deliveredAt = null;}
// Projection for order management events@Projection(eventSequence = "order-management")class DecFromEventSequenceMultiOrderProjection implements IProjectionFor<DecFromEventSequenceOrder> { @Override public void define(IProjectionBuilderFor<DecFromEventSequenceOrder> builder) { builder.from(DecFromEventSequenceOrderCreated.class); builder.from(DecFromEventSequenceOrderUpdated.class); builder.from(DecFromEventSequenceOrderShipped.class); }}
// Projection for shipping events from a different sequence@Projection(eventSequence = "shipping-management")class DecFromEventSequenceShippingProjection implements IProjectionFor<DecFromEventSequenceShipping> { @Override public void define(IProjectionBuilderFor<DecFromEventSequenceShipping> builder) { builder.from(DecFromEventSequencePackageCreated.class); builder.from(DecFromEventSequencePackageShipped.class); builder.from(DecFromEventSequencePackageDelivered.class); }}Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecFromEventSequencePackageCreated { packageId = '';}
@eventType()class DecFromEventSequencePackageShipped { packageId = ''; shippedAt = new Date();}
@eventType()class DecFromEventSequencePackageDelivered { packageId = ''; deliveredAt = new Date();}
class DecFromEventSequenceShipping { packageId = ''; shippedAt: Date | null = null; deliveredAt: Date | null = null;}
// Projection for order management events@projection()class DecFromEventSequenceMultiOrderProjection implements IProjectionFor<DecFromEventSequenceOrder> { define(builder: IProjectionBuilderFor<DecFromEventSequenceOrder>): void { builder .fromEventSequence('order-management') .autoMap() .from(DecFromEventSequenceOrderCreated, _ => _ .set(m => m.status).toValue(DecFromEventSequenceOrderStatus.Created)); }}
// Projection for shipping events from a different sequence@projection()class DecFromEventSequenceShippingProjection implements IProjectionFor<DecFromEventSequenceShipping> { define(builder: IProjectionBuilderFor<DecFromEventSequenceShipping>): void { builder .fromEventSequence('shipping-management') .autoMap() .from(DecFromEventSequencePackageCreated) .from(DecFromEventSequencePackageShipped) .from(DecFromEventSequencePackageDelivered); }}When to use FromEventSequence
Section titled “When to use FromEventSequence”Use FromEventSequence() when:
- Bounded contexts: You have separate domains with their own event sequences
- Data partitioning: Events are logically separated by business area or tenant
- Security boundaries: Different sequences have different access requirements
- Performance optimization: You want to reduce the number of events a projection processes
- Legacy integration: You need to process events from specific legacy systems
- Multi-tenant scenarios: Each tenant has their own event sequence
Default behavior
Section titled “Default behavior”If you don’t specify FromEventSequence():
- The projection uses the default
event-logevent sequence. - All events matching the specified types are processed regardless of sequence
- This is suitable for most single-sequence scenarios
Performance considerations
Section titled “Performance considerations”- Specifying an event sequence can improve performance by reducing the number of events processed
- Each sequence maintains its own ordering and sequence numbers
- Consider the volume and frequency of events in each sequence when designing projections
- Event sequence isolation can help with parallel processing and scaling