DDD实战:如何在Kotlin中对集成层隐藏聚合根特定构造方法
嘿,我太懂这种感觉了——DDD的理论书读起来通透得很,但真要落地写代码,就总觉得哪里卡壳。针对你说的「隐藏聚合根构造方法,不让集成层直接调用」的问题,咱们从Java和Kotlin两种语言分别聊聊具体实现,完全贴合DDD的封装原则。
核心思路:用工厂方法替代直接构造
DDD里聚合根的创建逻辑必须完全封装,不能让外部(比如API层、Repository层)直接通过new来实例化——这样才能保证聚合根的业务不变量(比如Name非空、Address有效)始终被遵守。咱们的目标就是把构造方法藏起来,只对外暴露安全的「创建入口」。
Java实现方案
- 把构造方法设为私有(或包私有)
直接禁止外部类调用构造方法,只有聚合根内部或同包的可信类(比如Repository)能访问。 - 在聚合根内部写静态工厂方法
对外提供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
相关产品推荐
相关产品推荐

