vidocq-runtime-spi exposes the interfaces an extension implements to contribute to the runtime. It is the only module a user-side extension must declare in its Java Modules requires.

Coordinates

Artefact

io.vidocq.runtime:vidocq-runtime-spi:0.3.0

Java module

io.vidocq.runtime.spi

Source

vidocq-runtime-spi/src/main/java/io/vidocq/runtime/spi/

Public surface

List indexed from the existing sources:

Type Role

VidocqExtension

The extension point — lifecycle interface with name(), priority() (lower runs first, default 1000), configure(VidocqConfiguration), beforeStart(VaubanContainerBuilder), onStart(ExtensionContext), onStop() and configKeys(). Discovered via ServiceLoader.

ExtensionContext

Handed to onStart — container() (Vauban), beanManager(), configuration() (legacy string API), config() (typed API).

VidocqConfiguration

String-property view passed to configure — property(key) returns Optional<String>, plus property(key, default) and portFor(extensionName, defaultPort).

config.VidocqConfig

Typed configuration API aligned with MicroProfile Config concepts — getValue(key, type) returns Optional<T>, getValue(key, type, default), getValues(key, elementType).

config.ConfigSource / config.ConfigSourceProvider

Pluggable configuration sources (analogous to MicroProfile), also discovered via ServiceLoader.

config.Converter<T>

Typed conversion of configuration values.

VidocqApp / @VidocqMain

Application-side entry points — the class run inside the Vauban application layer by Vidocq.run(Class, args), and the zero-config IDE trampoline annotation.

Writing an extension — skeleton

An extension overrides only the hooks it needs; every lifecycle method has an empty default implementation.

package com.example.greeting;

import io.vidocq.runtime.spi.VidocqExtension;
import io.vidocq.runtime.spi.VidocqConfiguration;
import io.vidocq.runtime.spi.ExtensionContext;

public class GreetingExtension implements VidocqExtension {

    private String prefix;

    @Override
    public String name() {
        return "greeting";
    }

    @Override
    public void configure(VidocqConfiguration config) {
        // called before CDI boot — read your keys
        this.prefix = config.property("vidocq.greeting.prefix", "Hello");
    }

    @Override
    public void onStart(ExtensionContext ctx) {
        // the CDI container is up — resolve beans, mount handlers
        Greeter greeter = ctx.container().select(Greeter.class);
        greeter.setPrefix(prefix);
    }

    @Override
    public java.util.Set<String> configKeys() {
        return java.util.Set.of("vidocq.greeting.prefix");
    }
}

Java Modules declaration in module-info.java:

module com.example.greeting {
    requires io.vidocq.runtime.spi;

    provides io.vidocq.runtime.spi.VidocqExtension
        with com.example.greeting.GreetingExtension;
}

That is all. At startup the runtime’s ExtensionLoader discovers the provides directive through java.util.ServiceLoader, sorts all extensions by ascending priority(), and VidocqBootstrap drives the lifecycle: configure → beforeStart → onStart, then onStop in reverse order at shutdown.

Ordering with priority()

Priorities express real ordering constraints between extensions. The built-in HTTP stack illustrates the idiom: the Chappe engine prepares the server at priority 100, contributors such as the Cassini REST extension mount their handlers around priority 500, and the listener bootstrap opens the sockets last at priority 10 000. Pick a value between the extensions you must run after and before; unrelated extensions can keep the default 1000.

Configuration keys audit

Declaring your keys in configKeys() is optional but recommended: the runtime’s ConfigKeyAudit compares every configured vidocq. key against the union of declared keys and warns at startup about keys nobody consumes — the typical typo that otherwise silently keeps the default. Entries ending in declare a prefix ("vidocq.greeting.*").

Next steps