java.lang.Object
io.axoniq.framework.messaging.multitenancy.api.TenantRouter
All Implemented Interfaces:
DescribableComponent

@Internal public class TenantRouter extends Object implements DescribableComponent
Decides which tenant an operation is routed to, wrapping a TenantResolver to resolve a Message (or the ProcessingContext carrying it) to one of the known tenants, and yielding an Optional instead of throwing when no known tenant matches.

Registered as a component, so every tenant-routing component decides the tenant of a message the same way, against one and the same set of known tenants.

The tenant may originate either from the ProcessingContext resource or from the message itself, depending on how the message was dispatched and where in the handling chain resolution happens. Resolution only ever yields a tenant present in the configured TenantDescriptors, so a stale or unknown tenant never routes.

Since:
5.3.0
Author:
Jan Galinski, Laura Devriendt
  • Constructor Details

    • TenantRouter

      public TenantRouter(TenantResolver tenantResolver, TenantDescriptors tenantDescriptors)
      Creates a TenantRouter wrapping the given tenantResolver and resolving against the given tenantDescriptors.
      Parameters:
      tenantResolver - the TenantResolver to wrap
      tenantDescriptors - the known tenants to resolve against
  • Method Details

    • resolveFromContext

      public Optional<TenantDescriptor> resolveFromContext(@Nullable ProcessingContext context)
      Resolves the tenant carried by the given context, taken from its tenant resource when present, otherwise from the message the context carries.

      A tenant resource that is present decides on its own. It is never overruled by the tenant named in the message, because that would let message metadata redirect an operation to another tenant's store whenever the resource names a tenant this application does not know. Only an absent resource falls back to the message.

      Parameters:
      context - the processing context to resolve the tenant from
      Returns:
      the known tenant of the context, or empty when none is available
      Throws:
      TenantNotResolvedException - if the context carries a tenant that is not a known tenant
    • resolveFromMessage

      public Optional<TenantDescriptor> resolveFromMessage(Message message)
      Resolves the tenant of the given message.

      Returns Optional.empty() when the message cannot be attributed to a known tenant, so a message naming a tenant this application does not know never routes.

      Parameters:
      message - the message to resolve the tenant of
      Returns:
      the known tenant of the message, or empty when none matches
    • resolveSharedTenant

      public Optional<TenantDescriptor> resolveSharedTenant(Collection<? extends Message> messages)
      Resolves the single tenant shared by all given messages.

      Returns the resolved tenant when every message resolves to it. Returns Optional.empty() when the batch is empty or when any message cannot be attributed to a known tenant, so an unresolved message is never silently dropped.

      Parameters:
      messages - the messages to resolve a single tenant from
      Returns:
      the tenant shared by all messages, or empty when it cannot be determined
      Throws:
      TenantNotResolvedException - if the messages resolve to more than one tenant
    • attachTenant

      public Message attachTenant(Message message, TenantDescriptor tenant)
      Attaches the given tenant to the given message, the inverse of resolveFromMessage(Message), delegating to the wrapped TenantResolver.
      Parameters:
      message - the message to attach the given tenant to
      tenant - the tenant to attach to the given message
      Returns:
      a copy of the given message carrying the given tenant
    • describeTo

      public void describeTo(ComponentDescriptor descriptor)
      Description copied from interface: DescribableComponent
      Describe the properties of this DescribableComponent with the given descriptor.

      Components should call the appropriate describeProperty methods on the descriptor to register their properties. The descriptor is responsible for determining how these properties are formatted and structured in the final output.

      Best Practices: As a general rule, all relevant fields of a DescribableComponent implementation should be described in this method. However, developers have discretion to include only the fields that make sense in the context. Not every field may be meaningful for description purposes, especially internal implementation details. Furthermore, components might want to expose different information based on their current state. The final decision on what properties to include lies with the person implementing the describeTo method, who should focus on providing information that is useful for understanding the component's configuration and state.

      Example implementation:

       public void describeTo(ComponentDescriptor descriptor) {
           descriptor.describeProperty("name", this.name);
           descriptor.describeProperty("enabled", this.enabled);
           descriptor.describeProperty("configuration", this.configuration); // A nested component
           descriptor.describeProperty("handlers", this.eventHandlers);      // A collection
       }
       
      Specified by:
      describeTo in interface DescribableComponent
      Parameters:
      descriptor - The component descriptor to describe this DescribableComponentn its properties in.