Configuration Enhancers

A ConfigurationEnhancer is a hook that runs once during ApplicationConfigurer#build(), after all explicit registrations have been collected but before the AxonConfiguration is built. Enhancers provide a structured way to add or conditionally adjust configuration without polluting the main application setup code.

The framework uses the ConfigurationEnhancer extensively. It is the mechanism that attaches default infrastructure components, for example the MessagingConfigurationDefaults that is attached when a MessagingConfigurer is built, or the EventSourcingConfigurationDefaults that include the EventStore and EventStorageEngine when the EventSourcingConfigurer is utilized. Furthermore, it is used to add cross-cutting concerns to the framework, like multi-tenancy and distributed tracing support, which both have their own ConfigurationEnhancer extending the behavior.

Writing your own ConfigurationEnhancer is a mechanism you can use to expand your application in the same way, whenever the need arises.

How enhancers work

Enhancers implement a single method, shown here via a minimal implementation:

import org.axonframework.common.configuration.ComponentRegistry;
import org.axonframework.common.configuration.ConfigurationEnhancer;

class MinimalConfigurationEnhancer implements ConfigurationEnhancer {

    @Override
    public void enhance(ComponentRegistry registry) {
        // register, decorate, or otherwise adjust components here
    }

    @Override
    public int order() {
        return 0;
    }
}

When ApplicationConfigurer#build() is called, all registered enhancers are sorted by their order() and invoked in that order. Each enhancer receives the fully populated ComponentRegistry, on which it can:

  • Register new components with registerComponent

  • Register decorators with registerDecorator

  • Register nested modules with registerModule

  • Disable other enhancers with disableEnhancer

Because enhancers are invoked after all application-level registrations, they can inspect what was registered and react accordingly. An enhancer that provides a sensible default should use registerIfNotPresent so that an explicitly registered component is never overridden.

Enhancer ordering

The order() method controls the sequence in which enhancers run. Lower values execute first. The default is 0.

This order is extremely important, since each enhancer runs against whatever the ones before it have already left in the ComponentRegistry. A preceding enhancer therefore directly impacts what a subsequent enhancer sees, and consequently how it behaves, including whether an inspect-then-register check finds the component it is looking for. Getting the order wrong can silently change that outcome.

For a rough ballpark on which order to use, consult the following table:

Value range Intended use

Negative

Enhancers that provide sensible defaults, which should be applied before any user-defined enhancement

0 (default)

Standard user enhancements

Positive

Enhancers that should override or complement earlier ones, for example a tracing enhancer that depends on an interceptor registered by another enhancer

With this input on enhancer ordering under our belt, we can take a look at an example ConfigurationEnhancer that provides the default CommandBus to an application:

import org.axonframework.common.configuration.ComponentRegistry;
import org.axonframework.common.configuration.ConfigurationEnhancer;
import org.axonframework.messaging.commandhandling.CommandBus;
import org.axonframework.messaging.commandhandling.SimpleCommandBus;
import org.axonframework.messaging.core.unitofwork.UnitOfWorkFactory;

class DefaultCommandBusEnhancer implements ConfigurationEnhancer {

    @Override
    public void enhance(ComponentRegistry registry) {
        registry.registerIfNotPresent(
                CommandBus.class,
                config -> new SimpleCommandBus(config.getComponent(UnitOfWorkFactory.class))
        );
    }

    @Override
    public int order() {
        return -100; // run early so users can override
    }
}

Registering an enhancer

Enhancers are registered on the ComponentRegistry:

import org.axonframework.messaging.core.configuration.MessagingConfigurer;

class RegisterEnhancerExample {

    void register() {
        MessagingConfigurer.create()
                           .componentRegistry(registry -> registry.registerEnhancer(new TracingEnhancer()));
    }
}

Or using a lambda when the enhancer needs no state:

import org.axonframework.messaging.core.ClassBasedMessageTypeResolver;
import org.axonframework.messaging.core.MessageTypeResolver;
import org.axonframework.messaging.core.configuration.MessagingConfigurer;

class LambdaEnhancerExample {

