Skip to content
Reference

Key Interfaces

Key Interfaces

All public SPI interfaces with their method signatures and contracts.


FailoverHandler\<T>

Central coordinator. The default implementation chain is AdvancedFailoverHandler → ScatterGatherFailoverHandler → DefaultFailoverHandler (see How It Works — Handler Chain and Execution Order).

Every operation carries the intercepted @NonNull Method so the outermost handler can tag metrics per method (e.g. CountryService#findAll):

public interface FailoverHandler<T> {
    T store(@NonNull Failover failover, @NonNull Method method, List<Object> args, T payload);
    T recover(@NonNull Failover failover, @NonNull Method method, List<Object> args, Class<T> clazz, Throwable throwable);

    default List<T> recoverAll(@NonNull Failover failover, @NonNull Method method, List<Object> args, Class<T> clazz, Throwable throwable) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    void clean();
}
Method Called when Side effects
store Upstream succeeds Writes entry to FailoverStore
recover Upstream throws Reads from FailoverStore, checks expiry
recoverAll Recover-all / scatter slice path Reads every entry for the referential
clean Scheduled cleanup Deletes expired entries via cleanByExpiry

Implementing a custom handler

Handlers that do not need the Method should extend AbstractFailoverHandler, which bridges the method-aware contract to clean protected method-less operations — you implement only store(failover, args, payload) / recover(failover, args, clazz, throwable). Decorators that need the method (to tag per-method metrics) implement FailoverHandler directly. See ADR 52.


FailoverStore\<T>

Persistence contract. Implementations must return a defensive copy from find().

public interface FailoverStore<T> {
    void store(ReferentialPayload<T> referentialPayload);
    void delete(ReferentialPayload<T> referentialPayload);
    Optional<ReferentialPayload<T>> find(String name, String key);
    void cleanByExpiry(Instant expiry);
}

Defensive copy in find()

find() must return a copy of the stored object, not a live reference. Callers mutate upToDate and asOf on the returned object without affecting the persisted data. This is guaranteed for all built-in stores. Custom stores must ensure this themselves.


KeyGenerator

Derives a raw string key from method arguments.

public interface KeyGenerator {
    String key(Failover failover, List<Object> args);
}

The returned string is then wrapped by FailoverKeyGenerator (Layer 2), which prefixes it with effectiveName and hashes to a UUID.


ExpiryPolicy\<T>

Computes and checks the expiry timestamp for a stored entry.

public interface ExpiryPolicy<T> {
    Instant computeExpiry(Failover failover);
    boolean isExpired(Failover failover, ReferentialPayload<T> payload);
}

computeExpiry is called at store time. isExpired is called at recover time.


PayloadEnricher\<T>

Enriches the ReferentialPayload envelope on both paths.

public interface PayloadEnricher<T> {
    ReferentialPayload<T> enrichOnStore(
        Failover failover, Class<T> clazz, ReferentialPayload<T> payload);

    ReferentialPayload<T> enrichOnRecover(
        Failover failover, Class<T> clazz,
        @Nullable ReferentialPayload<T> payload, Throwable cause);
}

Default sets upToDate=true/false and asOf on the Referential / ReferentialAware object.


PayloadSplitter\<T, R>

Splits a composite payload into per-entity slices for scatter/gather operations.

public interface PayloadSplitter<T, R> {
    List<StoreContext<R>> splitOnStore(StoreContext<T> context);
    List<RecoverContext<R>> splitOnRecover(RecoverContext<T> context);
    RecoverContext<T> merge(List<RecoverContext<R>> contexts);
}
Parameter Meaning
T Composite type (List<Country>)
R Slice type (Country)

Prefer the base classes over the raw interface

For a List<T> composite, extend AbstractListPayloadSplitter<T> and implement only keyArgsForSlice (the slice key); for id-based methods also override keyArgsToRecover. For a non-List composite (a wrapper object) extend AbstractPayloadSplitter<T, R>. Both supply the splitOnStore / splitOnRecover / merge plumbing so you only write the domain-specific hooks. Implement PayloadSplitter directly only when you need full control. See the Payload Splitter How-to.


RecoveredPayloadHandler

Last-chance hook when recovery returns null.

public interface RecoveredPayloadHandler {
    <T> T handle(Failover failover, Class<T> clazz, Throwable cause);
}

Called by AdvancedFailoverHandler when the inner handler returns null and exception-policy: never_throw.


ContextPropagator

Propagates thread-local context across virtual-thread executor boundaries in scatter/gather.

public interface ContextPropagator {
    Supplier<Runnable> wrap(Supplier<Runnable> task);
    static ContextPropagator noOp() { ... }
}

Multiple beans are composed by auto-configuration into a CompositeContextPropagator.


TenantResolver

Returns the current tenant ID for multi-tenant store routing.

public interface TenantResolver {
    String resolve();
}

Called on every store/find/delete/cleanByExpiry operation when multi-tenant mode is enabled.


TenantStoreFactory\<T>

Creates a FailoverStore<T> for a given tenant. Required for the SCHEMA multi-tenant strategy.

public interface TenantStoreFactory<T> {
    FailoverStore<T> create(String tenantId);
}

Next Steps