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

DDD实战:如何在Kotlin中对集成层隐藏聚合根特定构造方法

嘿,我太懂这种感觉了——DDD的理论书读起来通透得很,但真要落地写代码,就总觉得哪里卡壳。针对你说的「隐藏聚合根构造方法,不让集成层直接调用」的问题,咱们从Java和Kotlin两种语言分别聊聊具体实现,完全贴合DDD的封装原则。

核心思路:用工厂方法替代直接构造

DDD里聚合根的创建逻辑必须完全封装,不能让外部(比如API层、Repository层)直接通过new来实例化——这样才能保证聚合根的业务不变量(比如Name非空、Address有效)始终被遵守。咱们的目标就是把构造方法藏起来,只对外暴露安全的「创建入口」。


Java实现方案

  1. 把构造方法设为私有(或包私有)
    直接禁止外部类调用构造方法,只有聚合根内部或同包的可信类(比如Repository)能访问。
  2. 在聚合根内部写静态工厂方法
    对外提供create()这类公开方法,在方法里完成参数校验、生成ID等逻辑,最后返回合法的聚合根实例。

代码示例:

public class Client extends AggregateRoot {
    private final ClientId clientId;
    private final Name name;
    private final Address address;

    // 私有构造方法:只有内部能调用
    private Client(ClientId clientId, Name name, Address address) {
        // 在这里强制执行业务校验
        if (name == null || name.getValue().isBlank()) {
            throw new IllegalArgumentException("客户名称不能为空");
        }
        if (address == null || !address.isValid()) {
            throw new IllegalArgumentException("客户地址不合法");
        }
        this.clientId = clientId;
        this.name = name;
        this.address = address;
    }

    // 公开的工厂方法:业务层/集成层只能通过这个方法创建Client
    public static Client create(Name name, Address address) {
        // 生成聚合根ID(可以用UUID、雪花算法,或者调用领域服务生成)
        ClientId clientId = new ClientId(UUID.randomUUID().toString());
        return new Client(clientId, name, address);
    }

    // 可选:给Repository用的包私有构造(从数据库加载时跳过部分校验)
    Client(ClientId clientId, Name name, Address address, boolean isLoadedFromDb) {
        // 持久化的数据已经是合法的,无需重复校验
        this.clientId = clientId;
        this.name = name;
        this.address = address;
    }

    // 其他业务方法...
}

Kotlin实现方案

Kotlin的语法更简洁,用private constructor直接锁定主构造,再通过伴生对象提供工厂方法:

class Client private constructor(
    val clientId: ClientId,
    val name: Name,
    val address: Address
) : AggregateRoot() {

    companion object {
        // 公开的工厂方法:外部唯一的创建入口
        fun create(name: Name, address: Address): Client {
            // Kotlin用require函数做参数校验,更简洁
            require(name.value.isNotBlank()) { "客户名称不能为空" }
            require(address.isValid()) { "客户地址不合法" }

            val clientId = ClientId(UUID.randomUUID().toString())
            return Client(clientId, name, address)
        }

        // 可选:给Repository用的internal方法(同模块可见)
        internal fun fromPersistence(clientId: ClientId, name: Name, address: Address): Client {
            // 从数据库加载时跳过校验,直接返回实例
            return Client(clientId, name, address)
        }
    }

    // 其他业务方法...
}

额外的DDD小提醒

  • 如果聚合根的创建逻辑特别复杂(比如需要调用多个领域服务、处理多步规则),可以把工厂方法抽成单独的ClientFactory类,但简单场景下放在聚合根内部更紧凑。
  • 集成层(比如REST API、Repository)永远只能通过工厂方法创建/加载聚合根,绝对不能直接调用构造方法——这是保证聚合根业务完整性的关键。

内容的提问来源于stack exchange,提问作者Marco Teixeira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:33:50