    void register() {
        MessagingConfigurer.create()
                           .componentRegistry(registry -> registry.registerEnhancer(cr -> {
                               cr.registerComponent(
                                       MessageTypeResolver.class,
                                       config -> new ClassBasedMessageTypeResolver()
                               );
                           }));
    }
}

When running with the Spring Boot Starter, an enhancer does not have to go through registerEnhancer at all: simply declare it as a Spring bean, and Axon picks it up automatically. See Customizing Axon’s configuration through Spring for how the SpringComponentRegistry discovers ConfigurationEnhancer beans in the Application Context.

Conditional enhancement

The key benefit of enhancers over direct registration is the ability to inspect the registry before making changes. Use ComponentRegistry#hasComponent(Class<?>) to make registration conditional:

import org.axonframework.common.configuration.ComponentRegistry;
import org.axonframework.common.configuration.ConfigurationEnhancer;
import org.axonframework.messaging.commandhandling.CommandBus;
import org.axonframework.messaging.queryhandling.QueryBus;
import org.axonframework.messaging.tracing.SpanFactory;

class TracingEnhancer implements ConfigurationEnhancer {

    @Override
    public void enhance(ComponentRegistry registry) {
        if (registry.hasComponent(CommandBus.class)) {
            registry.registerDecorator(
                    CommandBus.class,
                    100,
                    (config, name, delegate) -> new TracingCommandBusDecorator(
                            delegate, config.getComponent(SpanFactory.class)
                    )
            );
        }
        if (registry.hasComponent(QueryBus.class)) {
            registry.registerDecorator(
                    QueryBus.class,
                    100,
                    (config, name, delegate) -> new TracingQueryBusDecorator(
                            delegate, config.getComponent(SpanFactory.class)
                    )
            );
        }
    }
}

This pattern is used throughout Axon Framework to apply cross-cutting concerns such as tracing and monitoring only when the relevant infrastructure is present.

Because such a conditional check only sees what has been registered so far, its outcome depends on the order in which enhancers run: the same conditional enhancer can find (or fail to find) the component it checks for depending on whether it runs before or after the enhancer that registers it. Give a conditional enhancer an explicit order() value whenever it needs to run after the enhancer it depends on.

Automatic discovery via ServiceLoader

Axon Framework discovers ConfigurationEnhancer implementations automatically through the Java ServiceLoader mechanism. If you add an entry to META-INF/services/org.axonframework.common.configuration.ConfigurationEnhancer in your JAR, your enhancer is registered without any explicit call to registerEnhancer. This is how Axon’s own modules register their defaults.

To list your enhancer for automatic discovery:

# META-INF/services/org.axonframework.common.configuration.ConfigurationEnhancer
com.example.myapp.TracingEnhancer

Disabling enhancers

When a specific enhancer’s behavior is not desired, you can disable it from within another enhancer or during application setup:

import org.axonframework.messaging.core.configuration.MessagingConfigurer;

class DisableEnhancerByClassExample {

    void configure() {
        MessagingConfigurer.create()
                           .componentRegistry(cr -> cr.disableEnhancer(SomeUnwantedEnhancer.class));
    }
}

To disable an enhancer from another module that you cannot reference directly (for example, when the class is internal), use the fully qualified class name:

import org.axonframework.common.configuration.ComponentRegistry;

class DisableEnhancerByNameExample {

    void configure(ComponentRegistry registry) {
        registry.disableEnhancer("org.axonframework.tracing.SomeTracingEnhancer");
    }
}

To disable all ServiceLoader-discovered enhancers at once, which may be desired for ahead-of-time (AOT) compilation support, you can do the following:

import org.axonframework.common.configuration.ComponentRegistry;
import org.axonframework.messaging.core.configuration.MessagingConfigurer;

class DisableEnhancerScanningExample {

    void configure() {
        MessagingConfigurer.create()
                           .componentRegistry(ComponentRegistry::disableEnhancerScanning);
    }
}

Disabling enhancer scanning removes framework defaults that are required for correct operation. Prefer disableEnhancer(Class) to target only the specific enhancer you want to remove.