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 共同引用。

租户核心模型聚合关系图 Tenant(聚合根) - tenantId: TenantId - name: String - status: Enum TenantId(值对象) - id: UUID - createdAt: Instant Subscription(实体) - subscriptionId: UUID - tenantId: TenantId - serviceType: Enum - plan: Enum 引用 1 对多 聚合规则 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。

这些约束通常在实体方法或领域服务中通过前置条件检查实现,确保租户隔离方案在数据层面不会出现不一致。