Skip to content

Multi-tenancy

Booking is multi-tenant by default - but invisibly for single-site installs. Every resource, slot, category and booking is stamped with a tenant machine name; a shipped default tenant means simple sites never think about it.

flowchart LR
    REQ[Request] --> TC[TenantContext]
    TC --> R1[resolver A]
    TC --> R2[resolver B]
    R1 -->|tenant id| TC
    TC -->|first non-empty wins,<br/>else default| T[(Active tenant)]
    T --> DATA[All rows filtered / stamped by tenant]

How it works

  • Tenant is a config entity (yoyaku_tenant), one shipped at install: default.
  • TenantField::definition() provides the shared tenant base field every partitioned entity adds. A booking inherits its category's tenant.
  • TenantContext (booking.tenant_context) resolves the active tenant by consulting resolvers tagged booking.tenant_resolver in priority order, falling back to default.
  • TenantResolverInterface - implement and tag it to map your dimension (domain, group, site…) to a tenant id.

Adding a resolver

# my_module.services.yml
services:
  my_module.domain_tenant_resolver:
    class: Drupal\my_module\DomainTenantResolver
    arguments: ['@domain.negotiator']
    tags:
      - { name: booking.tenant_resolver, priority: 100 }
use Drupal\yoyaku\TenantResolverInterface;

final class DomainTenantResolver implements TenantResolverInterface {
  public function resolveTenantId(): ?string {
    // Return a tenant id, or NULL to defer to the next resolver.
  }
}

Note

Capacity is always counted per category id, so categories in different tenants are independent by construction. The tenant field additionally lets consumers scope listings and access per tenant.

Tenant-scoped resource types

A resource type (yoyaku_resource_type) is a hybrid catalogue, the same shape as a shared vocabulary with per-tenant additions. Its tenant property is either empty or a tenant id:

  • empty means the type is shared: it is offered in every tenant;
  • set means the type is private to that tenant and offered only there.

ResourceTypeCatalogue is the single place this resolves: the types available in a tenant are the shared types plus that tenant's own. The resource Type selector lists only available types, and a validation constraint rejects assigning a type from another tenant, so a resource never references a type outside its realm.

Manage types and tenants under Booking in the admin menu (Resource types, Tenants).

Constraints are enforced per tenant

Order constraints (the per-customer limits) always count within the order's tenant. Even an exclusivity group spanning resources of a shared type pools per tenant: a customer holding one booking of the group in one tenant is not blocked from booking it in another. The tenant is the isolation boundary, so bookings in one realm never interfere with another.