Tenancy
A request is for one tenant. Tenancy has three separate decisions, and Arc treats them separately:
| Decision | Who makes it |
|---|---|
| Selection: which tenant is this request for | Arc.Tenancy, configured below |
| Membership: may this caller use that tenant | You: authorize in a command, a query, an Arc.Policy or an Arc.Filter |
| Storage isolation: where the tenant’s data lives | Chronicle (the event store namespace) and cratis_arc_mongodb (the database) |
A resolver only selects. A header, a query string value or a host is chosen by the caller, so none of them is evidence that the caller belongs to the tenant. Check membership before trusting it.
Choose a resolver
Section titled “Choose a resolver” tenancy: [resolver: :header],The other resolvers take their options in the same keyword list, for example tenancy: [resolver: :subdomain, base_domain: "myapp.com"].
| Resolver | Reads | Options |
|---|---|---|
:header (default) |
a request header | header, default "x-cratis-tenant-id" |
:query |
a query string parameter | query_parameter, default "tenantId" |
:claim |
a claim of the authenticated principal | claim_type, default "tenant_id" |
:subdomain |
the label in front of a base domain | base_domain (required), falls back to the header |
:fixed, :development |
nothing: one tenant for every request | tenant_id, default "development" |
| a module | whatever it likes | implement Arc.Tenancy |
The :claim resolver reads the principal, so authentication must have run, and it is the one that ties selection to something the caller proved. The :subdomain resolver takes a tenant only from a host that is exactly one label in front of the base domain; everything else falls back to the header, which a client controls, so strip it at your ingress. The application refuses to start with a :subdomain resolver that has no usable base domain.
The result is ctx.tenant: a string, or nil. nil, an empty value and "Default" are all the default tenant.
What the tenant selects
Section titled “What the tenant selects”- Chronicle. A named tenant becomes the
namespace:of every Chronicle call made through the context, soChronicle.all(Author, ctx.chronicle)and the events a command appends go to that tenant’s namespace. The default tenant uses the default namespace. An explicitnamespace:inchronicle:wins. - MongoDB. Each tenant is a database of its own. See MongoDB.
- Identity. The identity details provider receives the tenant,
Arc.Identity.Context.tenant, andArc.Identity.Context.context/1turns it into anArc.Context. The showcase reports it in its details:
tenant: if(Arc.Tenancy.default?(tenant), do: "Default", else: tenant)- Logging.
:arc_tenantis Logger metadata while an artifact runs.
Develop with a tenant
Section titled “Develop with a tenant”/.cratis/tenants lists the tenants a development tool such as Lens can offer, from your Arc.Identity.TenantsProvider. It does not decide which tenant a request is for.