Mar*_*ira 4 java domain-driven-design kotlin
我对 DDD 有点陌生,即使在阅读了蓝色和红色的书之后,我仍然对如何将一些原则转换为代码有一些疑问,特别是使用 Kotlin 和 Java。
例如,我确定接收一些参数的客户端聚合根需要它来创建名称和地址:
class Client: AggregateRoot {
var clientId: ClienteId
var name: Name
var address: Address
constructor(name: Name,address: Address) : super(){
// validations ....
this.name = name
this.address = address
}
Run Code Online (Sandbox Code Playgroud)
简单部分:为了创建一个新的客户端,我在 RS 服务中收到一个 DTO 并尝试创建一个传递上述参数的新客户端类,如果一切正常且所有规则都满足我将客户端的新实例发送到存储库,非常直接前进。
clientRepository.store(client)
Run Code Online (Sandbox Code Playgroud)
其他部分:我需要搜索我的客户端以更改地址,因此我将 id 发送到存储库并在数据库中找到客户端,然后我需要将数据库实体转换为聚合根并返回给调用者。
override fun getById(id: Long): Client {
val clientEntity = em.find(...)
val client: Client(.....) //But I need another constructor with ClientId
return client
}
Run Code Online (Sandbox Code Playgroud)
然后我需要一个新的构造函数来接收更多的参数,比如 ClientId
constructor(clientId: ClienteId,name: Name,address: Address) : super(){
Run Code Online (Sandbox Code Playgroud)
问题是每个服务都可以调用这个新的构造函数并创建一个不正确的聚合根实例,所以我的问题是:
另一个例子是,如果我不需要每次创建客户端时都传递地址,而是在另一种方法之后传递地址,例如:
client.addAddress(address)
Run Code Online (Sandbox Code Playgroud)
但是在这两种情况下,我都需要从数据库中完成整个客户端,因此我需要第二个带有地址参数的构造函数。
因此,问题是如何通过将错误的接口暴露给客户端代码(即应用程序层或表示层),在不破坏其封装的情况下从持久性中重新水化聚合。
我看到了两个解决方案:
使用反射来填充字段。这是大多数 ORM 使用的解决方案,也是最通用的解决方案。它适用于大多数持久性类型,即使存在阻抗不匹配也是如此。一些 ORM 需要注释字段或关系。
向客户端代码公开不同的接口。这意味着您的 Aggregate 实现比接口更大,并且包含仅由基础结构使用的其他初始化方法。
作为伪代码中的示例,您可能有:
// what you want the upper layers to see
interface Client {
void addAddress(address);
}
// the actual implementations
public class ClientAggregate implements Client
{
void rehidrate(clientId,name,address){...}
void addAddress(address){...}
}
public class ClientRepository
{
// this method returns Client (interface)
Client getById(id){
val clientEntity = em.find(...)
val client = new ClientAggregate()
client.rehydrate(clientEntity.id, clientEntity.name, clientEntity.address)
return client //you are returning ClientAggregate but the other see only Client (interface)
}
}
Run Code Online (Sandbox Code Playgroud)
作为旁注,我没有公开构造函数来从域的角度创建聚合。我喜欢有空的构造函数和一个专用的方法,命名为 Ubiquitous language,它创建聚合。原因是不清楚构造函数创建了一个新的聚合。构造函数实例化一个类的新实例;它更多是一个实现细节,而不是一个领域问题。一个例子:
class Client {
constructor(){ //some internal initializations, if needed }
void register(name){ ... }
}
Run Code Online (Sandbox Code Playgroud)