366 lines
9.7 KiB
Markdown
366 lines
9.7 KiB
Markdown
# Notification 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.
|
|
|
|
## Summary
|
|
|
|
This plan builds `Notification Service` as a durable asynchronous orchestration
|
|
service between domain producers, `Gateway`, `Mail Service`, and `User Service`.
|
|
The implementation must keep business-audience resolution in the producer,
|
|
contact enrichment in `Notification Service`, client push delivery in
|
|
`Gateway`, and email execution in `Mail Service`.
|
|
|
|
## Global Rules
|
|
|
|
- Keep `Notification Service` orchestration-only.
|
|
- Preserve direct auth-code email flow from `Auth / Session Service` to `Mail Service`.
|
|
- Use one dedicated Redis Stream for normalized notification intents.
|
|
- Keep route retries independent per channel.
|
|
- Do not make notification delivery a correctness dependency for gameplay or
|
|
geo review state.
|
|
- Keep user-facing push payloads lightweight.
|
|
|
|
## ~~Stage 01.~~ Freeze Vocabulary And Cross-Service Ownership
|
|
|
|
Status: implemented.
|
|
|
|
Note:
|
|
|
|
- Later-stage artifacts may already exist in the repository as draft or
|
|
pre-staged documentation.
|
|
- Their presence does not mark the corresponding later stages as implemented.
|
|
|
|
Goal:
|
|
|
|
- remove ambiguity before runtime work starts
|
|
|
|
Tasks:
|
|
|
|
- Freeze `notification:intents` as the dedicated ingress stream.
|
|
- Freeze that producers publish concrete `recipient_user_id` values for
|
|
user-targeted intents.
|
|
- Freeze that `Notification Service` resolves user email and locale from
|
|
`User Service`.
|
|
- Freeze that admin-only notifications use type-specific configured email
|
|
lists.
|
|
- Freeze that `template_id == notification_type`.
|
|
- Freeze that private-game invites in v1 are user-bound by internal `user_id`.
|
|
|
|
Exit criteria:
|
|
|
|
- `ARCHITECTURE.md`, `TESTING.md`, and service READMEs no longer contradict the
|
|
agreed notification model
|
|
|
|
## ~~Stage 02.~~ Define The Intent Contract
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- publish one stable producer-to-notification contract
|
|
|
|
Tasks:
|
|
|
|
- Add `notification/api/intents-asyncapi.yaml`.
|
|
- Freeze envelope fields:
|
|
- `notification_type`
|
|
- `producer`
|
|
- `audience_kind`
|
|
- `recipient_user_ids_json`
|
|
- `idempotency_key`
|
|
- `occurred_at_ms`
|
|
- `request_id`
|
|
- `trace_id`
|
|
- `payload_json`
|
|
- Freeze duplicate and conflict rules on `(producer, idempotency_key)`.
|
|
- Freeze `audience_kind=user|admin_email`.
|
|
|
|
Exit criteria:
|
|
|
|
- every producer can publish normalized intents without service-specific side
|
|
agreements
|
|
|
|
## ~~Stage 03.~~ Freeze The Notification Catalog
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- turn product decisions into one exact type catalog
|
|
|
|
Tasks:
|
|
|
|
- Freeze v1 types and channel matrix.
|
|
- Freeze which types are user-targeted versus admin-only.
|
|
- Freeze that `lobby.application.submitted` is user-targeted for private games
|
|
and admin-email-only for public games.
|
|
- Freeze that `lobby.invite.revoked` produces no notification.
|
|
- Freeze payload requirements per type.
|
|
|
|
Exit criteria:
|
|
|
|
- no notification type remains partially specified
|
|
|
|
## ~~Stage 04.~~ Define Push Payload Schemas
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- freeze lightweight client-facing payloads
|
|
|
|
Tasks:
|
|
|
|
- Add `pkg/schema/fbs/notification.fbs`.
|
|
- Define one table per user-facing push type.
|
|
- Generate Go bindings under `pkg/schema/fbs/notification`.
|
|
- Document the mapping from `notification_type` to FlatBuffers table.
|
|
|
|
Exit criteria:
|
|
|
|
- `Gateway` and future client code have one stable schema file for
|
|
user-facing notification payloads
|
|
|
|
## ~~Stage 05.~~ Freeze Mail Template Contracts
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- make email handoff deterministic
|
|
|
|
Tasks:
|
|
|
|
- Freeze `payload_mode=template` for notification-generated email.
|
|
- Add initial `en` templates for all supported email types in
|
|
`mail/templates/<template_id>/en`.
|
|
- Update `mail` documentation so notification template IDs align with
|
|
`notification_type`.
|
|
- Keep `Auth / Session Service` auth-code mail unchanged.
|
|
|
|
Exit criteria:
|
|
|
|
- every supported email notification type has a documented template directory
|
|
|
|
## ~~Stage 06.~~ Define Redis State And Retry Model
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- freeze durable service-local storage before runtime code
|
|
|
|
Tasks:
|
|
|
|
- Define `notification_record`, `notification_route`,
|
|
`notification_idempotency_record`, `notification_dead_letter_entry`, and
|
|
malformed-intent storage.
|
|
- Freeze Redis keys and schedule structures.
|
|
- Freeze route status vocabulary:
|
|
- `pending`
|
|
- `published`
|
|
- `failed`
|
|
- `dead_letter`
|
|
- `skipped`
|
|
- Freeze retry budgets:
|
|
- `push=3`
|
|
- `email=7`
|
|
|
|
Exit criteria:
|
|
|
|
- the runtime can restart without losing accepted-or-retryable work
|
|
|
|
## ~~Stage 07.~~ Build The Runnable Service Skeleton
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- create the initial process shape
|
|
|
|
Tasks:
|
|
|
|
- Add `cmd/notification`.
|
|
- Add `internal/app`, `internal/config`, `internal/api`, `internal/service`,
|
|
and `internal/adapters` packages.
|
|
- Wire Redis startup checks, graceful shutdown, logger setup, and telemetry.
|
|
- Do not add an operator REST API in v1.
|
|
|
|
Exit criteria:
|
|
|
|
- the process boots with Redis and configuration validation only
|
|
|
|
## ~~Stage 08.~~ Implement Intent Acceptance And Idempotency
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- durably accept valid intents and reject invalid or conflicting duplicates
|
|
|
|
Tasks:
|
|
|
|
- Consume `notification:intents`.
|
|
- Validate the envelope and normalized payload.
|
|
- Persist idempotency records and accepted notification records.
|
|
- Record malformed intents separately.
|
|
- Materialize channel routes according to the type catalog and `audience_kind`.
|
|
|
|
Exit criteria:
|
|
|
|
- valid intents are durable and replay-safe before downstream publication begins
|
|
|
|
## ~~Stage 09.~~ Implement User Enrichment And Locale Resolution
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- make user-targeted routes self-sufficient for later publication
|
|
|
|
Tasks:
|
|
|
|
- Read users by `user_id` from `User Service`.
|
|
- Extract `email` and `preferred_language`.
|
|
- Apply `en` fallback when locale is missing or unsupported.
|
|
- Keep admin-email routes independent from `User Service`.
|
|
|
|
Exit criteria:
|
|
|
|
- every user-targeted route can be published without additional producer input
|
|
|
|
## ~~Stage 10.~~ Implement Push Publication
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- hand off user-facing notification events to `Gateway`
|
|
|
|
Tasks:
|
|
|
|
- Encode the correct FlatBuffers table per `notification_type`.
|
|
- Publish client events into the configured `Gateway` stream with `user_id`
|
|
targeting only.
|
|
- Apply independent `push` retry policy and route-level dead-letter handling.
|
|
|
|
Exit criteria:
|
|
|
|
- user-targeted push notifications survive temporary `Gateway` stream failures
|
|
|
|
## ~~Stage 11.~~ Implement Mail Publication
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- hand off non-auth email notifications to `Mail Service`
|
|
|
|
Tasks:
|
|
|
|
- Build template-mode generic mail commands.
|
|
- Set `template_id == notification_type`.
|
|
- Pass through normalized template variables from `payload_json`.
|
|
- Apply independent `email` retry policy and route-level dead-letter handling.
|
|
|
|
Exit criteria:
|
|
|
|
- user and admin email notifications are durably handed off to `Mail Service`
|
|
|
|
## ~~Stage 12.~~ Integrate Producers
|
|
|
|
Status: implemented.
|
|
|
|
Note:
|
|
|
|
- Implemented as the shared Go producer contract module
|
|
`galaxy/notificationintent` because `Game Lobby` and `Geo Profile Service`
|
|
code modules are not present in this repository yet.
|
|
|
|
Goal:
|
|
|
|
- move upstream services onto the new notification contract
|
|
|
|
Tasks:
|
|
|
|
- `Game Master` publishes:
|
|
- `game.turn.ready`
|
|
- `game.finished`
|
|
- `game.generation_failed`
|
|
- `Game Lobby` publishes:
|
|
- `lobby.runtime_paused_after_start`
|
|
- `lobby.application.submitted`
|
|
- `lobby.membership.approved`
|
|
- `lobby.membership.rejected`
|
|
- `lobby.invite.created`
|
|
- `lobby.invite.redeemed`
|
|
- `lobby.invite.expired`
|
|
- `Geo Profile Service` publishes:
|
|
- `geo.review_recommended`
|
|
- Update `Game Lobby` architecture and later implementation plan to use
|
|
user-bound private invites by `user_id`.
|
|
|
|
Exit criteria:
|
|
|
|
- producers no longer rely on ad hoc notification-side audience inference
|
|
|
|
## ~~Stage 13.~~ Add Observability And Recovery Coverage
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- make the async runtime supportable in operations
|
|
|
|
Tasks:
|
|
|
|
- Add metrics for intake, duplicates, enrichment, publish attempts, retries,
|
|
dead letters, and lag.
|
|
- Add structured logging fields shared across intake and route publishers.
|
|
- Document manual recovery steps for dead-letter inspection and replay.
|
|
|
|
Exit criteria:
|
|
|
|
- the runtime exposes enough signals to detect stuck, noisy, or broken delivery
|
|
|
|
## ~~Stage 14.~~ Complete Test Coverage And Documentation Alignment
|
|
|
|
Status: implemented.
|
|
|
|
Goal:
|
|
|
|
- close the loop across service tests, boundary tests, and docs
|
|
|
|
Tasks:
|
|
|
|
- Add service tests for malformed intents, duplicates, locale fallback, retry
|
|
budgets, and route isolation.
|
|
- Add inter-service tests with `Gateway`, `Mail Service`, `Game Master`,
|
|
`Game Lobby`, and `Geo Profile Service`.
|
|
- Update `TESTING.md`.
|
|
- Update `ARCHITECTURE.md`, `mail/README.md`, `geoprofile/README.md`, and
|
|
gateway examples.
|
|
- Verify docs still state that auth-code mail bypasses `Notification Service`.
|
|
|
|
Exit criteria:
|
|
|
|
- the implementation and the cross-service documentation describe the same
|
|
contracts
|
|
|
|
## Final Acceptance Criteria
|
|
|
|
The implementation is complete only when all of the following hold:
|
|
|
|
- valid intents are consumed from `notification:intents`
|
|
- duplicates are idempotent and conflicting duplicates are rejected
|
|
- user enrichment resolves email and locale from `User Service`
|
|
- `push` and `email` routes are persisted and retried independently
|
|
- route dead letters are isolated per channel and per recipient
|
|
- `Gateway` fan-out remains user-wide, not session-specific
|
|
- `Mail Service` receives template-mode commands whose template IDs match
|
|
notification types
|
|
- admin notifications remain `email`-only
|
|
- auth-code email still bypasses `Notification Service`
|