Skip to content

Strategy

The Strategy pattern defines a family of algorithms, encapsulates each one in a separate class, and makes them interchangeable. Algorithm selection is delegated to configuration or runtime.

classDiagram
    class TenantIsolationStrategy {
        SharedDatabase
        SchemaPerTenant
        DatabasePerTenant
    }

    class ITenantIsolationStrategyProvider {
        +CreateDbContext()
    }

    class SharedDatabaseDbContextFactory
    class TenantPerSchemaDbContextFactory
    class TenantPerDatabaseDbContextFactory

    ITenantIsolationStrategyProvider <|.. SharedDatabaseDbContextFactory
    ITenantIsolationStrategyProvider <|.. TenantPerSchemaDbContextFactory
    ITenantIsolationStrategyProvider <|.. TenantPerDatabaseDbContextFactory

    TenantIsolationStrategy ..> ITenantIsolationStrategyProvider : selects

    class IBlobKeyStrategy {
        +BuildObjectKey()
        +ResolveBucketName()
    }

    class PrefixBlobKeyStrategy

    IBlobKeyStrategy <|.. PrefixBlobKeyStrategy

    class IStringEncryptionProvider {
        +EncryptAsync()
        +DecryptAsync()
    }

    class AesStringEncryptionProvider

    IStringEncryptionProvider <|.. AesStringEncryptionProvider
StrategyInterfaceFileImplementations
Tenant isolationITenantIsolationStrategyProvidersrc/Granit.Persistence/MultiTenancy/SharedDatabase, TenantPerSchema, TenantPerDatabase
S3 keyIBlobKeyStrategysrc/Granit.BlobStorage/IBlobKeyStrategy.csPrefixBlobKeyStrategy
EncryptionIStringEncryptionProvidersrc/Granit.Encryption/Providers/AesStringEncryptionProvider
Tenant resolutionITenantResolversrc/Granit.MultiTenancy/Resolvers/HeaderTenantResolver, JwtClaimTenantResolver

Custom variant — Enum-based Selection: TenantIsolationStrategy is an enum used as a selection key. The factory picks the implementation via a switch expression — no reflection or complex configuration.

The choice between SharedDatabase, SchemaPerTenant, and DatabasePerTenant has major implications on cost, performance, and security. The Strategy pattern allows changing this decision without modifying application code.

// The strategy is selected via appsettings.json (section "Persistence")
// { "Persistence": { "IsolationStrategy": "SchemaPerTenant" } }
services.AddGranitPersistence();
// Application code is identical regardless of the strategy
public sealed class PatientService(AppDbContext db)
{
public async Task<Patient?> FindAsync(Guid id, CancellationToken cancellationToken)
=> await db.Patients.FindAsync([id], ct);
// SharedDatabase -> WHERE Id = @id AND TenantId = @tid
// SchemaPerTenant -> SET search_path TO tenant_xxx; SELECT ... WHERE Id = @id
// DatabasePerTenant -> Connection to tenant_xxx_db; SELECT ... WHERE Id = @id
}