645 lines
20 KiB
Markdown
645 lines
20 KiB
Markdown
# User Service Implementation Plan
|
|
|
|
## 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
|
|
|
|
### 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:
|
|
- `GetMyAccount`
|
|
- `UpdateMyProfile`
|
|
- `UpdateMySettings`
|
|
- Freeze that `race_name` replaces `display_name`.
|
|
- Freeze the current ownership split for `declared_country`:
|
|
- current value in `User Service`
|
|
- workflow and history in `Geo Profile Service`
|
|
- Freeze the auth-facing internal REST endpoints already reserved by
|
|
`Auth / Session Service`.
|
|
- Freeze the need for create-only registration context on
|
|
`EnsureUserByEmail`.
|
|
|
|
### Deliverables
|
|
|
|
- service README with stable terminology
|
|
- short internal ADR or equivalent note for `declared_country` ownership 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
|
|
|
|
### 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
|
|
|
|
### 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 `BlockByEmail` idempotent for both existing-user and no-user cases.
|
|
- Ensure `ResolveByEmail` and `EnsureUserByEmail` both 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
|
|
|
|
### Exit Criteria
|
|
|
|
- auth can distinguish `existing`, `creatable`, and `blocked`
|
|
- blocked e-mail subjects prevent user creation before a user exists
|
|
- `BlockByUserID` and `BlockByEmail` are 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 — Add New-User Creation Context from Auth
|
|
|
|
### Goal
|
|
|
|
Support first-login user creation with initial settings captured at confirm
|
|
time.
|
|
|
|
### Tasks
|
|
|
|
- Extend `EnsureUserByEmail` contract with create-only registration context:
|
|
- `preferred_language`
|
|
- `time_zone`
|
|
- Validate `preferred_language` as BCP 47.
|
|
- Validate `time_zone` as IANA TZ name.
|
|
- Generate initial `race_name` in `player-<shortid>` form during creation.
|
|
- Initialize the newly created user with:
|
|
- free entitlement
|
|
- no active sanctions
|
|
- no custom limits
|
|
- Ignore registration context for existing users.
|
|
- Document required follow-up changes in `gateway` and `authsession`.
|
|
|
|
### Deliverables
|
|
|
|
- extended ensure-by-email request model
|
|
- create-user domain service
|
|
- generated-race-name helper
|
|
|
|
### 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`, derived `preferred_language`,
|
|
and required client `time_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
|
|
|
|
### Goal
|
|
|
|
Expose the minimal authenticated account surface routed by `Edge Gateway`.
|
|
|
|
### Tasks
|
|
|
|
- Implement `GetMyAccount`.
|
|
- Implement `UpdateMyProfile` for `race_name` only.
|
|
- Implement `UpdateMySettings` for:
|
|
- `preferred_language`
|
|
- `time_zone`
|
|
- Ensure `GetMyAccount` returns:
|
|
- account identity fields
|
|
- current entitlement snapshot
|
|
- active sanctions
|
|
- active effective limits
|
|
- read-only `declared_country`
|
|
- Reject attempts to mutate `email` or `declared_country` through self-service
|
|
flows.
|
|
- Enforce `profile_update_block` sanction 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
|
|
|
|
- `GetMyAccount` returns current entitlement, active sanctions, active limits,
|
|
and read-only `declared_country`
|
|
- `UpdateMyProfile` cannot change email or `declared_country`
|
|
- `UpdateMySettings` validates BCP 47 and IANA values
|
|
- active `profile_update_block` denies both update flows
|
|
|
|
## Stage 06 — Implement race_name Uniqueness Policy Behind a Dedicated Interface
|
|
|
|
### 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_name` values 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
|
|
|
|
### 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:
|
|
- `free`
|
|
- `paid_monthly`
|
|
- `paid_yearly`
|
|
- `paid_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
|
|
|
|
### 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_block`
|
|
- `private_game_create_block`
|
|
- `private_game_manage_block`
|
|
- `game_join_block`
|
|
- `profile_update_block`
|
|
- Freeze v1 limit catalog:
|
|
- `max_owned_private_games`
|
|
- `max_active_private_games`
|
|
- `max_pending_public_applications`
|
|
- `max_pending_private_join_requests`
|
|
- `max_pending_private_invites_sent`
|
|
- `max_active_game_memberships`
|
|
- 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
|
|
- applying and removing sanctions/limits is idempotent where appropriate
|
|
|
|
## Stage 09 — Implement Lobby Eligibility Snapshot API
|
|
|
|
### 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
|
|
- 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 Lobby` can 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
|
|
|
|
## Stage 10 — Implement Geo declared_country Sync Command
|
|
|
|
### 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_country` without introducing hidden
|
|
history in `User 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
|
|
|
|
### 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 Service` can 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, and `race_name` resolve the correct user
|
|
- every trusted mutation preserves actor and reason metadata
|
|
|
|
## Stage 12 — Add Per-Domain-Area Async Events and Observability
|
|
|
|
### 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
|
|
|
|
### 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`,
|
|
derived `preferred_language`, and required client `time_zone`
|
|
- existing user confirm ignores create-only registration context
|
|
- blocked e-mail subject prevents user creation before a user record exists
|
|
- `GetMyAccount` returns current entitlement, active sanctions, active limits,
|
|
and read-only `declared_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
|
|
|
|
### Goal
|
|
|
|
Prepare the surrounding platform changes required for the service to work in
|
|
its intended end-to-end form.
|
|
|
|
### Tasks
|
|
|
|
- Document the required `gateway` public `confirm-email-code` addition of
|
|
`time_zone`.
|
|
- Document the required `authsession` public OpenAPI mirror change.
|
|
- Document the required `authsession -> user` ensure contract extension for
|
|
create-only registration context.
|
|
- Document the required shared `pkg/geoip` package for gateway and geo.
|
|
- Document README follow-up updates needed in `gateway` and `geoprofile`.
|
|
- 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:
|
|
|
|
1. Stage 01
|
|
2. Stage 02
|
|
3. Stage 03
|
|
4. 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:
|
|
|
|
1. Stage 05
|
|
2. Stage 06
|
|
3. Stage 07
|
|
4. Stage 08
|
|
5. 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`, derived
|
|
`preferred_language`, and required client `time_zone`
|
|
- existing-user auth confirm ignores create-only registration context
|
|
- blocked e-mail subjects prevent new-user creation before a user record exists
|
|
- `race_name` uniqueness rejects case-insensitive and anti-fraud-confusable
|
|
collisions
|
|
- `GetMyAccount` returns current entitlement, active sanctions, active limits,
|
|
and read-only `declared_country`
|
|
- `UpdateMyProfile` cannot change email or `declared_country`
|
|
- `UpdateMySettings` validates 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_country` sync 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:
|
|
|
|
1. freeze vocabulary and ownership
|
|
2. define domain entities and logical storage
|
|
3. build auth-facing resolution and blocking
|
|
4. add new-user creation context
|
|
5. build self-service account read and updates
|
|
6. add race-name uniqueness policy
|
|
7. build entitlement history and current snapshot
|
|
8. build sanctions and limits
|
|
9. add lobby eligibility snapshot
|
|
10. add geo country sync
|
|
11. add admin reads, listing, and mutations
|
|
12. add events and observability
|
|
13. add cross-service contract tests
|
|
14. document and sequence rollout dependencies
|