You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

仓储层是否违反领域驱动设计(DDD)的聚合根封装规则?

解决聚合根创建与重建的封装冲突问题

核心矛盾很明确:客户端需要创建符合业务规则的新聚合根(自动生成ID、初始化默认值、验证输入),而仓储需要从持久化数据重建已有聚合根(复用存储的ID、日期等字段,不需要重新初始化),但要避免客户端拿到重建能力破坏封装。下面是几个落地的解决思路:

1. 包级私有/内部可见的重建方法

把聚合根和仓储放在同一个代码包(模块)内,给聚合根添加一个仅包内可见的重建方法,客户端在包外无法访问这个方法,只有仓储能调用它来从存储数据构建实例。

以Go语言为例:

// 聚合根定义在 order 包内
package order

import (
	"errors"
	"time"
	"github.com/google/uuid"
)

type Order struct {
	ID        string
	CreatedAt time.Time
	CustomerID string
	// 其他业务字段
}

// 公开的New方法:供客户端创建新订单,自动生成ID和创建时间
func NewOrder(customerID string) (*Order, error) {
	if customerID == "" {
		return nil, errors.New("customer ID cannot be empty")
	}
	return &Order{
		ID:        uuid.NewString(),
		CreatedAt: time.Now(),
		CustomerID: customerID,
	}, nil
}

// 包内可见的reconstruct方法:仅仓储能调用,从存储数据重建订单
func reconstructOrder(id string, createdAt time.Time, customerID string) (*Order, error) {
	if id == "" {
		return nil, errors.New("order ID cannot be empty")
	}
	return &Order{
		ID:        id,
		CreatedAt: createdAt,
		CustomerID: customerID,
	}, nil
}

仓储和聚合根同属order包,直接调用reconstructOrder方法从数据库查询结果构建实例;客户端只能调用公开的NewOrder,无法接触到重建逻辑。

2. 接口隔离客户端访问

给聚合根定义两个接口:一个面向客户端的业务接口,仅暴露业务行为;另一个面向仓储的重建接口,包含重建方法。聚合根实现这两个接口,但客户端只能拿到业务接口类型的实例,无法访问重建方法。

以Java为例:

import java.time.LocalDateTime;
import java.util.UUID;

// 客户端可见的业务接口
public interface Order {
    void addItem(Item item);
    void confirm();
    // 仅暴露业务行为,无创建/重建方法
}

// 仓储可见的重建接口(包级私有)
interface ReconstructableOrder extends Order {
    static ReconstructableOrder reconstruct(String id, LocalDateTime createdAt, CustomerId customerId) {
        return new OrderImpl(id, createdAt, customerId);
    }
}

// 聚合根实现类(包级私有)
class OrderImpl implements ReconstructableOrder {
    private final String id;
    private final LocalDateTime createdAt;
    private final CustomerId customerId;
    
    // 私有构造函数,仅内部使用
    private OrderImpl(String id, LocalDateTime createdAt, CustomerId customerId) {
        this.id = id;
        this.createdAt = createdAt;
        this.customerId = customerId;
        if (id == null || createdAt == null || customerId == null) {
            throw new IllegalArgumentException("Invalid order data");
        }
    }
    
    // 公开的工厂方法,供客户端创建新订单
    public static Order newOrder(CustomerId customerId) {
        if (customerId == null) {
            throw new IllegalArgumentException("Customer ID cannot be null");
        }
        return new OrderImpl(UUID.randomUUID().toString(), LocalDateTime.now(), customerId);
    }
    
    // 实现业务方法
    @Override
    public void addItem(Item item) { /* 业务逻辑 */ }
    @Override
    public void confirm() { /* 业务逻辑 */ }
}

客户端依赖Order接口,只能调用newOrder创建实例;仓储依赖ReconstructableOrder接口,调用reconstruct方法重建实例,完全隔离了两种场景的能力。

3. 仓储专属的内部工厂类

把重建逻辑封装在一个仅仓储能访问的工厂类里,比如聚合根的私有内部类,或者与仓储同包的工厂类,专门负责从持久化数据构建聚合根。

以C#为例:

using System;

public interface IOrder
{
    Guid Id { get; }
    DateTime CreatedAt { get; }
    Guid CustomerId { get; }
    void AddItem(Item item);
}

public class Order : IOrder
{
    // 私有构造函数,禁止外部直接创建
    private Order(Guid id, DateTime createdAt, Guid customerId)
    {
        Id = id;
        CreatedAt = createdAt;
        CustomerId = customerId;
        if (id == Guid.Empty) throw new ArgumentException("Order ID cannot be empty");
    }

    // 公开的工厂方法:供客户端创建新订单
    public static IOrder NewOrder(Guid customerId)
    {
        if (customerId == Guid.Empty) throw new ArgumentException("Customer ID cannot be empty");
        return new Order(Guid.NewGuid(), DateTime.UtcNow, customerId);
    }

    // 仓储专属的内部工厂类:仅同程序集可见
    internal static class OrderFactory
    {
        public static IOrder Reconstruct(Guid id, DateTime createdAt, Guid customerId)
        {
            return new Order(id, createdAt, customerId);
        }
    }

    // 业务字段与方法
    public Guid Id { get; }
    public DateTime CreatedAt { get; }
    public Guid CustomerId { get; }
    public void AddItem(Item item) { /* 业务逻辑 */ }
}

// 仓储类与Order同程序集,可访问OrderFactory
public class OrderRepository
{
    private readonly AppDbContext _dbContext;

    public OrderRepository(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IOrder GetById(Guid id)
    {
        var dbData = _dbContext.Orders.Find(id);
        return Order.OrderFactory.Reconstruct(dbData.Id, dbData.CreatedAt, dbData.CustomerId);
    }
}

客户端只能通过NewOrder创建实例,仓储通过内部的OrderFactory重建实例,完全避免了客户端滥用重建能力。

核心思路总结

所有方案的本质都是区分“创建新聚合根”和“重建已有聚合根”两个不同场景,通过语言的访问控制特性(包级私有、内部可见、接口隔离),把重建能力严格限制在仓储层,只向客户端开放符合业务规则的创建入口,从而保证聚合根的封装边界不被打破。

内容的提问来源于stack exchange,提问作者sultan motiri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 21:00:04