Documentation
¶
Index ¶
- Constants
- Variables
- func EventTypeFor(entityType, action string) string
- func IsStandardEventType(eventType string) bool
- func MarshalEventToJSON[T any](envelope EventEnvelope[T]) ([]byte, error)
- func RegisterType[T any](d *EventDispatcher, eventType string, factory func() T) error
- func Subscribe[T any](d *EventDispatcher, eventType string, handler EventHandler[T]) error
- type BasicTripleEvent
- type Entity
- type Event
- type EventDispatcher
- func (d *EventDispatcher) Dispatch(ctx context.Context, envelope EventEnvelope[any]) error
- func (d *EventDispatcher) SubscribeWildcard(handler func(context.Context, EventEnvelope[any]) error) error
- func (d *EventDispatcher) UnmarshalEvent(ctx context.Context, data []byte, eventType string) (EventEnvelope[any], error)
- type EventEnvelope
- func NewEventEnvelope[T any](payload T, aggregateID, eventType string, sequenceNo int) EventEnvelope[T]
- func ToAnyEnvelope[T any](envelope EventEnvelope[T]) EventEnvelope[any]
- func UnmarshalEventFromJSON[T any](data []byte) (EventEnvelope[T], error)
- func WrapEvent[T any](payload T, aggregateID, eventType string, sequenceNo int) (EventEnvelope[T], error)
- type EventHandler
- type EventStore
Constants ¶
const ( // EventTypeCreate represents an entity creation event. EventTypeCreate = "created" // EventTypeUpdate represents an entity update event. EventTypeUpdate = "updated" // EventTypeDelete represents an entity deletion event. EventTypeDelete = "deleted" // EventTypeTriple represents an RDF-style relationship event (subject-predicate-object). EventTypeTriple = "triple" )
Standard event type constants for common domain operations.
Variables ¶
var ( // ErrEventNotFound is returned when an event is not found in the store. ErrEventNotFound = errors.New("event not found") // ErrConcurrencyConflict is returned when there's a version conflict during event persistence. ErrConcurrencyConflict = errors.New("concurrency conflict: expected version mismatch") // ErrInvalidEvent is returned when an event is invalid or malformed. ErrInvalidEvent = errors.New("invalid event") // ErrGlobalOrderingNotSupported is returned by ReadAfter on stores that // cannot provide a global, cross-aggregate ordering of events. ErrGlobalOrderingNotSupported = errors.New("event store does not support a global ordered feed") )
Functions ¶
func EventTypeFor ¶
EventTypeFor constructs an event type string from an entity type and action. For example, EventTypeFor("user", EventTypeCreate) returns "user.created".
func IsStandardEventType ¶
IsStandardEventType checks if the given event type is one of the standard types.
func MarshalEventToJSON ¶
func MarshalEventToJSON[T any](envelope EventEnvelope[T]) ([]byte, error)
MarshalEventToJSON marshals a generic EventEnvelope to JSON.
func RegisterType ¶
func RegisterType[T any](d *EventDispatcher, eventType string, factory func() T) error
RegisterType registers a type factory for an event type to enable type-safe deserialization. This is separate from handler registration and is used when unmarshaling events from storage.
func Subscribe ¶
func Subscribe[T any](d *EventDispatcher, eventType string, handler EventHandler[T]) error
Subscribe registers a typed event handler for a specific event type. The handler will be called when events of the specified type are dispatched. Multiple handlers can be registered for the same event type. This is a generic function (not a method) because Go doesn't support generic methods on non-generic types.
Types ¶
type BasicTripleEvent ¶
type BasicTripleEvent struct {
Subject string `json:"subject"`
Predicate string `json:"predicate"`
Object string `json:"object"`
Original int64 `json:"original"`
}
BasicTripleEvent is a standard triple event payload that other strongly typed triple events can use in composition. It represents a subject-predicate-object relationship with an original identifier.
type Entity ¶
type Entity interface {
// GetID returns the aggregate ID of the entity.
GetID() string
// GetSequenceNo returns the last event sequence number from when the entity was hydrated.
GetSequenceNo() int
// GetUncommittedEvents returns all uncommitted events that have been recorded but not yet persisted.
GetUncommittedEvents() []EventEnvelope[any]
// ClearUncommittedEvents removes all uncommitted events, typically called after successful persistence.
ClearUncommittedEvents()
}
Entity is an interface for entities that can be tracked by UnitOfWork. It provides methods to access entity state and uncommitted events.
type Event ¶
Event is an optional interface that events can implement for convenience. It provides a way to extract the aggregate ID from an event.
type EventDispatcher ¶
type EventDispatcher struct {
// contains filtered or unexported fields
}
EventDispatcher is responsible for registering event handlers and dispatching events to them. It acts as both a handler registry and event dispatcher.
func NewEventDispatcher ¶
func NewEventDispatcher() *EventDispatcher
NewEventDispatcher creates a new EventDispatcher instance.
func (*EventDispatcher) Dispatch ¶
func (d *EventDispatcher) Dispatch(ctx context.Context, envelope EventEnvelope[any]) error
Dispatch dispatches an event to all registered handlers for the event type and matching patterns. Handlers are executed in parallel using goroutines. If any handler returns an error, it is collected and returned after all handlers complete. Pattern matching: "user.created" triggers handlers for "user.created", "user.*", "*.created", and "*.*"
func (*EventDispatcher) SubscribeWildcard ¶
func (d *EventDispatcher) SubscribeWildcard(handler func(context.Context, EventEnvelope[any]) error) error
SubscribeWildcard registers a catch-all handler that will be called for all event types. Wildcard handlers are executed in parallel with pattern-matched handlers.
func (*EventDispatcher) UnmarshalEvent ¶
func (d *EventDispatcher) UnmarshalEvent(ctx context.Context, data []byte, eventType string) (EventEnvelope[any], error)
UnmarshalEvent unmarshals an event from JSON using the registered type factory. It looks up the type factory from the registry and reconstructs EventEnvelope[any] with the typed payload.
type EventEnvelope ¶
type EventEnvelope[T any] struct { ID string `json:"id"` AggregateID string `json:"aggregate_id"` EventType string `json:"event_type"` Payload T `json:"payload"` Created time.Time `json:"timestamp"` SequenceNo int `json:"sequence_no"` TransactionID string `json:"transaction_id,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` // Position is the event's place in the store's global, cross-aggregate // commit order. It is assigned by the event store at persistence time and // populated on reads; it is zero on envelopes that have not been persisted // (or that were stored before global ordering existed and have not been // backfilled). Positions are strictly increasing but not necessarily dense. Position int64 `json:"position,omitempty"` }
EventEnvelope is a generic struct that wraps event payloads with metadata for transport and persistence. The type parameter T represents the strongly-typed event payload.
func NewEventEnvelope ¶
func NewEventEnvelope[T any](payload T, aggregateID, eventType string, sequenceNo int) EventEnvelope[T]
NewEventEnvelope creates a new EventEnvelope with the given payload and metadata. If the payload implements the Event interface, the AggregateID is extracted from it. Otherwise, the provided aggregateID parameter is used. sequenceNo is the sequence number for this event within the aggregate's event stream.
func ToAnyEnvelope ¶
func ToAnyEnvelope[T any](envelope EventEnvelope[T]) EventEnvelope[any]
ToAnyEnvelope converts an EventEnvelope[T] to EventEnvelope[any] for storage. This allows storing events with different payload types together in the event store.
func UnmarshalEventFromJSON ¶
func UnmarshalEventFromJSON[T any](data []byte) (EventEnvelope[T], error)
UnmarshalEventFromJSON unmarshals an EventEnvelope from JSON. The type parameter T must match the payload type in the JSON data.
func WrapEvent ¶
func WrapEvent[T any](payload T, aggregateID, eventType string, sequenceNo int) (EventEnvelope[T], error)
WrapEvent wraps a typed payload in a generic EventEnvelope. If the payload implements the Event interface, the AggregateID is extracted from it. Otherwise, the provided aggregateID parameter is used. sequenceNo is the sequence number for this event within the aggregate's event stream.
func (*EventEnvelope[T]) MarshalJSON ¶
func (e *EventEnvelope[T]) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for EventEnvelope. This custom implementation ensures the generic type is properly serialized.
func (*EventEnvelope[T]) UnmarshalJSON ¶
func (e *EventEnvelope[T]) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for EventEnvelope. This custom implementation ensures the generic type is properly deserialized.
type EventHandler ¶
type EventHandler[T any] func(ctx context.Context, env EventEnvelope[T]) error
EventHandler is a type-safe handler function for processing events. The type parameter T represents the strongly-typed event payload.
type EventStore ¶
type EventStore interface {
// Append appends one or more events to the event store for a given aggregate.
// It returns an error if the expected version doesn't match (optimistic concurrency control).
// If expectedVersion is -1, no version check is performed.
Append(ctx context.Context, aggregateID string, expectedVersion int, events ...EventEnvelope[any]) error
// GetEvents retrieves all events for a given aggregate ID.
// Returns an empty slice if no events are found.
GetEvents(ctx context.Context, aggregateID string) ([]EventEnvelope[any], error)
// GetEventsFromVersion retrieves events for an aggregate starting from a specific version.
// Returns an empty slice if no events are found.
GetEventsFromVersion(ctx context.Context, aggregateID string, fromVersion int) ([]EventEnvelope[any], error)
// GetEventsRange retrieves events for an aggregate within a version range.
// If fromVersion is -1, it defaults to version 1 (the first event).
// If toVersion is -1, it retrieves all events from fromVersion to the end.
// Returns an empty slice if no events are found in the range.
GetEventsRange(ctx context.Context, aggregateID string, fromVersion, toVersion int) ([]EventEnvelope[any], error)
// GetEventByID retrieves a specific event by its ID.
// Returns ErrEventNotFound if the event doesn't exist.
GetEventByID(ctx context.Context, eventID string) (EventEnvelope[any], error)
// GetEventsByTransactionID retrieves all events that share the given transaction ID,
// ordered by aggregate ID then sequence number.
// transactionID must not be empty; implementations return ErrInvalidEvent if it is.
// Returns an empty slice if no events are found.
GetEventsByTransactionID(ctx context.Context, transactionID string) ([]EventEnvelope[any], error)
// GetCurrentVersion returns the current version number for an aggregate.
// Returns 0 if the aggregate doesn't exist.
GetCurrentVersion(ctx context.Context, aggregateID string) (int, error)
// ReadAfter returns committed events across all aggregates whose global
// Position is greater than afterPosition, ordered by Position ascending.
// At most limit events are returned; limit <= 0 means no limit.
//
// The result is safe to use as a resumable feed: once an event is
// returned, no event with a smaller Position will ever appear in a later
// call. Implementations backed by databases with concurrent writers must
// withhold events whose positions are visible before an earlier-position
// transaction has committed. The cost of that guarantee is liveness, not
// correctness: a long-running write transaction anywhere in the database
// delays the feed (an empty result can mean "caught up" or "withheld
// behind an in-flight writer").
//
// Stores without a global ordering return ErrGlobalOrderingNotSupported.
ReadAfter(ctx context.Context, afterPosition int64, limit int) ([]EventEnvelope[any], error)
// HeadPosition returns the highest Position that ReadAfter could
// currently deliver (0 when the store is empty). Feed consumers use it to
// measure lag against their checkpoint.
//
// Stores without a global ordering return ErrGlobalOrderingNotSupported.
HeadPosition(ctx context.Context) (int64, error)
// Close closes the event store and releases any resources.
Close() error
}
EventStore defines the interface for persisting and retrieving events. Implementations should be thread-safe and handle concurrent access. Events are stored as EventEnvelope[any] to allow storing events with different payload types together.