You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
8.9 KiB
Go

package events
import (
"knowfoolery/backend/shared/types"
)
// DomainEvent represents a domain event that occurred in the system
type DomainEvent interface {
// EventID returns unique identifier for this event occurrence
EventID() string
// EventType returns the type of event
EventType() string
// EventVersion returns the version of event schema
EventVersion() string
// OccurredAt returns when the event occurred
OccurredAt() types.Timestamp
// AggregateID returns the ID of the aggregate that produced this event
AggregateID() string
// AggregateType returns the type of aggregate that produced this event
AggregateType() string
// EventData returns the event-specific data
EventData() interface{}
}
// EventDispatcher handles publishing domain events
type EventDispatcher interface {
// Publish publishes a domain event
Publish(event DomainEvent) error
// PublishBatch publishes multiple domain events
PublishBatch(events []DomainEvent) error
}
// EventHandler handles specific types of domain events
type EventHandler interface {
// Handle processes a domain event
Handle(event DomainEvent) error
// CanHandle returns true if this handler can process the event type
CanHandle(eventType string) bool
}
// EventStore provides persistence for domain events
type EventStore interface {
// Store persists a domain event
Store(event DomainEvent) error
// StoreBatch persists multiple domain events atomically
StoreBatch(events []DomainEvent) error
// GetEvents retrieves events for an aggregate
GetEvents(aggregateID string, aggregateType string, fromVersion int) ([]DomainEvent, error)
// GetEventsSince retrieves events since a specific timestamp
GetEventsSince(since types.Timestamp) ([]DomainEvent, error)
}
// Event type constants
const (
// User events
EventTypeUserRegistered = "user.registered"
EventTypeUserUpdated = "user.updated"
EventTypeUserDeleted = "user.deleted"
// Game session events
EventTypeGameSessionStarted = "game_session.started"
EventTypeGameSessionCompleted = "game_session.completed"
EventTypeGameSessionTimedOut = "game_session.timed_out"
EventTypeGameSessionAbandoned = "game_session.abandoned"
EventTypeSessionTimerWarning = "game_session.timer_warning"
// Question events
EventTypeQuestionPresented = "question.presented"
EventTypeQuestionAnswered = "question.answered"
EventTypeQuestionSkipped = "question.skipped"
EventTypeHintRequested = "hint.requested"
// Attempt events
EventTypeAttemptMade = "attempt.made"
EventTypeMaxAttemptsReached = "attempt.max_reached"
// Scoring events
EventTypeScoreUpdated = "score.updated"
EventTypeLeaderboardUpdated = "leaderboard.updated"
// Theme events
EventTypeThemeCreated = "theme.created"
EventTypeThemeUpdated = "theme.updated"
EventTypeThemeDeleted = "theme.deleted"
)
// Aggregate type constants
const (
AggregateTypeUser = "user"
AggregateTypeGameSession = "game_session"
AggregateTypeQuestion = "question"
AggregateTypeTheme = "theme"
AggregateTypeLeaderboard = "leaderboard"
)
// Base event data structures
// UserEventData contains common user event data
type UserEventData struct {
UserID types.UserID `json:"user_id"`
PlayerName string `json:"player_name"`
CreatedAt types.Timestamp `json:"created_at,omitempty"`
UpdatedAt types.Timestamp `json:"updated_at,omitempty"`
}
// GameSessionEventData contains common game session event data
type GameSessionEventData struct {
SessionID types.GameSessionID `json:"session_id"`
UserID types.UserID `json:"user_id"`
Status types.SessionStatus `json:"status"`
Score int `json:"score"`
QuestionsAsked int `json:"questions_asked"`
QuestionsCorrect int `json:"questions_correct"`
HintsUsed int `json:"hints_used"`
StartTime types.Timestamp `json:"start_time"`
EndTime *types.Timestamp `json:"end_time,omitempty"`
Duration *types.Duration `json:"duration,omitempty"`
}
// QuestionEventData contains common question event data
type QuestionEventData struct {
QuestionID types.QuestionID `json:"question_id"`
SessionID types.GameSessionID `json:"session_id"`
ThemeID types.ThemeID `json:"theme_id"`
ThemeName string `json:"theme_name"`
QuestionText string `json:"question_text"`
IsCorrect *bool `json:"is_correct,omitempty"`
PointsAwarded *int `json:"points_awarded,omitempty"`
AttemptNumber *int `json:"attempt_number,omitempty"`
HintUsed *bool `json:"hint_used,omitempty"`
PresentedAt types.Timestamp `json:"presented_at"`
AnsweredAt *types.Timestamp `json:"answered_at,omitempty"`
}
// AttemptEventData contains attempt-specific event data
type AttemptEventData struct {
AttemptID types.AttemptID `json:"attempt_id"`
SessionID types.GameSessionID `json:"session_id"`
QuestionID types.QuestionID `json:"question_id"`
AttemptNumber int `json:"attempt_number"`
PlayerAnswer string `json:"player_answer"`
CorrectAnswer string `json:"correct_answer"`
IsCorrect bool `json:"is_correct"`
PointsAwarded int `json:"points_awarded"`
HintUsed bool `json:"hint_used"`
TimeTaken types.Duration `json:"time_taken"`
AttemptedAt types.Timestamp `json:"attempted_at"`
}
// ScoreEventData contains scoring event data
type ScoreEventData struct {
SessionID types.GameSessionID `json:"session_id"`
UserID types.UserID `json:"user_id"`
PreviousScore int `json:"previous_score"`
NewScore int `json:"new_score"`
PointsAwarded int `json:"points_awarded"`
QuestionID types.QuestionID `json:"question_id"`
UpdatedAt types.Timestamp `json:"updated_at"`
}
// LeaderboardEventData contains leaderboard event data
type LeaderboardEventData struct {
SessionID types.GameSessionID `json:"session_id"`
UserID types.UserID `json:"user_id"`
PlayerName string `json:"player_name"`
FinalScore int `json:"final_score"`
QuestionsAsked int `json:"questions_asked"`
QuestionsCorrect int `json:"questions_correct"`
SuccessRate float64 `json:"success_rate"`
Duration types.Duration `json:"duration"`
Rank *int `json:"rank,omitempty"`
IsTopTen bool `json:"is_top_ten"`
CompletedAt types.Timestamp `json:"completed_at"`
}
// TimerWarningEventData contains timer warning event data
type TimerWarningEventData struct {
SessionID types.GameSessionID `json:"session_id"`
UserID types.UserID `json:"user_id"`
TimeRemaining types.Duration `json:"time_remaining"`
WarningType string `json:"warning_type"` // "5min", "1min", "10sec"
Message string `json:"message"`
WarningAt types.Timestamp `json:"warning_at"`
}
// ThemeEventData contains theme event data
type ThemeEventData struct {
ThemeID types.ThemeID `json:"theme_id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
IsActive bool `json:"is_active"`
CreatedAt types.Timestamp `json:"created_at,omitempty"`
UpdatedAt types.Timestamp `json:"updated_at,omitempty"`
}
// Repository interfaces for event sourcing
// EventSourcedRepository provides event sourcing capabilities for aggregates
type EventSourcedRepository interface {
// Save saves an aggregate and its events
Save(aggregateID string, aggregateType string, expectedVersion int, events []DomainEvent) error
// Load loads an aggregate from its events
Load(aggregateID string, aggregateType string) ([]DomainEvent, int, error)
// GetVersion gets the current version of an aggregate
GetVersion(aggregateID string, aggregateType string) (int, error)
}
// Projection represents a read model projection
type Projection interface {
// ProjectionName returns the name of this projection
ProjectionName() string
// Handle handles a domain event and updates the projection
Handle(event DomainEvent) error
// CanHandle returns true if this projection handles the event type
CanHandle(eventType string) bool
// Reset resets the projection to initial state
Reset() error
}
// ProjectionManager manages projections
type ProjectionManager interface {
// RegisterProjection registers a projection
RegisterProjection(projection Projection) error
// StartProjection starts processing events for a projection
StartProjection(projectionName string) error
// StopProjection stops processing events for a projection
StopProjection(projectionName string) error
// RebuildProjection rebuilds a projection from all events
RebuildProjection(projectionName string) error
}