Vidocq Runtime borrows the extension idea popularised by Quarkus, but implements it with a deliberately simple, 100 % JDK mechanism: a ServiceLoader-discovered lifecycle interface on top of CDI Lite via Vauban and static code generation (Class-File API, JEP 484). This page defines the terms used throughout the documentation.
Extension
An extension is a Maven module that contributes to the runtime by implementing the io.vidocq.runtime.spi.VidocqExtension interface from vidocq-runtime-spi. It manifests as:
-
an implementation class registered with
provides io.vidocq.runtime.spi.VidocqExtension with …in itsmodule-info.java(or aMETA-INF/servicesentry on the class path); -
a set of lifecycle hooks invoked by the runtime core in a fixed order;
-
optionally an APT codegen companion (
vidocq-runtime-<x>-extension-codegen) that generates bean metadata at compile time instead of relying on runtime reflection.
Reflection-heavy work is pushed to compile time through APT and the Class-File API; at runtime the extension only wires already-generated artifacts into the lifecycle.
The VidocqExtension lifecycle
VidocqExtension declares seven methods. Two identify and order the extension:
-
name()— unique extension name (e.g.rest-cassini); -
priority()— execution order, lower runs first (default1000).
Four are lifecycle hooks, all with empty default implementations so an extension overrides only what it needs:
-
configure(VidocqConfiguration)— called before CDI boot. The extension reads its configuration keys (config.property("vidocq.rest.context-path", "/"),config.portFor(name, defaultPort), …). -
beforeStart(VaubanContainerBuilder)— enrich the Vauban container builder before the container exists: add synthetic beans, packages or interceptors. -
onStart(ExtensionContext)— the CDI container is up. The extension resolves beans, mounts HTTP handlers, opens listeners. -
onStop()— server shutdown; release resources. Extensions are stopped in reverse priority order.
The seventh method, configKeys(), lets an extension declare the vidocq. keys (or prefix. patterns) it consumes, so the runtime’s ConfigKeyAudit can warn at startup about configured keys that no extension reads.
ExtensionContext
ExtensionContext is handed to onStart and gives the extension:
-
container()— the initialized VaubanVaubanContainer; -
beanManager()— shortcut to the CDIBeanManager; -
configuration()— the legacyVidocqConfiguration(string properties); -
config()— the typedVidocqConfigAPI (Optional-returninggetValue(key, type), aligned with MicroProfile Config concepts).
Discovery and ordering
There is no build-step graph and no bytecode recording: discovery is plain java.util.ServiceLoader. At boot, ExtensionLoader loads every VidocqExtension visible on the module path, sorts the list by ascending priority(), then VidocqBootstrap drives each phase across the sorted list — configure, then beforeStart, then container start, then onStart; onStop walks the list backwards.
Priorities express real ordering constraints. In the HTTP stack, for example, ChappeEngineExtension (priority 100) prepares the web server engine before contributing extensions such as CassiniExtension (priority 500) mount their handlers, and ChappeServerBootstrap (priority 10 000) opens the listeners at the very end of the chain.
Notable differences from Quarkus
| Aspect | Quarkus | Vidocq Runtime |
|---|---|---|
Extension model |
Build-step graph + bytecode recorders |
|
Runtime CDI |
ArC (custom) |
Vauban (CDI 4.1 Lite, TCK 774/774) |
Bytecode generation |
Gizmo (ASM) |
JDK Class-File API (JEP 484) |
HTTP transport |
Vert.x |
Chappe on virtual threads |
JSON |
Jackson |
Champollion (JSON-B 3.0) |
Persistence |
Hibernate ORM/Reactive |
Mansart (Jakarta Data 1.0 + JDBC) |
Minimum JDK |
17 |
25 (LTS) |
Native Java Modules |
No |
Yes — |
Composition of the six bricks
Vidocq Runtime invents no runtime; it composes the six foundational bricks through the same lifecycle. The vidocq-runtime-cassini-rest-extension, for example:
-
reads
vidocq.rest.context-pathandvidocq.rest.listenerinconfigure; -
in
onStart, resolves the JAX-RSApplicationbean from the Vauban container, then mounts the Cassini dispatcher (whose routing was generated statically at compile time) on a mount point of the Chappe server contributed byvidocq-runtime-chappe-webserver-extension; -
relies on
priority()so the Chappe engine exists before the mount and the listeners open after it.
This shared lifecycle grammar keeps the ecosystem homogeneous while every brick remains self-contained in its own repo.