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
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
|
|
} |