Event-driven notification service for an e-commerce platform.
Order events flow through RabbitMQ into a durable consumer pipeline —
persisted to PostgreSQL, reported to S3, with graceful degradation
when upstream services fail. JWT-secured REST API, Testcontainers
integration tests, LocalStack S3 in Docker.
POST /orders │ ▼ OrderController ──► OrderProducer ──► RabbitMQ (orders.queue, durable) │ ▼ OrderConsumer │ ┌─────────────────────┼──────────────────────┐ ▼ ▼ ▼ OrderRepository NotificationRepository S3ReportService │ │ │ ▼ ▼ ┌──────┴──────┐ PostgreSQL PostgreSQL │ success │ failure │ │ ▼ ▼ SENT PENDING_UPLOAD │ @Scheduled every 5 min ──► S3RetryScheduler
| topic | decision | why |
|---|---|---|
| Hibernate N+1 | JOIN FETCH in JPQL | Single SQL loads orders + notifications instead of 1+N queries |
| S3 fallback | PENDING_UPLOAD status | DB commit before S3 — upload failure never loses the order |
| Idempotency | existsById() check | Duplicate RabbitMQ delivery silently skipped, no duplicate orders |
| Transaction split | TransactionTemplate | S3 call outside TX — connection not held open during slow upload |
| DLQ | orders.dlq | Poison messages don't loop forever — inspectable, replayable |
| Retry scheduler | @Scheduled fixed delay | S3 recovers → all PENDING_UPLOAD notifications auto-healed |
| Auth | JWT + Spring Security | Stateless, ownership-checked — users only see their own orders |
| test | containers | what it verifies | result |
|---|---|---|---|
| whenOrderCreated_thenOrderAndNotificationSavedInDb | Postgres · RabbitMQ | REST → queue → consumer → DB · full async flow with Awaitility | ✓ PASS |
| whenMessagePublishedDirectly_thenConsumedAndPersistedToPostgres | Postgres · RabbitMQ | Producer → durable queue → consumer saves Order + Notification | ✓ PASS |
| whenReportUploaded_thenFileExistsInS3AndPresignedUrlIsReturned | Postgres · RabbitMQ · LocalStack | S3 upload via LocalStack · HeadObject confirms file · presigned URL generated | ✓ PASS |
| whenFetchingOrdersForUser_thenNotificationsLoadedWithoutN1 | Postgres · RabbitMQ · LocalStack | 2 orders × 2 notifications — JOIN FETCH loads all without LazyInitializationException | ✓ PASS |
| whenS3Unavailable_thenNotificationSavedWithPendingUploadStatus | Postgres · RabbitMQ · LocalStack | Bucket deleted → upload fails → notification persisted as PENDING_UPLOAD | ✓ PASS |