package aether import ( "errors" "fmt" "time" ) // ErrVersionConflict is returned when attempting to save an event with a version // that is not strictly greater than the current latest version for an actor. // This ensures events have monotonically increasing versions per actor. var ErrVersionConflict = errors.New("version conflict") // VersionConflictError provides details about a version conflict. // It is returned when SaveEvent is called with a version <= the current latest version. type VersionConflictError struct { ActorID string AttemptedVersion int64 CurrentVersion int64 } func (e *VersionConflictError) Error() string { return fmt.Sprintf("%s: actor %q has version %d, cannot save version %d", ErrVersionConflict, e.ActorID, e.CurrentVersion, e.AttemptedVersion) } func (e *VersionConflictError) Unwrap() error { return ErrVersionConflict } // Event represents a domain event in the system type Event struct { ID string `json:"id"` EventType string `json:"eventType"` ActorID string `json:"actorId"` CommandID string `json:"commandId,omitempty"` // Correlation ID for command that triggered this event Version int64 `json:"version"` Data map[string]interface{} `json:"data"` Timestamp time.Time `json:"timestamp"` } // ActorSnapshot represents a point-in-time state snapshot type ActorSnapshot struct { ActorID string `json:"actorId"` Version int64 `json:"version"` State map[string]interface{} `json:"state"` Timestamp time.Time `json:"timestamp"` } // EventStore defines the interface for event persistence. // // # Version Semantics // // Events for an actor must have monotonically increasing versions. When SaveEvent // is called, the implementation must validate that the event's version is strictly // greater than the current latest version for that actor. If the version is less // than or equal to the current version, SaveEvent must return a VersionConflictError // (which wraps ErrVersionConflict). // // This validation ensures event stream integrity and enables optimistic concurrency // control. Clients should: // 1. Call GetLatestVersion to get the current version for an actor // 2. Set the new event's version to currentVersion + 1 // 3. Call SaveEvent - if it returns ErrVersionConflict, another writer won // 4. On conflict, reload the latest version and retry if appropriate // // For new actors (no existing events), version 1 is expected for the first event. type EventStore interface { // SaveEvent persists an event to the store. The event's Version must be // strictly greater than the current latest version for the actor. // Returns VersionConflictError if version <= current latest version. SaveEvent(event *Event) error // GetEvents retrieves events for an actor from a specific version (inclusive). // Returns an empty slice if no events exist for the actor. GetEvents(actorID string, fromVersion int64) ([]*Event, error) // GetLatestVersion returns the latest version for an actor. // Returns 0 if no events exist for the actor. GetLatestVersion(actorID string) (int64, error) } // SnapshotStore extends EventStore with snapshot capabilities type SnapshotStore interface { EventStore GetLatestSnapshot(actorID string) (*ActorSnapshot, error) SaveSnapshot(snapshot *ActorSnapshot) error }