infrastructure capa

infrastructure-tenant-pool

Multi-tenant database pool management with per-tenant isolation

Multi-tenant database pool management with per-tenant isolation

Tierinfrastructure
Roleunclassified (baselined)
Pathcrates/infrastructure/tenant-pool
Edition2021
Targetsinfrastructure_tenant_pool, pg_tenant_guc, provisioning_topology
Public items12 across 2 modules
Tests50

What it is for

Multi-tenant database pool management with per-tenant isolation.

This crate provides infrastructure for managing database connection pools in a multi-tenant environment where each tenant has their own database.

# Features

Pools are lazily created on first access and automatically evicted when idle.

runs migrations, and updates tenant records.

the appropriate database pool into handlers.

# Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Control Plane DB                        │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │ tenants (id, slug, database_url, status, isolation)     │ │
│  │ users, tenant_members, sessions                         │ │
│  └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
TenantPoolManager
(reads database_url)
│
┌───────────────────┼───────────────────┐
▼                   ▼                   ▼
┌──────────┐        ┌──────────┐        ┌──────────┐
│ Tenant A │        │ Tenant B │        │ Tenant C │
│ Database │        │ Database │        │ Database │
└──────────┘        └──────────┘        └──────────┘

# Example

use infrastructure_tenant_pool::{TenantPoolManager, PoolManagerConfig};
use sqlx::PgPool;

// Create the control plane pool
let control_pool = PgPool::connect("postgresql://localhost/control").await?;

// Create the pool manager
let config = PoolManagerConfig::default();
let manager = TenantPoolManager::new(control_pool, config);

// Get a pool for a specific tenant
let tenant_id = uuid::Uuid::parse_str("...")?;
let tenant_pool = manager.get_pool(tenant_id).await?;

// Use the pool for queries
let rows = sqlx::query("SELECT * FROM items")
.fetch_all(&tenant_pool)
.await?;

# Axum Integration

When the axum-middleware feature is enabled (default), you can use the tenant middleware in your Axum application:

use axum::{Router, routing::get};
use infrastructure_tenant_pool::{TenantPoolManager, tenant_middleware, TenantCtx};

async fn list_items(TenantCtx(ctx): TenantCtx) -> impl IntoResponse {
// ctx.pool is the tenant's database pool
// ctx.tenant is the Tenant record
let items = sqlx::query_as!(Item, "SELECT * FROM items")
.fetch_all(&ctx.pool)
.await?;
Json(items)
}

let app = Router::new()
.route("/items", get(list_items))
.layer(axum::middleware::from_fn_with_state(
pool_manager.clone(),
tenant_middleware,
));

Capabilities

provisioning (other)

Tenant database provisioning service.

Item
fn create_database_sql(db_name : & str, owner_role : & str, encoding : & str, locale : & str,) -> Result <String, ProvisionError>
fn harden_database_sql(db_name : & str, app_role : & str) -> Result <Vec <String>, ProvisionError>

ProvisionRequest

Tenant database provisioning service.

Item
pub struct ProvisionRequest
ProvisionRequest :: fn new(tenant_id : Uuid, tenant_slug : impl Into <String>) -> Self

ProvisionResult

Tenant database provisioning service.

Item
pub struct ProvisionResult

TenantProvisioningService

Tenant database provisioning service.

Item
pub struct TenantProvisioningService
TenantProvisioningService :: fn new(control_pool : PgPool, config : ProvisioningConfig) -> Self
TenantProvisioningService :: fn config(& self) -> & ProvisioningConfig
TenantProvisioningService :: fn control_pool(& self) -> & PgPool
TenantProvisioningService :: async fn provision(& self, request : ProvisionRequest,) -> Result <ProvisionResult, ProvisionError>
TenantProvisioningService :: async fn deprovision(& self, tenant_id : Uuid) -> Result <(), ProvisionError>
TenantProvisioningService :: async fn database_exists(& self, db_name : & str) -> Result <bool, ProvisionError>

How to use it

From this crate's own rustdoc:


# Example

From this crate's own rustdoc:


# Axum Integration

When the `axum-middleware` feature is enabled (default), you can use the
tenant middleware in your Axum application:

Module structure

infrastructure_tenant_pool

flowchart TD
  n_infrastructure_tenant_pool["infrastructure_tenant_pool"]
  n_infrastructure_tenant_pool --> n_prelude["prelude"]
  n_infrastructure_tenant_pool --> n_provisioning["provisioning"]

Public surface

`provisioning`

