第十六章:多租戶架構 (Multi-Tenancy)
16.1 引言:SaaS 的基石
多租戶 (Multi-Tenancy) 是 SaaS (Software as a Service) 應用程式的核心架構模式。它允許單一應用程式實例服務多個客戶 (租戶),同時確保每個租戶的資料完全隔離。
1. 為什麼需要多租戶?
- 成本效益:共享基礎設施,降低每個客戶的成本。
- 運維簡化:只需維護一套程式碼與部署。
- 快速擴展:新增客戶不需要重新部署。
2. 挑戰
- 資料隔離:絕對不能讓租戶 A 看到租戶 B 的資料。
- 效能隔離:租戶 A 的大量請求不應影響租戶 B。
- 客製化:不同租戶可能需要不同的功能或配置。
16.2 租戶隔離策略
ABP 支援三種主流的隔離策略。
1. 共享資料庫 + 共享 Schema (Shared Database)
所有租戶的資料存在同一個資料庫的同一個 Schema 中,透過 TenantId 欄位區分。
- 優點:最簡單、成本最低。
- 缺點:隔離性最差、單點故障、難以符合某些合規要求 (如 GDPR)。
- 適用:初創 SaaS、租戶數量少 (<100)。
csharp
public class Book : FullAuditedAggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; } // ABP 自動管理
public string Name { get; set; }
}2. 共享資料庫 + 獨立 Schema (Shared Database, Separate Schemas)
所有租戶共享一個資料庫伺服器,但每個租戶有自己的 Schema。
- 優點:較好的隔離性、備份與還原更靈活。
- 缺點:並非所有資料庫都良好支援 (PostgreSQL 支援較好)。
- 適用:中型 SaaS (100-1000 租戶)。
3. 獨立資料庫 (Database per Tenant)
每個租戶有自己的資料庫 (甚至可以在不同的伺服器上)。
- 優點:最高隔離性、符合嚴格的合規要求、可針對大客戶單獨優化。
- 缺點:運維複雜度高、成本高。
- 適用:企業級 SaaS、高價值客戶。
16.3 啟用多租戶
1. 配置 Module
在 Domain 層的 Module 中啟用。
csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = true;
});
}2. 租戶解析 (Tenant Resolvers)
ABP 需要知道當前請求屬於哪個租戶。預設支援多種解析方式:
- Domain Resolver:
tenant1.myapp.com,tenant2.myapp.com - Header Resolver: HTTP Header
__tenant或X-Tenant-Id - Cookie Resolver: Cookie 中的
__tenant - Query String:
?__tenant=tenant1
您可以自訂解析優先順序:
csharp
Configure<AbpTenantResolveOptions>(options =>
{
options.TenantResolvers.Clear();
options.TenantResolvers.Add(new DomainTenantResolveContributor());
options.TenantResolvers.Add(new HeaderTenantResolveContributor());
});16.4 實作租戶隔離
1. 自動資料過濾
當實體實作 IMultiTenant 介面時,ABP 會自動在查詢時加上 TenantId 過濾。
csharp
// 查詢時,ABP 自動加上 WHERE TenantId = @CurrentTenantId
var books = await _bookRepository.GetListAsync();2. 手動切換租戶 (用於後台任務或測試)
csharp
using (_currentTenant.Change(tenantId))
{
// 在這個 scope 內,所有操作都屬於指定的租戶
var books = await _bookRepository.GetListAsync();
}3. 存取宿主資料 (Host Data)
某些資料是跨租戶共享的 (如系統設定)。這些實體不應實作 IMultiTenant。
csharp
public class SystemSetting : Entity<Guid>
{
// 沒有 TenantId,所有租戶共享
public string Key { get; set; }
public string Value { get; set; }
}16.5 獨立資料庫模式
1. 配置租戶連線字串
在租戶建立時,儲存其專屬的連線字串。
csharp
public async Task CreateTenantAsync(string name, string connectionString)
{
var tenant = new Tenant(GuidGenerator.Create(), name);
// 設定租戶專屬連線字串
tenant.SetConnectionString(connectionString);
await _tenantRepository.InsertAsync(tenant);
}2. 動態連線字串解析
ABP 會自動根據當前租戶載入對應的連線字串。
3. 資料庫遷移
每個租戶的資料庫都需要執行 Migration。
csharp
public class TenantDatabaseMigrationService : ITransientDependency
{
private readonly ITenantRepository _tenantRepository;
private readonly IDbContextProvider<BookStoreDbContext> _dbContextProvider;
public async Task MigrateAllTenantsAsync()
{
var tenants = await _tenantRepository.GetListAsync();
foreach (var tenant in tenants)
{
using (_currentTenant.Change(tenant.Id))
{
var dbContext = await _dbContextProvider.GetDbContextAsync();
await dbContext.Database.MigrateAsync();
}
}
}
}16.6 Feature Management (功能管理)
Feature Management 允許您為不同租戶啟用或停用特定功能,這是實現 SaaS 定價層級 (Pricing Tiers) 的關鍵。
1. 定義 Features
csharp
public class BookStoreFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var group = context.AddGroup("BookStore");
group.AddFeature(
"BookStore.MaxBookCount",
defaultValue: "100",
displayName: L("Feature:MaxBookCount"),
valueType: new FreeTextStringValueType(new NumericValueValidator(0, 1000000))
);
group.AddFeature(
"BookStore.AdvancedReporting",
defaultValue: "false",
displayName: L("Feature:AdvancedReporting"),
valueType: new ToggleStringValueType()
);
}
}2. 檢查 Feature
csharp
public class BookAppService : ApplicationService
{
public async Task CreateAsync(CreateBookDto input)
{
// 檢查租戶是否有權限建立更多書籍
var maxCount = await FeatureChecker.GetAsync<int>("BookStore.MaxBookCount");
var currentCount = await _bookRepository.CountAsync();
if (currentCount >= maxCount)
{
throw new BusinessException("BookStore:MaxBookCountExceeded");
}
// ...
}
[RequiresFeature("BookStore.AdvancedReporting")]
public async Task<byte[]> GenerateAdvancedReportAsync()
{
// 只有啟用此 Feature 的租戶才能呼叫
}
}3. 設定租戶 Feature 值
csharp
await FeatureManager.SetForTenantAsync(
tenantId,
"BookStore.MaxBookCount",
"500" // 升級為 Premium 方案,可建立 500 本書
);16.7 實戰練習
練習 1:建立多租戶應用
- 啟用多租戶功能。
- 建立兩個租戶:
TenantA與TenantB。 - 分別以兩個租戶的身分建立書籍,驗證資料隔離。
練習 2:實作 Feature-based 定價
- 定義三個 Features:
MaxUsers,AdvancedReporting,APIAccess。 - 建立三個定價方案:
- Basic: MaxUsers=10, 其他關閉。
- Pro: MaxUsers=100, AdvancedReporting=開啟。
- Enterprise: 全部開啟。
- 為不同租戶設定不同方案,並測試功能限制。
練習 3:獨立資料庫遷移
- 為一個租戶配置獨立的資料庫連線字串。
- 實作自動遷移腳本,在租戶建立時自動建立並初始化資料庫。
16.8 總結
多租戶架構是構建 SaaS 應用的核心。
- 隔離策略 的選擇影響整個系統的架構。
- Feature Management 讓您能靈活地為不同客戶提供差異化服務。
- 自動化 (租戶建立、資料庫遷移) 是運維的關鍵。
在下一章,我們將探討 測試策略 (Testing Strategy),學習如何為多租戶應用撰寫可靠的測試。
參考資源: