Projection with NotRewindable
The NotRewindable() method marks a projection as forward-only, meaning it cannot be replayed or rewound. This is useful for projections that maintain state that should not be recreated from scratch or for performance-critical scenarios where replay is not needed.
Defining a non-rewindable projection
Section titled “Defining a non-rewindable projection”Use NotRewindable() to mark a projection as forward-only:
using Cratis.Chronicle.Projections;
public class DecNotRewindableAuditLogProjection : IProjectionFor<DecNotRewindableAuditLogEntry>{ public void Define(IProjectionBuilderFor<DecNotRewindableAuditLogEntry> builder) => builder .NotRewindable() .AutoMap() .FromEvery(_ => _ .Set(m => m.ProcessedAt).ToEventContextProperty(c => c.Occurred)) .From<DecNotRewindableUserAction>(_ => _ .Set(m => m.OccurredAt).ToEventContextProperty(c => c.Occurred));}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@projection()class DecNotRewindableAuditLogProjection implements IProjectionFor<DecNotRewindableAuditLogEntry> { define(builder: IProjectionBuilderFor<DecNotRewindableAuditLogEntry>): void { builder .notRewindable() .autoMap() .fromEvery(_ => _ .set(m => m.processedAt).toEventContextProperty('occurred')) .from(DecNotRewindableUserAction, _ => _ .set(m => m.occurredAt).toEventContextProperty('occurred')); }}This projection:
- Cannot be replayed or reset
- Processes events only as they arrive in real-time
- Maintains forward-only state progression
- Is optimized for performance and append-only scenarios
Read model definition
Section titled “Read model definition”The read model can include timestamp and audit information:
public record DecNotRewindableAuditLogEntry( string UserId, string Action, string Details, DateTimeOffset OccurredAt, DateTimeOffset ProcessedAt, long SequenceNumber);Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.class DecNotRewindableAuditLogEntry { userId = ''; action = ''; details = ''; occurredAt = new Date(); processedAt = new Date(); sequenceNumber: bigint = 0n;}Event definitions
Section titled “Event definitions”Events should be designed for forward-only processing:
using Cratis.Chronicle.Events;
[EventType]public record DecNotRewindableUserAction( string UserId, string ActionType, string Details);
[EventType]public record DecNotRewindableSystemEvent( string ComponentName, string EventType, string Data);Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { eventType } from '@cratis/chronicle';
@eventType()class DecNotRewindableUserAction { userId = ''; actionType = ''; details = '';}
@eventType()class DecNotRewindableSystemEvent { componentName = ''; eventType = ''; data = '';}How it works
Section titled “How it works”When a projection is marked as NotRewindable():
- No replay capability: The projection cannot be reset and rebuilt from the beginning
- Forward-only processing: Events are processed only as they arrive in chronological order
- Performance optimization: Chronicle skips replay infrastructure and optimizations
- State preservation: Existing projection state is preserved and cannot be recreated
- Error handling: Failed events may require manual intervention since replay isn’t available
Use cases for non-rewindable projections
Section titled “Use cases for non-rewindable projections”Audit and logging projections
Section titled “Audit and logging projections”Perfect for audit logs where you want to preserve the exact timing and sequence:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record DecNotRewindableUserLoginAttempt(string UserId, bool Succeeded);
[EventType]public record DecNotRewindablePermissionChange(string UserId, string Permission);
public record DecNotRewindableSecurityAuditEntry( DateTimeOffset AuditedAt, long SequenceNumber);
public class DecNotRewindableSecurityAuditProjection : IProjectionFor<DecNotRewindableSecurityAuditEntry>{ public void Define(IProjectionBuilderFor<DecNotRewindableSecurityAuditEntry> builder) => builder .NotRewindable() .AutoMap() .FromEvery(_ => _ .Set(m => m.AuditedAt).ToEventContextProperty(c => c.Occurred) .Set(m => m.SequenceNumber).ToEventContextProperty(c => c.SequenceNumber)) .From<DecNotRewindableUserLoginAttempt>() .From<DecNotRewindablePermissionChange>();}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecNotRewindableUserLoginAttempt { userId = ''; succeeded = false;}
@eventType()class DecNotRewindablePermissionChange { userId = ''; permission = '';}
class DecNotRewindableSecurityAuditEntry { auditedAt = new Date(); sequenceNumber: bigint = 0n;}
@projection()class DecNotRewindableSecurityAuditProjection implements IProjectionFor<DecNotRewindableSecurityAuditEntry> { define(builder: IProjectionBuilderFor<DecNotRewindableSecurityAuditEntry>): void { builder .notRewindable() .autoMap() .fromEvery(_ => _ .set(m => m.auditedAt).toEventContextProperty('occurred') .set(m => m.sequenceNumber).toEventContextProperty('sequenceNumber')) .from(DecNotRewindableUserLoginAttempt) .from(DecNotRewindablePermissionChange); }}Performance monitoring
Section titled “Performance monitoring”For real-time performance metrics that don’t need historical accuracy:
[EventType]public record DecNotRewindableApiRequestCompleted(string Endpoint, int StatusCode, long DurationMilliseconds);
public record DecNotRewindablePerformanceMetric(DateTimeOffset Timestamp);
public class DecNotRewindablePerformanceMetricProjection : IProjectionFor<DecNotRewindablePerformanceMetric>{ public void Define(IProjectionBuilderFor<DecNotRewindablePerformanceMetric> builder) => builder .NotRewindable() .AutoMap() .From<DecNotRewindableApiRequestCompleted>(_ => _ .Set(m => m.Timestamp).ToEventContextProperty(c => c.Occurred));}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecNotRewindableApiRequestCompleted { endpoint = ''; statusCode = 0; durationMilliseconds: bigint = 0n;}
class DecNotRewindablePerformanceMetric { timestamp = new Date();}
@projection()class DecNotRewindablePerformanceMetricProjection implements IProjectionFor<DecNotRewindablePerformanceMetric> { define(builder: IProjectionBuilderFor<DecNotRewindablePerformanceMetric>): void { builder .notRewindable() .autoMap() .from(DecNotRewindableApiRequestCompleted, _ => _ .set(m => m.timestamp).toEventContextProperty('occurred')); }}Financial transactions
Section titled “Financial transactions”For append-only financial records where replay could cause confusion:
[EventType]public record DecNotRewindablePaymentProcessed(string PaymentId, decimal Amount);
public record DecNotRewindableLedgerEntry( DateTimeOffset RecordedAt, string TransactionType);
public class DecNotRewindableTransactionLedgerProjection : IProjectionFor<DecNotRewindableLedgerEntry>{ public void Define(IProjectionBuilderFor<DecNotRewindableLedgerEntry> builder) => builder .NotRewindable() .AutoMap() .FromEvery(_ => _ .Set(m => m.RecordedAt).ToEventContextProperty(c => c.Occurred)) .From<DecNotRewindablePaymentProcessed>(_ => _ .Set(m => m.TransactionType).ToValue("PAYMENT"));}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecNotRewindablePaymentProcessed { paymentId = ''; amount = 0;}
class DecNotRewindableLedgerEntry { recordedAt = new Date(); transactionType = '';}
@projection()class DecNotRewindableTransactionLedgerProjection implements IProjectionFor<DecNotRewindableLedgerEntry> { define(builder: IProjectionBuilderFor<DecNotRewindableLedgerEntry>): void { builder .notRewindable() .autoMap() .fromEvery(_ => _ .set(m => m.recordedAt).toEventContextProperty('occurred')) .from(DecNotRewindablePaymentProcessed, _ => _ .set(m => m.transactionType).toValue('PAYMENT')); }}When to use NotRewindable
Section titled “When to use NotRewindable”Use NotRewindable() when:
- Audit requirements: You need to preserve exact event timestamps and sequence
- Performance critical: The projection handles high-volume events and replay would be expensive
- Append-only data: The projection represents data that should never be regenerated
- Real-time processing: Only forward, real-time event flow matters — a rebuilt-from-replay history would not reflect current state
- External integrations: The projection triggers external actions that shouldn’t be repeated
- Compliance: Regulatory requirements prevent data recreation or replay
When NOT to use NotRewindable
Section titled “When NOT to use NotRewindable”Avoid NotRewindable() when:
- Business logic changes: You might need to replay events with updated logic
- Bug fixes: Errors in projection logic need to be corrected by replay
- Data migration: You need to rebuild projections with new schemas
- Testing: Development and testing scenarios benefit from replay capability
- Recovery: System failures might require rebuilding projection state
Combining with other features
Section titled “Combining with other features”NotRewindable can be combined with other projection features:
using Cratis.Chronicle.Projections;
[EventType]public record DecNotRewindableOrderReceived(string OrderId);
[EventType]public record DecNotRewindableOrderProcessing(string OrderId);
[EventType]public record DecNotRewindableOrderCompleted(string OrderId);
public record DecNotRewindableOrderStatus( string Status, DateTimeOffset LastUpdatedAt);
public class DecNotRewindableRealTimeOrderStatusProjection : IProjectionFor<DecNotRewindableOrderStatus>{ public void Define(IProjectionBuilderFor<DecNotRewindableOrderStatus> builder) => builder .NotRewindable() .FromEventSequence("order-processing") .Passive() .AutoMap() .FromEvery(_ => _ .Set(m => m.LastUpdatedAt).ToEventContextProperty(c => c.Occurred)) .From<DecNotRewindableOrderReceived>(_ => _ .Set(m => m.Status).ToValue("RECEIVED")) .From<DecNotRewindableOrderProcessing>(_ => _ .Set(m => m.Status).ToValue("PROCESSING")) .From<DecNotRewindableOrderCompleted>(_ => _ .Set(m => m.Status).ToValue("COMPLETED"));}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecNotRewindableOrderReceived { orderId = '';}
@eventType()class DecNotRewindableOrderProcessing { orderId = '';}
@eventType()class DecNotRewindableOrderCompleted { orderId = '';}
class DecNotRewindableOrderStatus { status = ''; lastUpdatedAt = new Date();}
@projection()class DecNotRewindableRealTimeOrderStatusProjection implements IProjectionFor<DecNotRewindableOrderStatus> { define(builder: IProjectionBuilderFor<DecNotRewindableOrderStatus>): void { builder .notRewindable() .fromEventSequence('order-processing') .passive() .autoMap() .fromEvery(_ => _ .set(m => m.lastUpdatedAt).toEventContextProperty('occurred')) .from(DecNotRewindableOrderReceived, _ => _ .set(m => m.status).toValue('RECEIVED')) .from(DecNotRewindableOrderProcessing, _ => _ .set(m => m.status).toValue('PROCESSING')) .from(DecNotRewindableOrderCompleted, _ => _ .set(m => m.status).toValue('COMPLETED')); }}This combines non-rewindable behavior with event sequence specification, passive mode, and event-context timestamp mapping.
Error handling considerations
Section titled “Error handling considerations”With non-rewindable projections:
- Failed events: Must be handled carefully since replay isn’t available
- Dead letter queues: Consider implementing for failed event processing
- Manual intervention: May be required to fix projection state
- Monitoring: Implement comprehensive monitoring and alerting
- Backup strategies: Consider point-in-time snapshots for recovery
Performance benefits
Section titled “Performance benefits”Non-rewindable projections offer several performance advantages:
- Reduced memory usage: No need to store replay state or checkpoints
- Faster startup: No replay phase during application startup
- Lower CPU usage: Eliminates replay processing overhead
- Simplified infrastructure: Fewer moving parts in the projection system
- Better throughput: All processing power focused on real-time events