Class TenantScopedCache<C>

java.lang.Object
io.axoniq.framework.messaging.multitenancy.api.TenantScopedCache<C>
Type Parameters:
C - the type of per-tenant component cached
All Implemented Interfaces:
MultiTenantAwareComponent, DescribableComponent

@Internal public class TenantScopedCache<C> extends Object implements MultiTenantAwareComponent
A cache of one infrastructure component per tenant that creates each component lazily on first use and evicts it when its tenant is removed.

As a MultiTenantAwareComponent it follows the tenant provider: registerTenant(TenantDescriptor) and registerAndStartTenant(TenantDescriptor) return a Registration whose cancellation evicts the tenant's cached component, so a re-added tenant rebuilds a fresh one.

Eviction only drops this cache's reference to the component. That suits a component whose lifecycle is owned elsewhere, such as one bound to a connection the connection manager closes. A component that has to be released on eviction needs that release wired in, which DefaultTenantComponentProvider does to destroy the instances it hands to message handlers. Such a release runs exactly once per component, whether its registration was cancelled, superseded, or replaced while the component was being created.

Only a registered tenant gets a component. Requesting the component of a tenant that was never registered, or whose registration was cancelled, is rejected with a TenantNotResolvedException. A removed tenant would otherwise get a fresh component that nothing evicts again, since the registration that would have evicted it is already cancelled, and for a backend-bound component that means holding a connection to a context that was just dropped.

Components are cached per registration rather than per tenant, so one created concurrently with the removal of its tenant is discarded instead of outliving that registration.

Component creation runs inside the cache update. The factory must therefore not register or unregister tenants on, nor request components from, this cache on the creating thread.

Since:
5.3.0
Author:
Jakob Hatzl, Laura Devriendt
  • Constructor Details

    • TenantScopedCache

      public TenantScopedCache(Function<TenantDescriptor,C> componentFactory, String owner)
      Constructs a TenantScopedCache building its components with the given componentFactory, dropping its reference to a component on eviction without releasing it any further.
      Parameters:
      componentFactory - the factory building a tenant's component, invoked once per tenant on first access
      owner - how this cache names its owner in the TenantNotResolvedException raised for a tenant that is not registered, so the component that rejected the tenant can be told from the others caching per tenant in the same call
      Throws:
      NullPointerException - if any of the given arguments is null
    • TenantScopedCache

      public TenantScopedCache(Function<TenantDescriptor,C> componentFactory, BiConsumer<TenantDescriptor,C> onEviction, String owner)
      Constructs a TenantScopedCache building its components with the given componentFactory and handing every component it evicts to the given onEviction.
      Parameters:
      componentFactory - the factory building a tenant's component, invoked once per tenant on first access
      onEviction - invoked with a tenant and the component evicted for it, exactly once per evicted component, so its owner can release it
      owner - how this cache names its owner in the TenantNotResolvedException raised for a tenant that is not registered
      Throws:
      NullPointerException - if any of the given arguments is null
  • Method Details

    • componentFor

      public C componentFor(TenantDescriptor tenant)
      Returns the component of the given tenant, creating and caching it on first access.
      Parameters:
      tenant - the tenant to return the component for
      Returns:
      the tenant's cached component
      Throws:
      TenantNotResolvedException - if the given tenant is not registered
    • registerTenant

      public Registration registerTenant(TenantDescriptor tenantDescriptor)
      Description copied from interface: MultiTenantAwareComponent
      Registers the given tenantDescriptor as a known tenant with this multi-tenant aware component.

      The caller must retain the returned Registration and cancel it when the tenant is removed, since releasing the component's per-tenant resources rides on that cancellation.

      Specified by:
      registerTenant in interface MultiTenantAwareComponent
      Parameters:
      tenantDescriptor - The TenantDescriptor to register with this component.
      Returns:
      A Registration used to deregister the given tenantDescriptor.
    • registerAndStartTenant

      public Registration registerAndStartTenant(TenantDescriptor tenantDescriptor)
      Behaves identically to registerTenant(TenantDescriptor): components are created lazily on first use, so there is nothing to start eagerly.
      Specified by:
      registerAndStartTenant in interface MultiTenantAwareComponent
      Parameters:
      tenantDescriptor - the tenant to register with this cache
      Returns:
      a registration whose cancellation evicts the tenant's component
    • tenants

      public List<TenantDescriptor> tenants()
      Returns the tenants currently registered with this cache, whether their component was built yet or not.
      Returns:
      the tenants currently registered with this cache
    • 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.