tenant
Multi-tenancy with schema-per-tenant isolation on PostgreSQL. Provides tenant CRUD, schema provisioning, tenant resolution, and cross-library integration.
|
Note
|
Requires PostgreSQL. On SQLite, MySQL or H2 the application refuses to boot with
:wagoe/tenant configured. A test profile on H2 sets :allow-h2? true under
:wagoe/tenant and runs it with tenant rows but no per-tenant schemas.
|
Key namespaces
| Namespace | Purpose |
|---|---|
|
Pure functions: slug validation, schema name generation, prepare-tenant, decisions |
|
Protocols: |
|
Malli schemas: |
|
Service layer with observability interceptors |
|
Database CRUD with case conversion |
|
PostgreSQL schema provisioning/deprovisioning |
|
REST API handlers (CRUD + provision/suspend/activate, export) |
|
Membership and invitation endpoints |
|
Integrant lifecycle ( |
Tenant lifecycle
:active → Created; its PostgreSQL schema is created with it
:suspended → Access disabled (data preserved)
:deleted → Soft delete, `deleted-at` set; the schema is kept
Slug and schema naming
;; Slug: lowercase alphanumeric + hyphens, 2–56 chars, so that
;; "tenant_" + slug fits PostgreSQL's 63-byte identifier limit
(tenant/valid-slug? "acme-corp") ;=> true
(tenant/valid-slug? "ACME") ;=> false (uppercase)
(tenant/valid-slug? "acme_corp") ;=> false (underscore)
;; Slug → PostgreSQL schema name
(tenant/slug->schema-name "acme-corp") ;=> "tenant_acme_corp"
Schema provisioning
(require '[wagoe.tenant.shell.provisioning :as provisioning])
;; Provision: creates PostgreSQL schema and copies table structure
(provisioning/provision-tenant! db-ctx tenant)
;=> {:success? true :schema-name "tenant_acme_corp" :table-count 5}
;; Execute queries in tenant schema context
(provisioning/with-tenant-schema db-ctx "tenant_acme_corp"
(fn [tx]
;; SET search_path TO tenant_acme_corp, public
(jdbc/execute! tx ["SELECT * FROM orders"])))
HTTP API
# Admin role required
POST /api/v1/tenants # Create tenant
GET /api/v1/tenants # List tenants
GET /api/v1/tenants/:id # Get tenant
PUT /api/v1/tenants/:id # Update tenant
POST /api/v1/tenants/:id/provision # Provision schema
POST /api/v1/tenants/:id/suspend # Suspend tenant
POST /api/v1/tenants/:id/activate # Activate tenant
DELETE /api/v1/tenants/:id # Soft delete
# Platform admin
GET /api/v1/tenants/:id/export # The tenant's data
POST /api/v1/tenants/:id/export-jobs # The same, as a background job
# Memberships
POST /api/v1/tenants/:tenant-id/memberships # Invite a user
GET /api/v1/tenants/:tenant-id/memberships # List members
GET|PUT|DELETE /api/v1/tenants/:tenant-id/memberships/:id
POST /api/v1/memberships/:id/accept # Accept an invitation
Testing
clojure -M:test:test/pg :tenant