ItemWhat it is
pub struct ProvisionRequestRequest to provision a new tenant database.
ProvisionRequest :: fn new(tenant_id : Uuid, tenant_slug : impl Into <String>) -> SelfCreate a new provision request.
pub struct ProvisionResultResult of a successful provisioning operation.
pub struct TenantProvisioningServiceService for provisioning and deprovisioning tenant databases
TenantProvisioningService :: fn new(control_pool : PgPool, config : ProvisioningConfig) -> SelfCreate a new provisioning service.
TenantProvisioningService :: fn config(& self) -> & ProvisioningConfigGet the configuration.
TenantProvisioningService :: fn control_pool(& self) -> & PgPoolGet the control plane pool.
TenantProvisioningService :: async fn provision(& self, request : ProvisionRequest,) -> Result <ProvisionResult, ProvisionError>Provision a new database for a tenant
TenantProvisioningService :: async fn deprovision(& self, tenant_id : Uuid) -> Result <(), ProvisionError>Deprovision (delete) a tenant's database
TenantProvisioningService :: async fn database_exists(& self, db_name : & str) -> Result <bool, ProvisionError>Check if a database exists
fn create_database_sql(db_name : & str, owner_role : & str, encoding : & str, locale : & str,) -> Result <String, ProvisionError>Validate database name (prevent SQL injection)
fn harden_database_sql(db_name : & str, app_role : & str) -> Result <Vec <String>, ProvisionError>Statements that make the per-database credential an actual boundary

Re-exports. Exported here, defined elsewhere.

ExportDefined in
{IsolationStrategy,Tenant,TenantStatus,TenantTier}identity_tenant::{IsolationStrategy,Tenant,TenantStatus,TenantTier}
{IsolationStrategy,Tenant}identity_tenant::{IsolationStrategy,Tenant}
{MiddlewareError,PoolError,ProvisionError}crate::error::{MiddlewareError,PoolError,ProvisionError}
{MiddlewareError,PoolError,ProvisionError}error::{MiddlewareError,PoolError,ProvisionError}
{PoolManagerConfig,ProvisioningConfig}config::{PoolManagerConfig,ProvisioningConfig}
{PoolManagerConfig,ProvisioningConfig}crate::config::{PoolManagerConfig,ProvisioningConfig}
{PoolManagerStats,TenantPoolManager}crate::pool_manager::{PoolManagerStats,TenantPoolManager}
{ProvisionRequest,ProvisionResult,TenantProvisioningService}crate::provisioning::{ProvisionRequest,ProvisionResult,TenantProvisioningService}
{ProvisionRequest,ProvisionResult,TenantProvisioningService}provisioning::{ProvisionRequest,ProvisionResult,TenantProvisioningService}
{ensure_schemaasensure_fleet_schema,run_tenant_migrations,sandbox_migrations_dir,AccessDecision,FleetError,FleetMigrationConfig,FleetMigrator,FleetRunReport,GateError,SchemaVersion,TenantAccessPolicy,TenantMigrationGate,TenantMigrationSet,TenantResolver,TenantTarget,}fleet_migrator::{ensure_schemaasensure_fleet_schema,run_tenant_migrations,sandbox_migrations_dir,AccessDecision,FleetError,FleetMigrationConfig,FleetMigrator,FleetRunReport,GateError,SchemaVersion,TenantAccessPolicy,TenantMigrationGate,TenantMigrationSet,TenantResolver,TenantTarget,}
{extract_tenant_id,resolve_authorized_tenant,tenant_middleware,AuthorizedTenants,TenantContext,TenantCtx,TenantLayer,TenantService,TENANT_ID_HEADER,}middleware::{extract_tenant_id,resolve_authorized_tenant,tenant_middleware,AuthorizedTenants,TenantContext,TenantCtx,TenantLayer,TenantService,TENANT_ID_HEADER,}
{single_pool,single_pool_options,PoolManagerStats,TenantPoolManager}pool_manager::{single_pool,single_pool_options,PoolManagerStats,TenantPoolManager}
{tenant_middleware,TenantContext,TenantCtx,TenantLayer}crate::middleware::{tenant_middleware,TenantContext,TenantCtx,TenantLayer}

Boundary

Reaches into foundation, identity.

Shares tier infrastructure with 82 other crates: infrastructure-acquire, infrastructure-adapters-google-calendar, infrastructure-adapters-google-gmail, infrastructure-adapters-google-places, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, … (82 total).

_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._

Where it sits

Tier (ontology)infrastructure
Architectural role (taxonomy)unclassified (baselined)
Locationcrates/infrastructure/tenant-pool
Vocabulary in force (lexicon)current

Tier flow. Which tiers this crate's own edges cross.

flowchart LR
  n_infrastructure["infrastructure"] --> n_foundation["foundation"]
  n_infrastructure["infrastructure"] --> n_identity["identity"]

Dependencies

