领域建模基础:领域模型、限界上下文、实体与值对象在租户模型中的应用
在构建可观测性租户隔离方案时,领域建模是决定系统能否长期演进的基石。本节将围绕四个核心概念展开:领域模型、限界上下文(Bounded Context)、实体(Entity)与值对象(Value Object),并逐一映射到租户模型的具体场景中。
1. 领域模型:业务知识的统一语言
领域模型是对业务问题域中核心概念、规则与关系的抽象表达。它不是数据库表设计,也不是API接口定义,而是业务专家与开发团队之间共享的“通用语言”(Ubiquitous Language)。
在可观测性租户隔离场景中,领域模型需要回答以下问题:
- 租户(Tenant)是什么?它拥有哪些资源?
- 租户之间的数据如何划分?哪些数据是共享的?
- 租户的配额、权限、计费规则如何表达?
关键原则:领域模型中的每一个术语,都必须在业务部门和开发团队之间达成一致。例如,“工作空间(Workspace)”在模型中是租户下的资源容器,而非UI上的一个标签页。
2. 限界上下文:划定模型的边界
限界上下文是DDD中最重要的战术模式之一。它定义了模型内部的术语、规则和概念只在特定边界内有效。同一个词(如“用户”)在不同上下文中可能具有完全不同的含义和行为。
在可观测性租户隔离方案中,典型的限界上下文包括:
| 限界上下文 | 核心职责 | 租户模型中的角色 |
|---|---|---|
| 租户管理上下文 | 租户的创建、激活、停用、删除 | 定义租户实体、租户状态、租户配置 |
| 身份与访问上下文 | 用户认证、角色分配、权限校验 | 管理租户内用户、角色、权限策略 |
| 可观测性数据上下文 | 指标、日志、链路的采集与存储 | 按租户标签隔离数据,执行配额限制 |
| 计费与配额上下文 | 资源用量统计、计费计算、配额预警 | 跟踪租户的资源消耗与剩余额度 |
实践要点:每个限界上下文应有独立的代码仓库、数据库模式(Schema)或至少独立的表前缀。上下文之间通过领域事件(Domain Event)或防腐层(Anti-Corruption Layer)进行通信,避免直接耦合。
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”),则应该是值对象。
5. 在租户隔离方案中的综合应用
将上述概念组合起来,可以构建出清晰的租户模型架构:
- 限界上下文将系统划分为租户管理、身份访问、可观测性数据、计费配额四个独立领域,每个领域拥有自己的实体和值对象。
- 实体(如Tenant、User、Workspace)负责承载业务逻辑和状态变更,通过领域事件通知其他上下文。
- 值对象(如TenantId、QuotaLimit、TimeRange)用于封装描述性概念,确保类型安全和业务规则内聚。
- 所有上下文通过租户ID(TenantId值对象)作为全局关联键,实现数据隔离与跨上下文查询。
常见陷阱:不要将数据库主键(自增ID)直接作为领域模型中的实体标识。数据库主键是技术实现细节,而实体标识是业务概念。推荐使用UUID或业务编码作为实体ID,避免与数据库耦合。
通过以上建模,可观测性租户隔离方案获得了清晰的领域边界、稳定的实体生命周期管理以及高内聚的值对象封装,为后续的架构实现和可观测性埋点奠定了坚实的模型基础。