Notification Service
Multi-channel notification delivery platform. Other services publish to a named topic; the notification service fans out to all channels configured for that topic — Firebase FCM push, STOMP/WebSocket, and HTTP (SSE via Redis ZSet).
Runtime: Spring Boot 3.5.7 / Java 21 · 3 pods · nexivo namespace
Image: communication-services/notification-service
Tech Stack
| Component |
Detail |
| Framework |
Spring Boot 3.5.7, Java 21 |
| Database |
PostgreSQL |
| Cache / Pub-Sub / SSE Store |
Redis |
| Push Notifications |
Firebase Admin SDK 9.2.0 (FCM) |
Delivery Channels
| Channel Type |
Mechanism |
Target |
Notes |
PUSH_NOTIFICATION |
Firebase FCM |
Device token or FCM topic |
TTL per platform; auto-removes unregistered tokens |
WEBSOCKET |
STOMP over SockJS |
/user/{userId}/{topic} (unicast) or /topic/{topic} (broadcast) |
User identified by X-USER-ID header |
HTTP |
SSE via Redis ZSet |
Connected GET /me/notifications clients |
Missed events replayed on reconnect via Last-Event-ID |
Send Flow
sequenceDiagram
participant Caller as Calling Service
participant NS as NotificationService
participant FCM as Firebase FCM
participant STOMP as STOMP Broker
participant Redis
participant SSE as SSE Client
Caller->>NS: POST /notifications/{topic}?subscriber=userId
NS->>NS: Resolve NotificationSettings for topic
NS->>NS: Fan-out to all ChannelConfigs
alt PUSH_NOTIFICATION channel
NS->>FCM: Send to device token(s) or FCM topic
FCM-->>NS: Delivery receipt
NS->>NS: Auto-remove unregistered tokens on error
end
alt WEBSOCKET channel
NS->>STOMP: convertAndSendToUser(userId, /topic, payload)
end
alt HTTP channel
NS->>Redis: ZADD notifications:{userId}:{channelId} <timestamp> <payload>
Redis-->>SSE: PUBLISH notifications channel event
SSE-->>SSE: Pop ZSet entries, push to emitter
end
Key Entities
NotificationChannel
A reusable, named delivery endpoint definition.
| Field |
Type |
Notes |
name |
String |
Human-readable identifier |
channelType |
Enum |
WEBSOCKET, PUSH_NOTIFICATION, HTTP |
NotificationSettings
Binds a unique topic to a TTL and one or more channel configs.
| Field |
Type |
Notes |
topic |
String |
Unique; used in POST /notifications/{topic} |
ttl |
Integer (seconds) |
Message time-to-live |
channelConfigs |
1:M → ChannelConfig |
Delivery targets |
ChannelConfig (single-table inheritance)
| Subtype |
Extra Fields |
PushNotificationConfig |
title, body, priority (HIGH / NORMAL) |
HttpConfig |
(base fields only) |
WebsocketConfig |
(base fields only) |
FcmTokenEntity
| Field |
Type |
Notes |
token |
String |
Unique; FCM registration token |
userId |
UUID |
Owning user |
platformType |
Enum |
ANDROID, IOS, WEB |
REST API
Notification Dispatch
| Method |
Path |
Description |
POST |
/notifications/{topic} |
Send notification to topic. Optional ?subscriber={userId} for unicast; omit for broadcast. |
Channel Definitions — /notification-channels
| Method |
Path |
Description |
GET |
/notification-channels |
List channels |
POST |
/notification-channels |
Create channel |
GET |
/notification-channels/{id} |
Get channel |
PUT |
/notification-channels/{id} |
Update channel |
DELETE |
/notification-channels/{id} |
Delete channel |
Topic Settings — /notification-settings
| Method |
Path |
Description |
GET |
/notification-settings |
List topic settings |
POST |
/notification-settings |
Create topic + channel configs |
GET |
/notification-settings/{id} |
Get topic settings |
PUT |
/notification-settings/{id} |
Update topic settings |
DELETE |
/notification-settings/{id} |
Delete topic settings |
Device Tokens — /me/fcm-tokens
| Method |
Path |
Description |
POST |
/me/fcm-tokens |
Register FCM device token |
GET |
/me/fcm-tokens |
List tokens for current user |
DELETE |
/me/fcm-tokens/{token} |
Remove a token |
SSE Subscription — /me/notifications
| Method |
Path |
Description |
GET |
/me/notifications |
Subscribe to live notification stream (text/event-stream) |
SSE Subscription Detail
The GET /me/notifications endpoint returns a text/event-stream response backed by a SseEmitter.
Behaviour:
- Timeout: 30 minutes per connection.
- Heartbeat: every 5 seconds (configurable via
notification.heartbeat.interval-ms).
Last-Event-ID support: on reconnect, the service replays any ZSet entries with a score (timestamp) greater than the supplied ID, recovering missed notifications.
- Concurrency: a
ReentrantLock per channel prevents concurrent emit races across multiple threads.
- Re-queue logic: a notification is only re-queued into the Redis ZSet if all registered emitters for the user fail to deliver.
Redis ZSet key pattern: notifications:{userId}:{channelId}
ZSet score = Unix timestamp (ms). TTL mirrors NotificationSettings.ttl.
Firebase FCM Delivery Notes
- Tokens are stored per
platformType; TTL is applied per platform:
- APNS (iOS):
apns-expiration header set from TTL.
- Android:
ttl field in FCM message (milliseconds).
- Web push:
TTL header in Webpush config.
- On any FCM error indicating an unregistered or invalid token, the
FcmTokenEntity row is automatically deleted.
- Topic broadcast (no
subscriber param) sends to the FCM topic matching the notification topic name.
Key Configuration
| Property |
Default / Example |
Description |
notification.firebase.service-account-key-json |
firebase.json (dev) · /etc/secrets/firebase.json (k8s) |
Path to Firebase service account JSON |
notification.heartbeat.interval-ms |
5000 |
SSE heartbeat interval in milliseconds |
| Redis |
(standard Spring Redis config) |
Used for SSE ZSet storage and STOMP broker relay |