Skip to content

Resource management

Design authority: docs/jdlib/resource-management.md — this guide is the developer-facing shape of that page, not a second authority for it. Where the two disagree, the authority page and the code win.

What it is

The tenant plane's data access: a repository whose sessions are bound to one tenant, a unit of work that commits or rolls back as one, and a resolver that turns a stored reference into a live connection.

Why it exists

Every tenant-scoped read or write has to open a session the right way. Putting that in one repository makes 'the tenant is bound' a property of the type rather than a habit of the caller.

When to use it

For all tenant-plane data: application tables, their relationships, and the records a service exposes. Use it from services, not routes, so the transaction boundary is the operation.

When not to use it

For the control plane (tenants, roles, keys), which is the registry's and the audit writer's business, and for schema work, which is the migration runner's.

How it works

TenantRepository opens a session through the tenant's strategy, sets the tenant on the connection, and exposes the operations the application needs; the unit of work wraps a request's work so a failure rolls the whole thing back. ResourceResolver hydrates a ResourceHandle into a live connection, with ConnectionConfig describing how -- and refuses when the reference is not this tenant's.

Architecture

persistence/ holds the repository, models, session handling and strategies; resources/ holds the reference types and their hydration; services own the transaction boundary; routes own nothing but the call.

Example

examples/enterprise/app/repositories/resources.py and app/services/resources.py are the example's tenant plane: a repository over the example's models, and services that create, list, read and archive through it.

Security

The repository's session is bound, so a query that forgets the tenant still cannot read another tenant's rows; references carry their tenant and ResourceHydrationError is raised when one does not belong. The write fence refuses a suspended tenant before the repository is reached.

Reliability

A unit of work commits once: a partial write is not a state the application can be in. Connections are per-tenant and disposed with the plane, and a failure surfaces as a typed error rather than a leaked session.

Observability

Repository operations run inside the request's span, so a slow query is attributable to the operation that ran it; the resource's id and kind are the span's attributes.

Audit

Writes that matter are audited by the operation that performs them, so the trail names the resource, not the table.

Configuration

The DSN and the placement strategy are configuration; the models are the application's own. Nothing in the repository reads the environment.

Testing

The library tests the models, the repository and the resolver; the example's live layer drives create/read/list/archive against a real PostgreSQL as the constrained role.

Common mistakes

Opening a session outside the repository; committing per statement; passing a session across tenants; treating a stored reference as a connection; doing tenant work in a route handler.

Production checklist

Run the runtime role (not the owner) so the policies constrain you; keep the migration runner separate from serving; size the pool per tenant; check /readyz against the database the application actually uses.