领域建模基础:领域模型、限界上下文、实体与值对象在租户模型中的应用

在构建可观测性租户隔离方案时,领域建模是决定系统能否长期演进的基石。本节将围绕四个核心概念展开:领域模型、限界上下文(Bounded Context)、实体(Entity)与值对象(Value Object),并逐一映射到租户模型的具体场景中。

1. 领域模型:业务知识的统一语言

领域模型是对业务问题域中核心概念、规则与关系的抽象表达。它不是数据库表设计,也不是API接口定义,而是业务专家与开发团队之间共享的“通用语言”(Ubiquitous Language)。

在可观测性租户隔离场景中,领域模型需要回答以下问题:

  • 租户(Tenant)是什么?它拥有哪些资源?
  • 租户之间的数据如何划分?哪些数据是共享的?
  • 租户的配额、权限、计费规则如何表达?
关键原则:领域模型中的每一个术语,都必须在业务部门和开发团队之间达成一致。例如,“工作空间(Workspace)”在模型中是租户下的资源容器,而非UI上的一个标签页。

2. 限界上下文:划定模型的边界

限界上下文是DDD中最重要的战术模式之一。它定义了模型内部的术语、规则和概念只在特定边界内有效。同一个词(如“用户”)在不同上下文中可能具有完全不同的含义和行为。

在可观测性租户隔离方案中,典型的限界上下文包括:

限界上下文 核心职责 租户模型中的角色
租户管理上下文 租户的创建、激活、停用、删除 定义租户实体、租户状态、租户配置
身份与访问上下文 用户认证、角色分配、权限校验 管理租户内用户、角色、权限策略
可观测性数据上下文 指标、日志、链路的采集与存储 按租户标签隔离数据,执行配额限制
计费与配额上下文 资源用量统计、计费计算、配额预警 跟踪租户的资源消耗与剩余额度
实践要点:每个限界上下文应有独立的代码仓库、数据库模式(Schema)或至少独立的表前缀。上下文之间通过领域事件(Domain Event)或防腐层(Anti-Corruption Layer)进行通信,避免直接耦合。
限界上下文与租户模型关系图 限界上下文与租户模型关系图 租户管理上下文 Tenant Entity / TenantConfig VO 身份与访问上下文 User / Role / Permission 可观测性数据上下文 Metric / Log / Trace 计费与配额上下文 Usage / Quota / Invoice 每个上下文独立演进 通过领域事件通信 租户ID作为全局标识 贯穿所有上下文

3. 实体(Entity):具有唯一标识的领域对象

实体是领域模型中拥有唯一标识(Identity)的对象。即使两个实体的所有属性值都相同,只要标识不同,它们就是不同的对象。实体的核心特征是可变性(Mutable)——其属性会随着业务操作而改变。

在租户模型中,典型的实体包括:

  • Tenant(租户):拥有唯一租户ID(如 tenant-001),状态可变更(激活→停用)。
  • User(用户):在租户内拥有唯一用户ID,可关联多个角色。
  • Workspace(工作空间):租户下的资源集合,具有独立的配置和配额。
实体设计示例(伪代码):
// Tenant 实体
class Tenant {
    private TenantId id;          // 值对象,唯一标识
    private TenantName name;      // 值对象
    private TenantStatus status;  // 枚举:ACTIVE, SUSPENDED, DELETED
    private List<Workspace> workspaces;

    public void activate() {
        if (this.status == TenantStatus.SUSPENDED) {
            this.status = TenantStatus.ACTIVE;
            // 发布领域事件:TenantActivatedEvent
        }
    }

    public void addWorkspace(Workspace workspace) {
        // 业务规则:租户处于激活状态才能添加工作空间
        if (this.status != TenantStatus.ACTIVE) {
            throw new IllegalStateException("租户未激活");
        }
        this.workspaces.add(workspace);
    }
}

4. 值对象(Value Object):描述性、不可变、无标识

值对象用于描述实体的属性或业务概念,它没有唯一标识,且通常是不可变的(Immutable)。两个值对象如果所有属性都相同,则视为同一个对象。值对象的核心价值在于封装复杂概念,避免原始类型泛滥(Primitive Obsession)。

在租户模型中的典型值对象:

值对象 封装属性 使用场景
TenantId String value 作为租户的唯一标识,校验格式(如UUID)
EmailAddress String localPart, String domain 封装邮箱格式校验、域名提取
QuotaLimit int maxMetrics, int maxLogs, int maxTraces 定义租户的资源配额上限
TimeRange Instant start, Instant end 查询可观测性数据时的时间范围
实体 vs 值对象判断法则:如果对象需要跟踪其生命周期变化(如“租户从激活变为停用”),则应该是实体;如果对象只是描述某个属性且替换后不影响业务(如“将邮箱从 a@b.com 改为 c@d.com”),则应该是值对象。
实体与值对象在租户模型中的协作 实体与值对象在租户模型中的协作 Tenant(实体) ID: tenant-001 状态: ACTIVE Workspace(实体) ID: ws-abc-123 名称: 生产环境 TenantId(值对象) value: "tenant-001" QuotaLimit(值对象) metrics: 1000, logs: 500 TimeRange(值对象) start→end 时间区间 实体:有ID、可变、 有生命周期 值对象:无ID、不可变 值对象可被多个实体共享 替换即创建新实例

5. 在租户隔离方案中的综合应用

将上述概念组合起来,可以构建出清晰的租户模型架构:

  1. 限界上下文将系统划分为租户管理、身份访问、可观测性数据、计费配额四个独立领域,每个领域拥有自己的实体和值对象。
  2. 实体(如Tenant、User、Workspace)负责承载业务逻辑和状态变更,通过领域事件通知其他上下文。
  3. 值对象(如TenantId、QuotaLimit、TimeRange)用于封装描述性概念,确保类型安全和业务规则内聚。
  4. 所有上下文通过租户ID(TenantId值对象)作为全局关联键,实现数据隔离与跨上下文查询。
常见陷阱:不要将数据库主键(自增ID)直接作为领域模型中的实体标识。数据库主键是技术实现细节,而实体标识是业务概念。推荐使用UUID或业务编码作为实体ID,避免与数据库耦合。

通过以上建模,可观测性租户隔离方案获得了清晰的领域边界、稳定的实体生命周期管理以及高内聚的值对象封装,为后续的架构实现和可观测性埋点奠定了坚实的模型基础。