Knock repose sur un principe simple : implémenter MicroProfile Health 4.0 avec le seul JDK et les specs Jakarta. Aucune bibliothèque tierce, aucune mécanique lourde en réflexion, aucun pool de threads plateforme. Cette page documente les choix d’implémentation qui maintiennent cette discipline.

Architecture des modules

Module Responsabilité

knock-mp-health-api

Fork Vidocq de l’API MicroProfile Health, reconditionné avec un module-info.java (voir le contournement JPMS plus bas).

knock-api

SPI Knock : ProbeType, HealthCheckRegistry, métadonnées Knock. Réexporte les types de la spec.

knock-core

Runtime autonome : registre, agrégateur, sérialiseur JSON-P, constructeur de réponse, façade KnockHealthService. Java SE pur.

knock-cdi-vauban

Build Compatible Extension CDI + auto-enregistrement sur Vauban.

knock-jaxrs

Ressource Jakarta REST exposant /health* (déployée sur Cassini).

knock-tck

Lanceur du TCK officiel MicroProfile Health 4.0.

Flux d’exécution

Diagram
  1. HealthCheckRegistry.getChecks(ProbeType) renvoie les contrôles de la sonde demandée.

  2. Chaque HealthCheck.call() s’exécute sur son propre thread virtuel.

  3. KnockAggregator réduit les réponses en un HealthSnapshot (règles spec §3.2).

  4. KnockJsonSerializer rend l’instantané via Jakarta JSON-P.

  5. KnockHealthService encapsule le résultat dans un HealthReport (statut HTTP + charge utile).

Classes du cœur

Classe Rôle

KnockHealthCheckRegistry (interne)

Implémentation de HealthCheckRegistry adossée à un ConcurrentHashMap. Pas de synchronized.

KnockAggregator (interne)

Applique la règle d’agrégation §3.2 et replie les exceptions en contrôles DOWN.

KnockJsonSerializer (interne)

Construit la réponse avec Jakarta JSON-P (Json.createObjectBuilder()), sans bibliothèque JSON tierce.

KnockHealthCheckResponseBuilder (interne)

Implémentation de HealthCheckResponseBuilder ; type les entrées data (String, Long, Boolean, Integer, sinon String.valueOf).

KnockHealthCheckResponseProvider (interne)

Implémentation de la SPI HealthCheckResponseProvider, enregistrée via provides/META-INF/services.

HealthSnapshot (record interne)

Résultat d’agrégation immuable : type de sonde, statut global, liste de contrôles.

KnockHealthService (runtime)

Façade publique liant registre + agrégateur + sérialiseur.

HealthReport (record runtime)

Enveloppe de réponse HTTP : code de statut + JSON sérialisé.

Découverte CDI

knock-cdi-vauban fournit deux pièces internes :

  • HealthCheckCdiExtension — une Build Compatible Extension validant la règle du qualificateur de sonde unique au moment du build (spec §4.2) ;

  • HealthCheckRegistrar — un bean CDI qui, sur @Initialized(ApplicationScoped.class), enregistre chaque HealthCheck découvert dans le registre (spec §4.1).

La découverte utilise le pipeline BCE de Vauban — pas de balayage du classpath à l’exécution, pas de setAccessible(true).

Ressource JAX-RS

KnockHealthResource est annotée @Path("/health") et expose les quatre endpoints. Chaque méthode associe son chemin à un ProbeType, appelle KnockHealthService.report(…​) et renvoie une Response avec le statut HTTP de l’instantané. La ressource n’importe que jakarta.ws.rs — jamais un paquet interne de Cassini.

Modèle de threads

Toute l’exécution des contrôles passe par Executors.newVirtualThreadPerTaskExecutor() (JEP 444). Conséquences :

  • un contrôle lent ne bloque jamais le reste d’un groupe de sondes ;

  • pas de pool de threads plateforme, pas de file bornée, pas de ThreadLocal ;

  • l’état partagé (le registre) utilise exclusivement ConcurrentHashMap.

Contournement JPMS (ADR-001)

Le jar amont de l’API MicroProfile Health est livré sans module-info.class, ce qui casse le JPMS strict et jlink. Knock le fork sous knock-mp-health-api et le reconstruit avec un descripteur de module en bonne et due forme. Pour empêcher ce descripteur d’interférer avec le traitement des annotations, le module-info.java réside dans src/main/module-info/ et est compilé dans une phase dédiée à prepare-package. La SPI est enregistrée deux fois — via provides JPMS et META-INF/services — afin que l’implémentation se résolve sur le module path comme sur le classpath. Justification complète : docs/adr/ADR-001-jpms-workaround-microprofile-health.md.

Compatibilité AOT

Pas de génération de proxy dynamique, pas de setAccessible(true), pas de réflexion à chaud dans le chemin de requête. Knock est compatible native-image et fonctionne dans une image jlink minimale.

Sources