22 KiB
User Service Implementation Plan
This plan has been already implemented and stays here for historical reasons.
It should NOT be threated as source of truth for service functionality.
Planning Principles
This plan is aligned with the current repository architecture and is written for an experienced middle-level Go developer implementing an internal trusted microservice.
Execution priorities:
- preserve the frozen auth and geo ownership boundaries
- keep user state authoritative in one service
- prefer explicit command behavior over generic patch behavior
- keep synchronous read paths simple for auth and lobby
- separate current effective state from append-only or historical records where fast reads matter
- keep the first version storage-agnostic at the domain boundary even if Redis is the initial backend
Stage 01 — Freeze Vocabulary, Contracts, and Cross-Service Ownership
Status: implemented.
Goal
Remove naming ambiguity and freeze the service boundary before implementation.
Tasks
- Freeze the regular-user-only scope of
User Service. - Freeze that system-admin identity is out of scope and belongs to later
Admin Service. - Freeze the self-service vocabulary:
GetMyAccountUpdateMyProfileUpdateMySettings
- Freeze that
race_namereplacesdisplay_name. - Freeze the current ownership split for
declared_country:- current value in
User Service - workflow and history in
Geo Profile Service
- current value in
- Freeze the auth-facing internal REST endpoints already reserved by
Auth / Session Service. - Freeze the exact create-only registration context shape on
EnsureUserByEmail:preferred_languagetime_zone
Deliverables
- service README with stable terminology
- short internal ADR or equivalent note for
declared_countryownership split - short internal ADR or equivalent note for regular-user versus admin identity split
Exit Criteria
- no unresolved naming conflict remains around
race_name,declared_country, entitlement, sanction, or limit semantics - no service boundary question remains open for auth, lobby, or geo
Targeted Tests
- none yet beyond documentation review
Stage 02 — Define Domain Entities and Redis-Backed Logical State
Status: implemented.
Goal
Describe the persistent state clearly enough that storage adapters can be built without revisiting core semantics.
Tasks
- Define logical entities for:
- user account
- race-name reservation
- blocked e-mail subject
- entitlement period record
- current entitlement snapshot
- sanction record
- limit record
- Freeze required fields, timestamps, and identifiers for each entity.
- Decide Redis logical key layout and lookup indexes without leaking them into the domain layer.
- Freeze deterministic pagination keys for admin listing.
- Define how active/effective evaluation works for sanctions and limits.
Deliverables
- domain entity definitions
- storage design notes for Redis keys and secondary indexes
- active/effective evaluation rules
Exit Criteria
- every required read and mutation can map to a clear logical entity set
- Redis adapters can be implemented directly from the frozen logical model
Targeted Tests
- domain validation tests for required fields
- tests for effective-state evaluation of active versus expired records
Stage 03 — Implement Auth-Facing Resolution, Ensure, Existence, and E-Mail Blocking
Status: implemented.
Goal
Provide the minimum trusted API needed by Auth / Session Service.
Tasks
- Implement:
- resolve by e-mail
- ensure by e-mail
- exists by user id
- block by user id
- block by e-mail
- Preserve exact route shapes already reserved by the auth REST client.
- Implement the separate blocked-email-subject model.
- Make
BlockByEmailidempotent for both existing-user and no-user cases. - Ensure
ResolveByEmailandEnsureUserByEmailboth respect blocked-email subjects.
Deliverables
- trusted internal REST handlers for auth-facing endpoints
- domain services for resolution and block behavior
- Redis-backed storage for user existence and blocked-email subjects
- runnable
cmd/userserviceprocess usingGinandgo-redis/v9 - durable create path that already materializes:
- opaque
user_id - generated
player-<shortid>race name - stored
preferred_languageandtime_zone - initial free entitlement snapshot
- opaque
Exit Criteria
- auth can distinguish
existing,creatable, andblocked - blocked e-mail subjects prevent user creation before a user exists
BlockByUserIDandBlockByEmailare idempotent
Targeted Tests
- resolve existing/creatable/blocked by e-mail
- ensure existing/created/blocked outcomes
- blocked e-mail subject prevents creation before user record exists
- block by user id on unknown user returns not found
- repeated block calls stay idempotent
Stage 04 — Implement New-User Creation Context from Auth
Status: implemented.
Goal
Tighten the already-implemented first-login create path with stricter semantic validation.
Tasks
- Preserve the already-frozen create-only
EnsureUserByEmailregistration context with:preferred_languagetime_zone
- Tighten
preferred_languagevalidation to BCP 47 semantics. - Tighten
time_zonevalidation to IANA TZ semantics. - Preserve generated initial
race_nameinplayer-<shortid>form during creation. - Preserve the newly created user initialization with:
- free entitlement
- no active sanctions
- no custom limits
- Ignore registration context for existing users.
- Document required follow-up changes in
gatewayandauthsession.
Deliverables
- create-user domain service using the frozen ensure-by-email request model
- generated-race-name helper
- create-path validation for
preferred_languageandtime_zone
Exit Criteria
- first successful ensure-create path can fully initialize a new user
- existing-user ensure does not overwrite language or time zone
Targeted Tests
- new user created with generated
race_name, derivedpreferred_language, and required clienttime_zone - existing user ensure ignores create-only registration context
- invalid BCP 47 or IANA inputs are rejected on create path
Stage 05 — Implement Self-Service Account Read and Split Profile/Settings Mutations
Status: implemented.
Goal
Expose the minimal authenticated account surface routed by Edge Gateway.
Tasks
- Implement
GetMyAccount. - Implement
UpdateMyProfileforrace_nameonly. - Implement
UpdateMySettingsfor:preferred_languagetime_zone
- Ensure
GetMyAccountreturns:- account identity fields
- current entitlement snapshot
- active sanctions
- active effective limits
- read-only
declared_country
- Reject attempts to mutate
emailordeclared_countrythrough self-service flows. - Enforce
profile_update_blocksanction on both self-service mutations.
Deliverables
- authenticated application services for account read and updates
- gateway-facing handler or adapter contracts for future routing
- DTOs for account aggregate and mutation requests
Exit Criteria
- authenticated users can read current account state in one aggregate
- profile and settings changes are clearly separated
- self-service updates cannot mutate forbidden fields
Targeted Tests
GetMyAccountreturns current entitlement, active sanctions, active limits, and read-onlydeclared_countryUpdateMyProfilecannot change email ordeclared_countryUpdateMySettingsvalidates BCP 47 and IANA values- active
profile_update_blockdenies both update flows
Stage 06 — Implement race_name Uniqueness Policy Behind a Dedicated Interface
Status: implemented.
Goal
Keep race_name uniqueness strict and replaceable.
Tasks
- Introduce a dedicated race-name policy interface.
- Implement canonicalization for uniqueness checks:
- case-insensitive folding
- confusable anti-fraud normalization
- Add Redis-backed reservation storage for canonicalized keys.
- Preserve original casing for stored and returned
race_name. - Ensure rename flow handles reservation swap safely.
- Keep the interface narrow so a future shared name-catalog service can replace the local implementation.
Deliverables
- race-name policy interface
- local normalization implementation
- reservation adapter and conflict handling
Exit Criteria
- no two users can hold conflicting
race_namevalues under the frozen policy - self-service rename is atomic with respect to uniqueness reservation
Targeted Tests
- uniqueness rejects case-insensitive collisions
- uniqueness rejects common anti-fraud-confusable collisions
- rename releases the old reservation only after the new one is secured
- failed reservation backend causes mutation to fail closed
Stage 07 — Implement Entitlement History Plus Materialized Current Snapshot
Status: implemented.
Goal
Support both auditability and fast synchronous entitlement reads.
Tasks
- Implement period-based entitlement history records.
- Implement a materialized current entitlement snapshot.
- Define the v1 plan catalog:
freepaid_monthlypaid_yearlypaid_lifetime
- Implement explicit trusted entitlement commands:
- grant paid access
- extend paid access
- revoke paid access
- Update current snapshot transactionally with each successful entitlement mutation.
- Ensure the default new-user path creates the correct free snapshot.
Deliverables
- entitlement domain model
- history store
- current snapshot store
- trusted entitlement command handlers
Exit Criteria
- current effective entitlement is always readable without replaying history
- history and snapshot stay consistent across supported mutation paths
Targeted Tests
- entitlement period mutations update the materialized current snapshot correctly
- free default is created for new users
- extending or revoking access preserves deterministic current-state behavior
Stage 08 — Implement Sanctions and Limit Records with Active/Effective Evaluation
Status: implemented.
Goal
Support negative policy and quota overrides without scattering policy logic into consumers.
Tasks
- Implement sanction records with optional expiry.
- Implement limit records with numeric values and optional expiry.
- Freeze v1 sanction catalog:
login_blockprivate_game_create_blockprivate_game_manage_blockgame_join_blockprofile_update_block
- Freeze v1 limit catalog:
max_owned_private_gamesmax_pending_public_applicationsmax_active_game_memberships
- Freeze supported v1 limit semantics:
- paid effective defaults:
max_owned_private_games=3max_pending_public_applications=10max_active_game_memberships=10
- free effective defaults:
max_owned_private_gamesis omittedmax_pending_public_applications=3max_active_game_memberships=3
max_active_game_membershipsapplies only to public gamesmax_pending_public_applicationsis the total public-games budget and is interpreted byGame Lobbytogether with current active public memberships
- paid effective defaults:
- Keep legacy retired limit codes backward-compatible on reads, but reject them for new trusted limit commands.
- Implement active/effective evaluation with current time.
- Implement trusted explicit commands to apply/remove sanctions and set/remove limits.
Deliverables
- sanction model and store
- limit model and store
- effective-state evaluator
- trusted mutation handlers
Exit Criteria
- active sanctions and active limits can be read consistently from one user account view
- expired or removed records are not treated as active
Targeted Tests
- active sanctions appear in account reads
- expired sanctions and limits stop affecting effective state
- retired legacy limit records are ignored during reads and effective evaluation
- retired legacy limit codes are rejected by trusted limit commands
- applying and removing sanctions/limits is idempotent where appropriate
Stage 09 — Implement Lobby Eligibility Snapshot API
Status: implemented.
Goal
Give Game Lobby one synchronous read that contains everything it needs for
user-level access decisions.
Tasks
- Design and implement one trusted query by
user_id. - Return:
- existence
- current entitlement snapshot
- active lobby-relevant sanctions
- effective lobby-relevant limits
- derived booleans for lobby decisions
- Freeze the lobby-facing effective limit catalog:
- paid users receive
max_owned_private_games=3,max_pending_public_applications=10, andmax_active_game_memberships=10 - free users omit
max_owned_private_gamesand receivemax_pending_public_applications=3andmax_active_game_memberships=3 max_pending_public_applicationsremains the total public-games budget consumed together with current active public memberships insideGame Lobby
- paid users receive
- Keep the response read-optimized so lobby does not need multiple dependent
calls back into
User Service. - Define deterministic not-found behavior.
Deliverables
- lobby eligibility query endpoint
- response DTO
- mapping from entitlement/sanction/limit state to derived eligibility fields
Exit Criteria
Game Lobbycan decide create/join/manage eligibility from one read- no extra fan-out to other user sub-queries is required
Targeted Tests
- lobby eligibility snapshot reflects paid status, sanctions, and limits
- unknown user returns stable not-found behavior
- derived booleans remain consistent with raw effective state
- free and paid snapshots materialize the reduced three-code effective limit catalog correctly
Stage 10 — Implement Geo declared_country Sync Command
Status: implemented.
Goal
Support the current-country denormalization path owned by Geo Profile Service.
Tasks
- Implement one explicit trusted command to sync current
declared_country. - Validate ISO alpha-2 input.
- Ensure the command updates only the current value on the user account.
- Do not add country history behavior to
User Service. - Preserve explicit not-found behavior for unknown
user_id. - Emit the corresponding auxiliary declared-country change event after a successful commit.
Deliverables
- geo-facing sync endpoint
- application service for country sync
- event publication on successful mutation
Exit Criteria
- geo can synchronize current
declared_countrywithout introducing hidden history inUser Service - unknown users are rejected deterministically
Targeted Tests
- geo country sync changes only current
declared_country - invalid country codes are rejected
- country sync emits the correct auxiliary event after commit
Stage 11 — Implement Admin Lookup, Filtered Listing, and Explicit Trusted Mutations
Status: implemented.
Goal
Provide the operational surface required by future Admin Service and manual
operations.
Tasks
- Implement exact reads by:
user_id- normalized
email - exact
race_name
- Implement paginated listing with richer filters:
- paid/free state
- paid expiry
- current
declared_country - sanction code
- limit code
- eligibility markers
- Freeze deterministic ordering for the listing.
- Implement the explicit trusted command surface for:
- entitlement grant/extend/revoke
- sanction apply/remove
- limit set/remove
- declared-country sync
- Preserve audit metadata on every trusted mutation.
Deliverables
- admin/internal read endpoints
- filtered listing endpoint
- explicit trusted mutation endpoints
Exit Criteria
- future
Admin Servicecan operate fully through this trusted API without needing direct storage access - list filtering and pagination are deterministic
Targeted Tests
- admin listing filters behave deterministically
- exact lookups by
user_id, email, andrace_nameresolve the correct user - every trusted mutation preserves actor and reason metadata
Stage 12 — Add Per-Domain-Area Async Events and Observability
Status: implemented.
Goal
Make production behavior observable without treating events as the source of truth.
Tasks
- Publish per-domain-area events for:
- profile changes
- settings changes
- entitlement changes
- sanction changes
- limit changes
- declared-country changes
- Add structured logs for trusted mutations and critical failures.
- Add metrics for:
- auth-facing resolution outcomes
- user creation outcomes
- race-name reservation conflicts
- entitlement mutation outcomes
- sanction and limit mutation outcomes
- event publication failures
- Add tracing spans on synchronous internal request paths where useful.
Deliverables
- event publisher integration
- structured logging hooks
- metrics and tracing instrumentation
Exit Criteria
- mutation flows are observable in production without ad hoc logging
- event publication failure does not compromise source-of-truth persistence
Targeted Tests
- async event publication failure does not lose source-of-truth state
- event payloads include minimum required metadata
- observability hooks do not change business behavior
Stage 13 — Add Contract Tests Against Auth, Lobby, and Geo Expectations
Status: implemented.
Goal
Verify the service not only in isolation, but against the internal contracts it must satisfy for other services.
Tasks
- Add compatibility tests against the frozen auth-facing REST contract.
- Add compatibility tests for the future ensure-by-email registration context.
- Add lobby eligibility snapshot contract tests.
- Add geo country-sync contract tests.
- Add account aggregate tests matching gateway-routed user expectations.
- Add tests for deterministic admin listing filters and ordering.
Deliverables
- cross-service contract test suite
- test fixtures for auth/lobby/geo integration expectations
Exit Criteria
- no ambiguity remains about service behavior expected by auth, lobby, or geo
- regressions in reserved internal contract shapes are caught automatically
Targeted Tests
- new user created on first successful confirm with generated
race_name, derivedpreferred_language, and required clienttime_zone - existing user confirm ignores create-only registration context
- blocked e-mail subject prevents user creation before a user record exists
GetMyAccountreturns current entitlement, active sanctions, active limits, and read-onlydeclared_country- lobby eligibility snapshot reflects paid status, sanctions, and limits
- geo country sync changes only current
declared_country
Stage 14 — Add Rollout Notes for Gateway/Auth/OpenAPI Updates and Shared geoip
Status: implemented.
Goal
Prepare the surrounding platform changes required for the service to work in its intended end-to-end form.
Tasks
- Document the required
gatewaypublicconfirm-email-codedependency ontime_zone. - Document the required
authsessionpublic OpenAPI preservation of the sametime_zonerequirement. - Document that the frozen
authsession -> userensure contract requires create-onlyregistration_contextwithpreferred_languageandtime_zone. - Document the required shared
pkg/geoippackage for gateway and geo. - Document README follow-up updates needed in
gatewayandgeoprofile. - Define rollout order so the cross-service contract changes do not land in an unsafe sequence.
Deliverables
- rollout checklist
- dependency order notes
- cross-repo or cross-module follow-up ticket list
Exit Criteria
- the implementation can be integrated into surrounding services without rediscovering hidden dependencies
- no required upstream or downstream change is left implicit
Targeted Tests
- documentation review only
Recommended First Working Slice
The smallest useful end-to-end slice is:
- Stage 01
- Stage 02
- Stage 03
- Stage 04
This slice makes it possible to support auth-driven user creation and blocking before the rest of the service surface exists.
Recommended Second Slice
The next highest-value slice is:
- Stage 05
- Stage 06
- Stage 07
- Stage 08
- Stage 09
This slice gives the platform usable account reads, self-service profile and settings updates, and the lobby eligibility integration.
Final Acceptance Criteria
The first production-capable v1 of User Service should satisfy all of the
following:
- new users can be created through auth with generated
race_name, derivedpreferred_language, and required clienttime_zone - existing-user auth confirm ignores create-only registration context
- blocked e-mail subjects prevent new-user creation before a user record exists
race_nameuniqueness rejects case-insensitive and anti-fraud-confusable collisionsGetMyAccountreturns current entitlement, active sanctions, active limits, and read-onlydeclared_countryUpdateMyProfilecannot change email ordeclared_countryUpdateMySettingsvalidates BCP 47 and IANA values- entitlement period mutations update the materialized current snapshot correctly
- lobby eligibility snapshot reflects paid status, sanctions, and limits
- geo
declared_countrysync changes only current account state - admin listing filters and ordering are deterministic
- async event publication failure does not lose source-of-truth state
Implementation Order Summary
Recommended implementation order:
- freeze vocabulary and ownership
- define domain entities and logical storage
- build auth-facing resolution and blocking
- add new-user creation context
- build self-service account read and updates
- add race-name uniqueness policy
- build entitlement history and current snapshot
- build sanctions and limits
- add lobby eligibility snapshot
- add geo country sync
- add admin reads, listing, and mutations
- add events and observability
- add cross-service contract tests
- document and sequence rollout dependencies