170 lines
6.2 KiB
Go
170 lines
6.2 KiB
Go
// Package ports defines the stable interfaces that connect Game Lobby
|
|
// Service use cases to external state and external services.
|
|
package ports
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"galaxy/lobby/internal/domain/common"
|
|
"galaxy/lobby/internal/domain/game"
|
|
)
|
|
|
|
// GameStore stores game records and their secondary indexes. Adapters are
|
|
// responsible for maintaining the status index together with the record.
|
|
type GameStore interface {
|
|
// Save upserts record. It is used for draft creation and for
|
|
// field-only edits. The adapter must rewrite the status secondary
|
|
// index when the status field changes. Save does not apply the
|
|
// domain transition gate; callers that intend a status transition
|
|
// must use UpdateStatus instead.
|
|
Save(ctx context.Context, record game.Game) error
|
|
|
|
// Get returns the record identified by gameID. It returns
|
|
// game.ErrNotFound when no record exists.
|
|
Get(ctx context.Context, gameID common.GameID) (game.Game, error)
|
|
|
|
// GetByStatus returns every record currently indexed under status.
|
|
// The slice is ordered by the created-at score ascending; callers
|
|
// may reorder as needed.
|
|
GetByStatus(ctx context.Context, status game.Status) ([]game.Game, error)
|
|
|
|
// CountByStatus returns the number of game records indexed under
|
|
// each known status. The map carries one entry per game.Status from
|
|
// game.AllStatuses, with zero counts for empty buckets. Telemetry
|
|
// uses the result to emit the `lobby.active_games` observable gauge
|
|
// without scanning record payloads.
|
|
CountByStatus(ctx context.Context) (map[game.Status]int, error)
|
|
|
|
// GetByOwner returns every record whose OwnerUserID equals userID.
|
|
// The order is adapter-defined; callers may reorder as needed. The
|
|
// secondary index is maintained alongside the per-status index;
|
|
// cascade-release callers consume it without touching the
|
|
// status listings.
|
|
GetByOwner(ctx context.Context, userID string) ([]game.Game, error)
|
|
|
|
// UpdateStatus applies one status transition in a compare-and-swap
|
|
// fashion. The adapter must first call game.Transition to reject
|
|
// invalid triplets without touching the store; on success it must
|
|
// verify that the current status equals input.ExpectedFrom, update
|
|
// the primary record, and rewrite the status secondary index.
|
|
// Adapters set StartedAt when transitioning to running and
|
|
// FinishedAt when transitioning to finished.
|
|
UpdateStatus(ctx context.Context, input UpdateStatusInput) error
|
|
|
|
// UpdateRuntimeSnapshot overwrites the denormalized runtime snapshot
|
|
// fields on the record identified by input.GameID. It does not
|
|
// mutate the status field or the status secondary index.
|
|
UpdateRuntimeSnapshot(ctx context.Context, input UpdateRuntimeSnapshotInput) error
|
|
|
|
// UpdateRuntimeBinding overwrites the runtime binding metadata on the
|
|
// record identified by input.GameID. It does not mutate the status
|
|
// field or the status secondary index. The binding must satisfy the
|
|
// domain invariants (see game.RuntimeBinding.Validate). The adapter
|
|
// returns game.ErrNotFound when no record exists.
|
|
UpdateRuntimeBinding(ctx context.Context, input UpdateRuntimeBindingInput) error
|
|
}
|
|
|
|
// UpdateStatusInput stores the arguments required to apply one status
|
|
// transition through a GameStore.
|
|
type UpdateStatusInput struct {
|
|
// GameID identifies the record to mutate.
|
|
GameID common.GameID
|
|
|
|
// ExpectedFrom stores the status the caller believes the record
|
|
// currently has. A mismatch results in game.ErrConflict.
|
|
ExpectedFrom game.Status
|
|
|
|
// To stores the destination status.
|
|
To game.Status
|
|
|
|
// Trigger stores the transition trigger used by the domain gate.
|
|
Trigger game.Trigger
|
|
|
|
// At stores the wall-clock used for UpdatedAt, and for StartedAt or
|
|
// FinishedAt when the destination status requires it.
|
|
At time.Time
|
|
}
|
|
|
|
// Validate reports whether input contains a structurally valid status
|
|
// transition request.
|
|
func (input UpdateStatusInput) Validate() error {
|
|
if err := input.GameID.Validate(); err != nil {
|
|
return fmt.Errorf("update status: game id: %w", err)
|
|
}
|
|
if !input.ExpectedFrom.IsKnown() {
|
|
return fmt.Errorf("update status: expected from status %q is unsupported", input.ExpectedFrom)
|
|
}
|
|
if !input.To.IsKnown() {
|
|
return fmt.Errorf("update status: to status %q is unsupported", input.To)
|
|
}
|
|
if !input.Trigger.IsKnown() {
|
|
return fmt.Errorf("update status: trigger %q is unsupported", input.Trigger)
|
|
}
|
|
if input.At.IsZero() {
|
|
return fmt.Errorf("update status: at must not be zero")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateRuntimeSnapshotInput stores the arguments required to update the
|
|
// denormalized runtime snapshot on one game record.
|
|
type UpdateRuntimeSnapshotInput struct {
|
|
// GameID identifies the record to mutate.
|
|
GameID common.GameID
|
|
|
|
// Snapshot stores the new snapshot values to persist.
|
|
Snapshot game.RuntimeSnapshot
|
|
|
|
// At stores the wall-clock used for UpdatedAt.
|
|
At time.Time
|
|
}
|
|
|
|
// Validate reports whether input contains a structurally valid runtime
|
|
// snapshot update request.
|
|
func (input UpdateRuntimeSnapshotInput) Validate() error {
|
|
if err := input.GameID.Validate(); err != nil {
|
|
return fmt.Errorf("update runtime snapshot: game id: %w", err)
|
|
}
|
|
if input.Snapshot.CurrentTurn < 0 {
|
|
return fmt.Errorf("update runtime snapshot: current turn must not be negative")
|
|
}
|
|
if input.At.IsZero() {
|
|
return fmt.Errorf("update runtime snapshot: at must not be zero")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateRuntimeBindingInput stores the arguments required to persist
|
|
// runtime binding metadata on one game record.
|
|
type UpdateRuntimeBindingInput struct {
|
|
// GameID identifies the record to mutate.
|
|
GameID common.GameID
|
|
|
|
// Binding stores the runtime binding values to persist. The adapter
|
|
// validates the binding before writing.
|
|
Binding game.RuntimeBinding
|
|
|
|
// At stores the wall-clock used for UpdatedAt.
|
|
At time.Time
|
|
}
|
|
|
|
// Validate reports whether input contains a structurally valid runtime
|
|
// binding update request.
|
|
func (input UpdateRuntimeBindingInput) Validate() error {
|
|
if err := input.GameID.Validate(); err != nil {
|
|
return fmt.Errorf("update runtime binding: game id: %w", err)
|
|
}
|
|
if err := input.Binding.Validate(); err != nil {
|
|
return fmt.Errorf("update runtime binding: %w", err)
|
|
}
|
|
if input.At.IsZero() {
|
|
return fmt.Errorf("update runtime binding: at must not be zero")
|
|
}
|
|
|
|
return nil
|
|
}
|