الملفات
back_end_oudelaa/docs/MICROSERVICES_MIGRATION.md
boutmoun123 8147f3b191
فشلت بعض الفحوصات
Deploy To Ghaymah / quality (push) Has been cancelled
Deploy To Ghaymah / deploy (push) Has been cancelled
feat: add notification microservices and transactional outbox
2026-08-05 14:46:12 +03:00

7.0 KiB

Oudelaa microservices migration

Current architecture

The backend is a NestJS modular monolith backed by MongoDB. Redis is used for cache, BullMQ jobs, and the optional Socket.IO adapter. Domain modules call the in-process NotificationsService. Public HTTP routes use the global /api/v1 prefix and the notifications Socket.IO namespace is owned by the same application.

Phase 1 boundary

  • apps/api-gateway is the new monorepo entry point. It deliberately bootstraps the existing src/AppModule, so the application remains runnable while modules are moved incrementally.
  • apps/notification-service owns notification persistence, queries, unread counts, read mutations, and the follow.request.approved consumer.
  • The four existing Flutter notification endpoints remain on the gateway. The gateway delegates them over Nest RMQ only when NOTIFICATION_SERVICE_ENABLED=true; otherwise it uses the original in-process service.
  • Socket.IO authentication, namespace, rooms, and public event names remain in the gateway. Internal RMQ responses include realtime data which the gateway emits.
  • Other notification producers stay in-process in phase 1. They can migrate one event at a time in later phases.

Reliability and topology

The main durable queue is oudelaa.notifications. Rejected messages are dead-lettered to the durable oudelaa.notifications.dlq queue through the default exchange. Consumers use manual acknowledgement and a prefetch limit. Event handling retries three times inside one delivery, then rejects without requeue. Gateway calls have a bounded timeout, one configurable retry, and a small circuit breaker. Rabbit message headers carry correlationId and requestId; logs are JSON records containing both.

Follow-request approval is the first true transactional-outbox flow. The request state, follow relationship, user counters, and deterministic outbox event are committed in one MongoDB transaction. No BullMQ or RabbitMQ call occurs inside that transaction. When both phase-1 flags are enabled, a polling dispatcher atomically leases pending/failed events, waits for the Notification Service RPC response, and only then marks the event processed. Timeouts use bounded exponential backoff and eventually move the event to dead; expired processing leases are recovered after restart. OutboxService.listByStatuses() and retryEvent() are the intentionally non-public administrative surface in this phase.

eventId is stored on notifications under a unique partial index. The consumer checks it before insert and also handles Mongo duplicate-key races. The existing unique index on recipient/type/reference remains as an additional guard for follow approval records.

Planned migration

  1. Phase 1: monorepo shell, contracts/events/common libraries, gateway compatibility facade, and Notification Service.
  2. Phase 2: move Auth and Users behind versioned internal contracts; centralize JWT verification primitives in libs/auth; add tracing/metrics in libs/observability.
  3. Phase 3: extract Posts and Media; replace direct notification calls with outbox events.
  4. Phase 4: extract Feed and Search read models, consuming post/user events.
  5. Phase 5: extract Moderation, then stabilize cross-service workflows and data ownership.
  6. Phase 6: remove transitional imports and local fallbacks after traffic comparison, replay tests, and a controlled cutover.

At every phase the gateway retains public paths and response envelopes. Internal event contracts evolve additively and are versioned before any breaking change.

Configuration and operating modes

NOTIFICATION_SERVICE_ENABLED=false and OUTBOX_DISPATCHER_ENABLED=false remain the safe defaults. In that mode the gateway does not instantiate or connect an RMQ client; the in-process notification path and public Flutter contracts remain unchanged. The standalone-Mongo fallback exists only for this disabled legacy transition mode.

The reliable phase-1 path requires both flags to be true. Dispatcher interval, batch, lease, attempt limit, and bounded backoff are configured by OUTBOX_*. RabbitMQ accepts an injected RABBITMQ_URL or separate host, port, username, password, and vhost values. TLS supports amqps://, CA/client material injected as base64 or mounted paths, peer verification, and SNI. Passwords and URL userinfo are never logged.

docker-compose.microservices.yml is local-only. It deliberately uses guest/guest inside the private Compose network, exposes management only on loopback, and runs a single-node Mongo replica set so transactions are exercised locally. It is not a production template. Queue names remain oudelaa.notifications and oudelaa.notifications.dlq to avoid an undeclared migration; both are durable, non-exclusive, and non-auto-delete. RPC messages are persistent, and consumers use manual acknowledgement with a prefetch limit.

Production prerequisites

  • Managed RabbitMQ (or an operated HA cluster), a dedicated vhost, non-guest least-privilege credentials, and TLS/amqps. Inject credentials and certificates from the deployment secret store; never copy local Compose values.
  • A transaction-capable MongoDB replica set or sharded cluster. Gateway startup rejects dispatcher activation against standalone MongoDB.
  • Production Redis, service-specific secrets, persistent storage where applicable, and network policy that permits RMQ/Mongo/Redis access only from the services.
  • Liveness probes at /api/v1/health and /health/live; readiness probes at /api/v1/health/ready and /health/ready. A temporary RMQ outage fails readiness but does not fail liveness. RabbitMQ management must not be publicly exposed.
  • Alerts for pending/failed/dead outbox counts, oldest event age, dispatcher failures, notification DLQ depth, RMQ connectivity, and duplicate-key activity.

Controlled activation and rollback

  1. Provision production RabbitMQ with TLS, a dedicated vhost, and least-privilege user.
  2. Confirm MongoDB is a replica set/mongos and Redis is production-ready.
  3. Deploy Notification Service with injected secrets and verify liveness/readiness.
  4. Deploy Gateway with both phase-1 flags still false; verify the monolith path.
  5. Inject the same RMQ endpoint/trust settings into Gateway without logging them.
  6. Enable both flags on one canary Gateway replica.
  7. Approve a controlled private follow and observe outbox pending -> processing -> processed, one notification, and one Socket emission.
  8. Monitor retries, dead events, DLQ, readiness, latency, and duplicate-key metrics.
  9. Expand the flags gradually; atomic leases make multiple Gateway replicas safe.
  10. Roll back by setting both flags to false. Do not delete pending events or queues; diagnose or retry them before the next canary, then drain Notification Service.

Notification Service still uses the same Mongo database and the same notifications collection as Gateway. This is a transitional shared-database model, not independent data ownership; physical separation belongs to a later migration phase.