简体   繁体   English

DDD:如何从Kotlin中的集成层隐藏特定的聚合根构造函数

[英]DDD: How to hide specific aggregate root constructors from integration layers in Kotlin

I'm kinda new on DDD and even after read the blue and red book I still have some questions about how to transform some principles to code, specifically using Kotlin and Java. 我对DDD有点新意,甚至在阅读了蓝色和红色书后,我仍然有一些关于如何将一些原则转换为代码的问题,特别是使用Kotlin和Java。

For example, I identify a Client aggregate root that receive some parameters need it for the creation like Name and Address: 例如,我标识了一个客户端聚合根,它接收一些参数需要它来创建,如名称和地址:

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
    }

Easy part: To create a new Client I receive a DTO inside the RS service and try to create a new Client class passing the parameters above, case everything was solid and all rules fulfilled I send the new instance of Client to the repository, pretty straight foward. 简单部分:要创建一个新客户端,我在RS服务中收到一个DTO,并尝试创建一个新的Client类,传递上面的参数,案例一切都很可靠,所有规则都已完成我将Client的新实例发送到存储库,非常直接盼着。

clientRepository.store(client)

Other part: I need to search my Client to change the address so I send the id to the repository and find the Client inside the database then I need to convert the database entity to the aggregate root and return to the caller. 其他部分:我需要搜索我的客户端以更改地址,因此我将id发送到存储库并在数据库中找到Client然后我需要将数据库实体转换为聚合根并返回给调用者。

override fun getById(id: Long): Client {
  val clientEntity = em.find(...)
  val client: Client(.....) //But I need another constructor with ClientId
  return client
}

Then I will need a new constructor one that receive more parameters like the ClientId 然后我将需要一个新的构造函数,它接收更多的参数,如ClientId

constructor(clientId: ClienteId,name: Name,address: Address) : super(){

The problem is that every service can call this new constructor and create a incorrect instance of my aggregation root, so my questions are: 问题是每个服务都可以调用这个新的构造函数并创建一个不正确的聚合根实例,所以我的问题是:

  1. Is there a way to hide the complete constructor just for the repository or specific layers to see. 有没有办法隐藏完整的构造函数只是为了存储库或特定的层看到。 Like in C# when you could use internal. 就像在C#中你可以使用内部。
  2. Is there any solution for Java or Kotlin to not expose this constructor that should be used just on tests and integrations ? Java或Kotlin是否有任何解决方案不公开应该仅用于测试和集成的构造函数?

Another example is if I didn't need the address to be passed every time a client is created but just after in another method like: 另一个例子是,如果我不是每次创建客户端时都需要传递地址,而是在另一个方法之后传递,例如:

client.addAddress(address)

But in both cases I will need to fulfill the entire Client from the database so I will need a second constructor with the address parameter. 但在这两种情况下,我都需要从数据库中完成整个Client,所以我需要第二个带有address参数的构造函数。

So, the problem is how to rehydrate an Aggregate from the persistence without breaking its encapsulation by exposing the wrong interface to the client code (ie the Application layer or the Presentation layer). 因此,问题是如何通过将错误的接口暴露给客户端代码(即应用层或表示层)来从持久性中重新水合聚合而不破坏其封装。

I see two solutions to this: 我看到两个解决方案:

  1. Use reflection to populate the fields. 使用反射填充字段。 This is the solution that most ORMs use and it is also the most generic. 这是大多数ORM使用的解决方案,也是最通用的。 It works for most persistence types, even when there is an impedance mismatch. 它适用于大多数持久性类型,即使存在阻抗不匹配。 Some ORMs need to annotate fields or relations. 一些ORM需要注释字段或关系。

  2. Expose a different interface to the client code. 向客户端代码公开不同的接口。 This means that your Aggregate implementation is larger that the interface and contains additional initialization methods used only by the infrastructure. 这意味着您的Aggregate实现接口更大 ,并包含仅由基础结构使用的其他初始化方法。

As an example in pseudo-code your could have: 作为伪代码的示例,您可以:

// 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)
    }
}

As a side note, I don't expose the constructor to create an Aggregate from the Domain point of view. 作为旁注,我不公开构造函数以从Domain的角度创建聚合。 I like to have empty constructors and a dedicated method, named from the Ubiquitous language , that creates the Aggregate. 我喜欢使用空的构造函数和一个以无处不在的语言命名的专用方法来创建聚合。 The reason is that is not clear that the constructor creates a new Aggregate. 原因是不清楚构造函数是否创建了新的Aggregate。 The constructor instantiate a new instance of a class; 构造函数实例化一个类的新实例; it is more a implementation details than a domain concern. 它更像是一个实现细节而不是域关注。 An example: 一个例子:

class Client {
    constructor(){ //some internal initializations, if needed }
    void register(name){ ... }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM