Tenant provisioning service for creating PostgreSQL schemas.
This namespace handles the provisioning of tenant-specific database schemas in PostgreSQL. It creates schema structures dynamically for new tenants.
Key Responsibilities:
Usage: (require '[boundary.tenant.shell.provisioning :as provisioning]) (provisioning/provision-tenant! ctx tenant-entity)
Constraints:
Tenant provisioning service for creating PostgreSQL schemas. This namespace handles the provisioning of tenant-specific database schemas in PostgreSQL. It creates schema structures dynamically for new tenants. Key Responsibilities: - Create PostgreSQL schema for tenant - Copy table structures from public schema - Initialize tenant-specific data - Validate schema creation success Usage: (require '[boundary.tenant.shell.provisioning :as provisioning]) (provisioning/provision-tenant! ctx tenant-entity) Constraints: - Only works with PostgreSQL (other databases ignored) - Schema names must be valid PostgreSQL identifiers - Provisioning is idempotent (safe to call multiple times) - Does not copy data from public schema (only structure)
(create-tenant-schema-provider)Create a new tenant schema provider instance.
This provider implements the ITenantSchemaProvider protocol, allowing other modules (like jobs) to execute code in tenant schema contexts without directly depending on implementation details.
Usage: (def provider (create-tenant-schema-provider)) (ports/with-tenant-schema provider db-ctx "tenant_acme_corp" (fn [tx] (jdbc/execute! tx ["SELECT * FROM users"])))
Create a new tenant schema provider instance.
This provider implements the ITenantSchemaProvider protocol, allowing
other modules (like jobs) to execute code in tenant schema contexts
without directly depending on implementation details.
Usage:
(def provider (create-tenant-schema-provider))
(ports/with-tenant-schema provider db-ctx "tenant_acme_corp"
(fn [tx]
(jdbc/execute! tx ["SELECT * FROM users"])))(deprovision-tenant! ctx tenant-entity)Deprovision tenant database schema.
WARNING: This is destructive and will delete all tenant data. Use with caution.
Args: ctx: Database context tenant-entity: Tenant entity map with :schema-name
Returns: {:success? boolean :schema-name string :message string}
Throws: ex-info with :type :validation-error if tenant entity is invalid ex-info with :type :deprovisioning-error if operation fails
Deprovision tenant database schema.
WARNING: This is destructive and will delete all tenant data.
Use with caution.
Args:
ctx: Database context
tenant-entity: Tenant entity map with :schema-name
Returns:
{:success? boolean
:schema-name string
:message string}
Throws:
ex-info with :type :validation-error if tenant entity is invalid
ex-info with :type :deprovisioning-error if operation fails(list-tenant-schemas ctx)List all tenant schemas in the database. Returns a vector of schema name strings matching the tenant_* pattern. Returns empty vector for non-PostgreSQL databases.
List all tenant schemas in the database. Returns a vector of schema name strings matching the tenant_* pattern. Returns empty vector for non-PostgreSQL databases.
(provision-tenant! ctx tenant-entity)Provision tenant database schema.
This creates a PostgreSQL schema for the tenant and copies the table structure from the public schema. It is idempotent and safe to call multiple times.
Steps:
Args: ctx: Database context with :datasource and :adapter tenant-entity: Tenant entity map with :schema-name
Returns: {:success? boolean :schema-name string :table-count int :message string :errors vector (only if success? false)}
Throws: ex-info with :type :validation-error if tenant entity is invalid ex-info with :type :not-supported if database is not PostgreSQL ex-info with :type :provisioning-error if provisioning fails
Example: (provision-tenant! ctx {:schema-name "tenant_acme_corp"}) => {:success? true :schema-name "tenant_acme_corp" :table-count 5 :message "Tenant schema provisioned successfully"}
Provision tenant database schema.
This creates a PostgreSQL schema for the tenant and copies the
table structure from the public schema. It is idempotent and safe
to call multiple times.
Steps:
1. Check if tenant already has schema
2. Create schema if it doesn't exist
3. Copy table structures from public schema
4. Validate provisioning success
Args:
ctx: Database context with :datasource and :adapter
tenant-entity: Tenant entity map with :schema-name
Returns:
{:success? boolean
:schema-name string
:table-count int
:message string
:errors vector (only if success? false)}
Throws:
ex-info with :type :validation-error if tenant entity is invalid
ex-info with :type :not-supported if database is not PostgreSQL
ex-info with :type :provisioning-error if provisioning fails
Example:
(provision-tenant! ctx {:schema-name "tenant_acme_corp"})
=> {:success? true
:schema-name "tenant_acme_corp"
:table-count 5
:message "Tenant schema provisioned successfully"}(sync-tenant-schemas! ctx)Backfill every existing tenant_<slug> schema with all tenant-scoped tables.
provision-tenant! only copies the tenant-scoped tables into a schema at
creation time and returns early when the schema already exists. So when
tenant-scoped-tables gains entries in a release, previously-provisioned
tenants do NOT get the new tables, and any job or repository using them under
the tenant search_path fails with a missing relation. This is the
upgrade/sync path: run it on deploy after extending tenant-scoped-tables.
For each existing tenant schema it re-copies the public table structures.
get-table-ddl emits CREATE TABLE IF NOT EXISTS, so existing tables are
left untouched and only missing ones are created — fully idempotent. No-op
(and empty result) on non-PostgreSQL databases.
Returns {:schemas-synced [schema-name ...] :tables [table-name ...]}.
Backfill every existing tenant_<slug> schema with all tenant-scoped tables.
`provision-tenant!` only copies the tenant-scoped tables into a schema at
creation time and returns early when the schema already exists. So when
`tenant-scoped-tables` gains entries in a release, previously-provisioned
tenants do NOT get the new tables, and any job or repository using them under
the tenant search_path fails with a missing relation. This is the
upgrade/sync path: run it on deploy after extending `tenant-scoped-tables`.
For each existing tenant schema it re-copies the public table structures.
`get-table-ddl` emits `CREATE TABLE IF NOT EXISTS`, so existing tables are
left untouched and only missing ones are created — fully idempotent. No-op
(and empty result) on non-PostgreSQL databases.
Returns {:schemas-synced [schema-name ...] :tables [table-name ...]}.(tenant-provisioned? ctx tenant-entity)Check if a tenant's database schema has been provisioned. Returns true if the schema exists in PostgreSQL, false otherwise. Returns false for non-PostgreSQL databases.
Check if a tenant's database schema has been provisioned. Returns true if the schema exists in PostgreSQL, false otherwise. Returns false for non-PostgreSQL databases.
(with-tenant-schema ctx tenant-schema-name f)Execute function f with database search_path set to tenant schema.
This ensures all database operations within f execute in the tenant's schema context. The search_path is restored after execution.
Usage: (with-tenant-schema ctx "tenant_acme" (fn [tx] ;; All queries here use tenant_acme schema (jdbc/execute! tx ["SELECT * FROM users"])))
Args: ctx: Database context with :datasource tenant-schema-name: Name of tenant schema (e.g., "tenant_acme") f: Function accepting transaction/connection (fn [tx] -> result)
Returns: Result of f
Notes:
Execute function f with database search_path set to tenant schema.
This ensures all database operations within f execute in the tenant's
schema context. The search_path is restored after execution.
Usage:
(with-tenant-schema ctx "tenant_acme"
(fn [tx]
;; All queries here use tenant_acme schema
(jdbc/execute! tx ["SELECT * FROM users"])))
Args:
ctx: Database context with :datasource
tenant-schema-name: Name of tenant schema (e.g., "tenant_acme")
f: Function accepting transaction/connection (fn [tx] -> result)
Returns:
Result of f
Notes:
- Only works with PostgreSQL
- search_path is transaction-scoped (isolated per connection)
- Falls back to public schema if error
- Schema must exist before calling this functioncljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |