Service Quarkus chargé de l'envoi des notifications push (Firebase Cloud Messaging) de l'ENT.
Il consomme une file de notifications en PostgreSQL, résout les tokens FCM des destinataires dans Neo4j, envoie via l'API FCM HTTP v1, journalise un rapport par token et purge les tokens devenus invalides.
- Java 25 / Quarkus 3.36, threads virtuels
- Port HTTP par défaut : 8101
- Artefact :
io.edifice:push-manager
Un scheduler (SendNotificationScheduler) tourne toutes les 10 s, sans exécution concurrente
(ConcurrentExecution.SKIP), sur thread virtuel. Le traitement est découpé en trois phases aux
transactions distinctes : aucune transaction n'est tenue pendant un appel réseau.
flowchart TD
A["TX1 — claimNextBatch()<br/>SELECT ... FOR UPDATE SKIP LOCKED (≤ 10 000)<br/>attempts += 1, attemptAt = now → COMMIT"] --> B
B["Envois hors transaction<br/>1 thread virtuel par notif, 32 envois FCM concurrents max<br/>Neo4j : lecture des fcmTokens · FCM HTTP v1 : 1 requête par token"] --> C
C["TX2 — persistOutcome() par tranches de 500<br/>1 rapport par token + statut final (SUCCESS / ERROR / reste PENDING)"] --> D
D["Purge hors transaction<br/>suppression des tokens obsolètes dans Neo4j, par utilisateur"]
Points de conception :
- Réservation durable : la tentative est inscrite (
attempts,attemptAt) et committée avant tout envoi. Les autres instances sont ensuite exclues par la fenêtre de reprise de 5 min denextBatch(), et non par des verrous tenus pendant les envois. Un crash en cours d'envoi ne rejoue le lot qu'après cette fenêtre, tentative décomptée. - Limite de tentatives :
push-notif.max-attempts(défaut 5) ; à épuisement la notif bascule enERROR. - Classification transitoire / définitif fondée sur le code d'erreur applicatif FCM
(
FcmErrorCode, lu danserror.details[]) et non sur le seul statut HTTP. - Purge de token uniquement sur
UNREGISTEREDetSENDER_ID_MISMATCH, codes qui désignent le token lui-même. Un 403PERMISSION_DENIED(projet Firebase mal configuré) ne purge rien. - Livraison at-least-once : un doublon reste possible si le process meurt entre l'envoi et TX2.
| Dépendance | Usage |
|---|---|
| PostgreSQL | schéma push_manager : file push_notifs, rapports push_notif_reports. Migrations Flyway (src/main/resources/db/migration), appliquées au démarrage. |
| Neo4j 3.x | lecture / mise à jour de (:User)-[:PREFERS]->(:UserAppConf).fcmTokens, via l'API HTTP transactionnelle (/db/data/transaction/commit) — Bolt indisponible. |
| FCM HTTP v1 | envoi, authentifié par un compte de service Google (OAuth2). |
| MongoDB | collection logpushnotifs — amorce de journalisation (LogPushNotifServiceImpl), pas encore branchée sur le batch. |
| Zookeeper | enregistrement du service pour Traefik, désactivé par défaut (REGISTER_ZOOKEEPER). |
- Docker
- Java 25 —
./build.shfait tourner Maven dans l'imagemvn-java25-node22, donc rien à installer pour ce chemin ; pour unmvnlocal, vérifier queJAVA_HOMEpointe bien sur un JDK 25 - edifice-cli — installé par
./build.sh init
Tout est surchargeable par variables d'environnement (voir src/main/resources/application.properties).
| Variable | Défaut | Rôle |
|---|---|---|
QUARKUS_HTTP_PORT |
8101 |
port HTTP |
DB_URL / DB_USERNAME / DB_PASSWORD |
jdbc:postgresql://localhost:5432/ong?currentSchema=push_manager / web-education / We_1234 |
PostgreSQL |
MIGRATE_AT_STARTUP |
true |
migrations Flyway au démarrage |
NEO4J_URL |
http://localhost:7474 |
endpoint HTTP Neo4j |
NEO4J_USERNAME / NEO4J_PASSWORD |
vides | auth Neo4j (vides en local, auth désactivée) |
FCM_PROJECT_ID |
vide | projet Firebase cible |
FCM_ISS |
vide | client_email du compte de service |
FCM_KEY |
vide | clé privée PEM (acceptée sur une seule ligne, cf. FirebaseConfig.normalizePem) |
FCM_SCOPE |
https://www.googleapis.com/auth/firebase.messaging |
scope OAuth2 |
PUSH_NOTIF_MAX_ATTEMPTS |
5 |
tentatives avant bascule en ERROR |
PLATFORM_ID / PLATFORM_NAME / PLATFORM_TIMEZONE |
— / local / Europe/Paris |
identification de la plateforme |
REGISTER_ZOOKEEPER / ZOOKEEPER_HOSTS |
false / zookeeper1:2181 |
enregistrement Zookeeper |
SERVICE_NAME / SERVICE_PATH_PREFIX |
push-manager / /push-manager |
identité annoncée à Traefik |
MONGDB_USERNAME / MONGDB_PASSWORD |
vides | MongoDB (noms tels quels dans la config) |
Sans FCM_PROJECT_ID / FCM_ISS / FCM_KEY valides, le service démarre mais tout envoi échoue.
Démarrer les dépendances (le compose.yaml fournit PostgreSQL, MongoDB, Adminer sur
localhost:48080 et Zookeeper) :
docker compose up -d postgres mongo adminer
compose.yamlne fournit pas Neo4j : pointerNEO4J_URLsur une instance existante.
Puis lancer le service en mode dev (rechargement à chaud) :
mvn quarkus:dev # ou : quarkus devEn mode dev : Swagger UI sur /q/swagger-ui.
./build.sh init clean install # init (+ edifice-cli), clean, install
./build.sh test # tests Maven dans le conteneur de build
./build.sh publish # publication du jar sur le Nexus ODE
./build.sh image # image conteneur JVM (tag <version>-jvm)
./build.sh imageNative # image native (tag <version>-native)La CI (Jenkinsfile) enchaîne ./build.sh init, puis ./edifice install, publish et image.
Tests Java (src/test/java, 20 tests) :
JAVA_HOME=<jdk-25> mvn test # ou ./build.sh testIls tournent sans dépendance à installer : Dev Services démarre un PostgreSQL jetable,
FakeNeo4jServer remplace Neo4j (vrai serveur HTTP sur port aléatoire) et FakeFcmSender remplace
l'envoi FCM. Le scheduler est désactivé (quarkus.scheduler.enabled=false) pour piloter
pollAndSend() de façon déterministe. Couverture : migrations Flyway, client Neo4j (avec et sans
auth), extraction du code d'erreur FCM, et le cycle complet du batch (succès, purge de token,
reprise, épuisement des tentatives).
Tests k6 (src/test/js) : scénario d'intégration it/scenarios/health.ts et amorce de test de
charge loadtest/scenarios, publiés comme paquet @edifice.io/push-manager-tests.
| Endpoint | Description |
|---|---|
/q/health, /q/health/live, /q/health/ready |
SmallRye Health (dont MemoryHealthCheck en readiness, seuil heap 90 %) |
/q/metrics |
métriques Prometheus (Micrometer) |
/q/openapi, /q/swagger-ui |
OpenAPI ; Swagger UI en mode dev uniquement |