Tenant-Scoped Components
A multi-tenant application usually has its own tenant-scoped resources: a read-model store, a SQL datasource, or any service that must not mix data between tenants. A tenant-scoped component lets you register such a resource once and have the correct tenant’s instance injected into your message handling methods, so a handler never has to look up the tenant itself. The resource itself stays passive: you describe how it is built for a tenant, and the framework holds one instance per tenant and injects the matching one into the handler.
A tenant-scoped component is described by two types:
-
TenantComponentFactory<T>builds (and cleans up) the instance of your component for a single tenant. -
TenantComponentProvider<T>holds one instance of the component typeTper tenant, creating each instance lazily through the factory on first use.
Each provider is generic over a single component type. To expose several tenant-scoped types, register one provider per type. Each provider is matched to handler parameters by its component type.
Registering a tenant-scoped component
Implement a TenantComponentFactory describing how the component is built for a tenant, and register the provider obtained through TenantComponentProvider.withFactory.
-
Declarative - Configuration API
-
Autodetected - Spring Boot
Register the provider as a component on your MessagingConfigurer, passed in from the place it is created:
public void registerTenantComponents(MessagingConfigurer configurer) {
configurer.componentRegistry(registry ->
registry.registerComponent(
TenantComponentProvider.class, (1)
config -> TenantComponentProvider.withFactory(
CourseStatisticsStore.class, (2)
tenant -> new InMemoryCourseStatisticsStore(tenant.tenantId())
)
)
);
}
| 1 | Register the provider under the TenantComponentProvider type so it is discovered during parameter resolution. |
| 2 | The component type, used to match the provider against handler parameters. The lambda is the TenantComponentFactory building the tenant’s instance. |
Expose the provider as a bean of type TenantComponentProvider:
@Bean
public TenantComponentProvider<CourseStatisticsStore> courseStatisticsStoreProvider() { (1)
return TenantComponentProvider.withFactory(
CourseStatisticsStore.class, (2)
tenant -> new InMemoryCourseStatisticsStore(tenant.tenantId())
);
}
| 1 | The bean type is TenantComponentProvider, which is how it is discovered during parameter resolution. |
| 2 | The component type, used to match the provider against handler parameters. The lambda is the TenantComponentFactory building the tenant’s instance. |
Message handlers can then declare the component type as a parameter.
The university demo builds its providers in TenantComponents and registers them from the UniversityConfiguration of its declarative and Spring Boot variants.
|
Injecting the tenant’s instance into a handler
Declare an @TenantScoped-annotated parameter of the component type on a message handling method.
During handling, the parameter is resolved to the instance belonging to the tenant of the message being handled.
@EventHandler
public void on(CourseCreated event, @TenantScoped CourseStatisticsStore store) { (1)
store.save(new CourseStatistics(event.courseId()));
}
| 1 | store is the CourseStatisticsStore instance of the current tenant.
The @TenantScoped annotation marks it as tenant scoped. |
The tenant whose instance is injected is the tenant resolved for the message being handled.
See Tenant resolution for how the tenant of a message is resolved and how to customize that resolution.
When no tenant can be resolved for the message, injecting the parameter fails with a TenantNotResolvedException, so a tenant-scoped resource is never created for an unresolved tenant.
| The demo injects tenant-scoped components into all three handler kinds: a command handler, an event handler and a query handler. |
Registering multiple component types
An application can register several tenant-scoped components, one provider per type.
Each provider is matched to a handler parameter by its component type.
Register a single provider per component type: registering two providers for the same type, or a parameter type that matches several providers, results in an AxonConfigurationException.
-
Declarative - Configuration API
-
Autodetected - Spring Boot
Register each provider under its own name:
configurer.componentRegistry(registry -> registry
.registerComponent(TenantComponentProvider.class, "courseStatisticsStore", (1)
config -> TenantComponentProvider.withFactory(
CourseStatisticsStore.class,
tenant -> new InMemoryCourseStatisticsStore(tenant.tenantId())))
.registerComponent(TenantComponentProvider.class, "reportingDataSource", (2)
config -> TenantComponentProvider.withFactory(
DataSource.class,
tenant -> buildDataSource(tenant)))
);
| 1 | A provider for the CourseStatisticsStore read model. |
| 2 | A second provider, for a per-tenant reporting DataSource, registered under a different name. |
Declare one bean per component type. The bean name distinguishes them:
@Bean
public TenantComponentProvider<CourseStatisticsStore> courseStatisticsStore() { (1)
return TenantComponentProvider.withFactory(
CourseStatisticsStore.class,
tenant -> new InMemoryCourseStatisticsStore(tenant.tenantId()));
}
@Bean
public TenantComponentProvider<DataSource> reportingDataSource() { (2)
return TenantComponentProvider.withFactory(
DataSource.class,
tenant -> buildDataSource(tenant));
}
| 1 | A provider for the CourseStatisticsStore read model. |
| 2 | A second provider, for a per-tenant reporting DataSource. |
Lifecycle and cleanup
Providers follow the tenant lifecycle automatically.
A tenant’s component is created the first time a handler for that tenant needs it, cached for later messages, and destroyed through TenantComponentFactory#destroy when the tenant is removed.
The default destroy closes components that implement AutoCloseable, which covers pooled datasource implementations and other connection-backed resources.
Override it to release other resources or to add custom shutdown logic.
TenantComponentFactory<CourseStatisticsStore> factory = new TenantComponentFactory<>() {
@Override
public CourseStatisticsStore create(TenantDescriptor tenant) {
return new JdbcCourseStatisticsStore(dataSourceFor(tenant));
}
@Override
public void destroy(TenantDescriptor tenant, CourseStatisticsStore store) {
store.flush();
store.close();
}
};