Runtime, in this workspace.

CrateTierOptionalOnly on
`foundation-audit-log`foundationnoalways
`foundation-basemodels`foundationnoalways
`identity-tenant`identitynoalways

Runtime, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
async-trait^0.1noalways
axum^0.7multipartyesalways
chrono^0.4serde, serdenoalways
dashmap^5.5noalways
http^1.1yesalways
humantime^2.1noalways
serde^1derive, derivenoalways
sqlx^0.8runtime-tokio, postgres, chrono, uuid, json, runtime-tokio, … (10 total)noalways
thiserror^2noalways
tokio^1full, sync, timenoalways
tower^0.5yesalways
tracing^0.1noalways
uuid^1v4, v7, serde, js, v4, serdenoalways

Development, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
tokio^1full, rt-multi-thread, macrosnoalways

Build. None.

Depended on by. 12 workspace crates.

Signal flow — what reaches this crate, and what it reaches.

flowchart LR
  n_application_communication["application-communication"] -->|uses| SELF
  n_application_dlp["application-dlp"] -->|uses| SELF
  n_application_legal_documents["application-legal-documents"] -->|uses| SELF
  n_application_legal_evidence["application-legal-evidence"] -->|uses| SELF
  n_application_legal_knowledge["application-legal-knowledge"] -->|uses| SELF
  n_application_legal_matter["application-legal-matter"] -->|uses| SELF
  n_application_legal_procedure["application-legal-procedure"] -->|uses| SELF
  n_application_party_api["application-party-api"] -->|uses| SELF
  n_application_tenant["application-tenant"] -->|uses| SELF
  n_operations_privacy_scan_runner["operations-privacy-scan-runner"] -->|uses| SELF
  n_operations_test_results_ingest["operations-test-results-ingest"] -->|uses| SELF
  n_platform_privacy_scan_api["platform-privacy-scan-api"] -->|uses| SELF
  SELF["infrastructure-tenant-pool"]
  SELF -->|runtime| n_foundation_audit_log["foundation-audit-log"]
  SELF -->|runtime| n_foundation_basemodels["foundation-basemodels"]
  SELF -->|runtime| n_identity_tenant["identity-tenant"]
  classDef self fill:#1f883d,stroke:#1f883d,color:#fff;
  class SELF self;

Feature flags

FeatureEnablesOn by default
axumdep:axumno
axum-middlewareaxum, tower, httpyes
defaultaxum-middlewareyes
httpdep:httpno
towerdep:towerno
flowchart LR
  n_axum["axum"] --> n_dep_axum["dep:axum"]
  n_axum_middleware["axum-middleware"] --> n_axum["axum"]
  n_axum_middleware["axum-middleware"] --> n_tower["tower"]
  n_axum_middleware["axum-middleware"] --> n_http["http"]
  n_default["default"] --> n_axum_middleware["axum-middleware"]
  n_http["http"] --> n_dep_http["dep:http"]
  n_tower["tower"] --> n_dep_tower["dep:tower"]

Targets

KindNameSource
libinfrastructure_tenant_pool`src/lib.rs`
testpg_tenant_guc`tests/pg_tenant_guc.rs`
testprovisioning_topology`tests/provisioning_topology.rs`

Error model

No public error type was detected: no public item declares a type named *Error, and no public signature returns one.

Operational characteristics

PropertyEvidence
async public surfaceyes
async runtimeyes
database accessyes
network I/Oyes
unsafe codenone detected
environment variablesnone detected

No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.

Configuration

No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.

12 workspace crates depend on this one: application-communication, application-dlp, application-legal-documents, application-legal-evidence, application-legal-knowledge, application-legal-matter, application-legal-procedure, application-party-api, application-tenant, operations-privacy-scan-runner, operations-test-results-ingest, platform-privacy-scan-api.

Verification

KindCount
Unit tests43
Integration tests7
Examples0
Doctests2

Evidence by module. How often each public module is named by something executable.

ModuleTestsExamplesConsumers
provisioning500

What the tests establish, by name:

Documentation coverage

MeasureDocumentedTotal
Public items with rustdoc1212
Public modules with a //! block12
pie showData
    title Public items with rustdoc
    "Documented" : 12
    "No rustdoc detected" : 0

Metrics

MetricValue
Rust source files7
Source lines3069
Code lines1971
Public API items12
Public modules2
Tests50
Examples0
Cargo features5
Direct runtime dependencies16
Workspace reverse dependencies12
pie showData
    title Public API by kind
    "function" : 2
    "method" : 7
    "struct" : 3
pie showData
    title Rust source composition
    "Code" : 1971
    "Blank or comment" : 1098

Generation

Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.

Todas las infrastructure · Manual