3. 租户核心模型:Tenant、TenantId 与 Subscription
在可观测性租户隔离方案中,租户核心模型是领域驱动设计的基石。本章将定义 Tenant(租户)实体、TenantId(租户标识)值对象、Subscription(订阅)实体,并明确它们之间的聚合关系与生命周期约束。
3.1 TenantId 值对象:不可变标识
TenantId 是租户的唯一标识,采用值对象模式设计,具备以下特征:
- 不可变性:一旦创建,其内部值不可修改。
- 无副作用:两个 TenantId 如果值相等,则视为同一个对象。
- 全局唯一:通常由 UUID 或雪花算法生成。
| 属性 | 类型 | 说明 |
|---|---|---|
| id | UUID / String | 全局唯一标识,不可变 |
| createdAt | Instant | 标识生成时间戳 |
设计原则:TenantId 作为值对象,不应包含业务逻辑或状态,仅用于标识和相等性比较。
3.2 Tenant 实体:租户聚合根
Tenant 是租户隔离领域的核心聚合根,承载租户的基本信息、状态以及生命周期管理。每个 Tenant 拥有唯一的 TenantId,并维护一组核心属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| tenantId | TenantId | 值对象,唯一标识 |
| name | String | 租户名称,如“某科技公司” |
| status | Enum | ACTIVE / SUSPENDED / DELETED |
| contactEmail | String | 管理员联系邮箱 |
| createdAt | Instant | 创建时间 |
| updatedAt | Instant | 最后更新时间 |
Tenant 实体提供以下领域行为:
activate():将状态从 SUSPENDED 转为 ACTIVE。suspend():暂停租户服务,所有关联 Subscription 进入暂停状态。delete():软删除,保留历史数据但不再提供服务。
3.3 Subscription 实体:租户与服务的关联
Subscription 表示租户对某个可观测性服务(如日志、指标、链路追踪)的订阅关系。它连接 Tenant 与具体的服务产品,并记录订阅的配置与状态。
| 属性 | 类型 | 说明 |
|---|---|---|
| subscriptionId | UUID | 订阅唯一标识 |
| tenantId | TenantId | 所属租户 |
| serviceType | Enum | LOG / METRIC / TRACE |
| plan | Enum | FREE / STANDARD / ENTERPRISE |
| status | Enum | ACTIVE / EXPIRED / CANCELLED |
| startDate | LocalDate | 订阅生效日期 |
| endDate | LocalDate | 订阅到期日期 |
约束规则:一个 Tenant 在同一 serviceType 上只能有一个 ACTIVE 状态的 Subscription。订阅到期后自动转为 EXPIRED,不可直接恢复,需重新订阅。
3.4 实体关系与聚合边界
Tenant 作为聚合根,Subscription 作为其内部实体,二者形成一对多的组合关系。TenantId 作为值对象被 Tenant 和 Subscription 共同引用。
3.5 领域服务:租户订阅管理
当需要创建租户并为其分配订阅时,领域服务 TenantSubscriptionService 负责协调聚合根与实体之间的操作:
public class TenantSubscriptionService {
private final TenantRepository tenantRepository;
private final SubscriptionRepository subscriptionRepository;
public Tenant createTenantWithSubscription(String name, String email, ServiceType serviceType, Plan plan) {
TenantId tenantId = TenantId.generate();
Tenant tenant = new Tenant(tenantId, name, email);
tenant.activate();
Subscription subscription = new Subscription(
UUID.randomUUID(),
tenantId,
serviceType,
plan,
LocalDate.now(),
LocalDate.now().plusMonths(1)
);
tenantRepository.save(tenant);
subscriptionRepository.save(subscription);
return tenant;
}
}
关键设计决策:Subscription 不直接持有 Tenant 引用,而是通过 TenantId 关联,保持聚合边界清晰,避免跨聚合直接导航。
3.6 模型验证与不变约束
为确保领域模型的完整性,以下不变约束必须在代码中强制执行:
- 唯一活跃订阅:同一 Tenant 在同一 serviceType 上只能有一个 ACTIVE 状态的 Subscription。
- 状态转换合法:只有 ACTIVE 的 Subscription 才能被取消;只有 SUSPENDED 的 Tenant 才能被激活。
- 时间连续性:Subscription 的 endDate 必须晚于 startDate。
这些约束通常在实体方法或领域服务中通过前置条件检查实现,确保租户隔离方案在数据层面不会出现不一致。