3. 领域驱动设计基础:实体、值对象、聚合、领域服务、仓储模式

领域驱动设计(Domain-Driven Design, DDD)为构建复杂业务系统提供了战略与战术层面的指导。在《Go权限审计网关领域边界》课程中,DDD的战术模式是定义权限模型、审计规则和网关核心逻辑的基石。本章将深入剖析实体、值对象、聚合、领域服务与仓储模式,并结合Go语言的实现特性进行讲解。

3.1 实体(Entity)

实体是具有唯一标识且在其生命周期内状态会持续变化的对象。在权限审计网关中,用户(User)、角色(Role)、资源(Resource) 都是典型的实体。判断一个对象是否为实体的核心标准是:是否拥有一个不随属性变化而改变的ID。

特征 说明 Go实现示例
唯一标识 使用UUID或自增ID,不可变 ID string
可变属性 姓名、邮箱、状态等可被修改 Name string
相等性 通过ID判断,而非属性值 a.ID == b.ID
// User 实体定义
type User struct {
    ID        string
    Name      string
    Email     string
    Status    UserStatus
    CreatedAt time.Time
}

// 实体相等性比较
func (u *User) Equals(other *User) bool {
    return u.ID == other.ID
}
实践要点:在Go中,实体通常作为结构体(struct)实现。避免将实体直接暴露给外部层,建议通过仓储(Repository)接口进行持久化操作。

3.2 值对象(Value Object)

值对象通过属性值来定义自身,没有唯一标识,且是不可变的。在权限审计场景中,IP地址、时间范围、权限策略规则 等适合建模为值对象。值对象的核心价值在于封装复杂概念,消除“基本类型偏执”。

// IPRange 值对象:表示一个IP地址段
type IPRange struct {
    Start net.IP
    End   net.IP
}

// 值对象不可变:返回新实例
func (r IPRange) Contains(ip net.IP) bool {
    // 比较逻辑
    return bytes.Compare(ip, r.Start) >= 0 && bytes.Compare(ip, r.End) <= 0
}

// 值对象相等性:比较所有属性
func (r IPRange) Equals(other IPRange) bool {
    return r.Start.Equal(other.Start) && r.End.Equal(other.End)
}
对比维度 实体 值对象
标识 有唯一ID 无ID,由属性定义
可变性 可变 不可变
生命周期 独立存在 依附于实体
相等性 ID相等 所有属性相等
权限网关示例 用户、角色、资源 IP范围、时间窗口、策略条件

3.3 聚合(Aggregate)与聚合根(Aggregate Root)

聚合是一组相关对象的集合,外部只能通过聚合根(Aggregate Root)访问聚合内部的对象。聚合保证了数据的一致性和业务不变量的保护。在权限审计网关中,权限策略聚合 是一个典型例子:聚合根为 Policy,内部包含多个 Rule(值对象)和 TargetResource(实体)。

// Policy 聚合根
type Policy struct {
    ID          string
    Name        string
    Description string
    Rules       []Rule          // 值对象集合
    Targets     []Resource      // 实体集合
    Version     int
}

// 聚合内方法:保证业务不变量
func (p *Policy) AddRule(rule Rule) error {
    // 校验规则是否重复
    for _, r := range p.Rules {
        if r.Equals(rule) {
            return ErrDuplicateRule
        }
    }
    p.Rules = append(p.Rules, rule)
    p.Version++
    return nil
}
聚合设计原则:
  • 聚合内部通过聚合根保证一致性,外部不能直接修改内部对象。
  • 一次事务只修改一个聚合实例。
  • 聚合应尽量小,避免性能瓶颈。
权限策略聚合结构 Policy (聚合根) TargetResource (实体) Rule (值对象) AuditLog (值对象) 聚合规则 • 外部只能通过 Policy 访问内部对象 • 一次事务只修改一个 Policy 聚合 • Rule 和 AuditLog 作为值对象,不可变 • TargetResource 作为实体,通过 Policy 管理生命周期

3.4 领域服务(Domain Service)

当某个业务操作无法自然地归属于某个实体或值对象时,应将其建模为领域服务。领域服务是无状态的,它协调多个聚合或外部系统完成一个业务用例。在权限审计网关中,权限校验服务 是一个典型的领域服务。

// AuthorizationService 领域服务:负责权限校验
type AuthorizationService struct {
    policyRepo PolicyRepository
    userRepo   UserRepository
}

func (s *AuthorizationService) CheckAccess(userID string, resourceID string, action Action) (bool, error) {
    user, err := s.userRepo.FindByID(userID)
    if err != nil {
        return false, err
    }
    policies, err := s.policyRepo.FindByUserRole(user.RoleID)
    if err != nil {
        return false, err
    }
    for _, policy := range policies {
        if policy.Matches(resourceID, action) {
            return true, nil
        }
    }
    return false, nil
}
领域服务 vs 应用服务:领域服务包含业务逻辑和规则,应用服务负责流程编排、事务管理和基础设施调用。在Go项目中,领域服务应放在 internal/domain/service 包中。

3.5 仓储模式(Repository Pattern)

仓储模式为聚合提供了一种类似集合的访问接口,将领域层与持久化技术解耦。在Go中,仓储通常定义为接口,实现放在基础设施层。仓储只负责聚合根的存取,不暴露内部实体。

// PolicyRepository 仓储接口
type PolicyRepository interface {
    Save(policy *Policy) error
    FindByID(id string) (*Policy, error)
    FindByUserRole(roleID string) ([]*Policy, error)
    Delete(id string) error
}

// PostgresPolicyRepository 仓储实现(基础设施层)
type PostgresPolicyRepository struct {
    db *sql.DB
}

func (r *PostgresPolicyRepository) Save(policy *Policy) error {
    // 使用事务保存聚合根及其内部对象
    tx, _ := r.db.Begin()
    // 保存 Policy 表
    // 保存 Rules 表
    // 保存 Targets 表
    return tx.Commit()
}
仓储原则 说明
面向聚合 每个聚合根对应一个仓储接口
接口在领域层 领域层定义接口,基础设施层实现
事务边界 仓储方法内部保证聚合的一致性
不暴露内部 仓储不提供直接访问内部实体的方法
DDD 分层架构与仓储模式 领域层 (Domain) 实体 · 值对象 · 聚合根 · 领域服务 · 仓储接口 PolicyRepository (interface) 基础设施层 (Infrastructure) PostgresPolicyRepository · RedisCache · gRPC Client 实现领域层定义的仓储接口 依赖倒置原则 领域层定义仓储接口,基础设施层实现。领域层不依赖任何外部框架或数据库。

3.6 在Go权限审计网关中的实践建议

  • 实体设计:使用 string 类型的UUID作为ID,避免暴露自增ID给外部。
  • 值对象:优先使用不可变结构体,方法返回新实例而非修改原对象。
  • 聚合边界:权限策略聚合应包含规则和资源引用,但不要包含用户信息,保持聚合小巧。
  • 领域服务:权限校验、审计日志记录等跨聚合操作应放在领域服务中。
  • 仓储实现:使用Go的 database/sql 或 gorm 实现,但接口定义在领域层。
Go语言特有优势:Go的接口隐式实现特性使得仓储模式实现非常自然。领域层定义接口,基础设施层无需显式声明 implements,降低了耦合度。