会议和邀请/如何在 DDD 上下文中模拟邀请机制?

Meeting and invitations / How to model invitations mechanism in a DDD context?

在 domain-driven 设计的背景下:

我的领域是关于会议和邀请发送。
一个例子:

用户可以创建一个名为 "MyMeeting" 的会议,他可以通过一些专用的 "invitations".

邀请他选择的用户

我读了 Vaughn Vernon 的书 (IDDD),我得出的结论是 MeetingInvitation 之间没有不变性

所以,我开始创建两个不同的聚合根:

我发现可能一次发送多个邀请
所以我考虑在 InvitationRepository 中创建一个特殊查询,旨在一次保存所有邀请:

void addAll(List<Invitation> invitations)

问题在于它引入了与 Invitation 聚合结构的不一致。
解释:
该结构涉及每个 Invitation 都有自己的关联 meetingId.
鉴于 addAll 旨在添加关于同一 MeetingId 的多个邀请;换句话说:每个邀请共有。

因此,由于当前的结构,如果我想避免多个数据库查询,那么使用这种实现会很丑陋:

void addAll(List<Invitation> invitations) {

    //meetingId supposed to be the same across those invitations, 
    //an ugly way to retrieve it would be : 
    MeetingId meetingId = invitations.get(0).getMeetingId(); 
    Date occurredOn = invitations.get(0).getOccurredOn();
    List<User> receivers = new ArrayList();

    for(invitation: Invitations) {
      receivers.add(invitation.getReceiver());
    }

    //then perform the query saving all invitations at once
    //=> one invitation for each receiver
}    

处理一次保存多个 Invitations 的情况的好方法是什么?
是否应该更改域结构?


我想到了 InvitationRepository 中的那些签名:

void add(List<Invitation> invitations)            
void addAll(List<Invitation> invitations, MeetingId meetingId, DateTime: occurredOn)    

甚至使用名为 Invitations 的包装器:

class Invitations {  //note the plurality
   MeetingId meetingId; 
   DateTime occurredOn;
   List<Invitation> invitations;
   //........ 
} 

=> void addAll(Invitations invitations)
在同一数据库查询中保存 嵌套 邀请(聚合根)。

这样,在 addAll 实现的情况下,我不会从 invitations itself 中选择 meetingIdoccurredOn,导致一种 DRY 违规,但是让整体不那么丑陋。

你怎么看?

为什么不简单地这样做呢?

public class InvitationBulkRequest {
     private final List<Invitation> invitations = new ArrayList<>();
     private final MeetingId meetingId;
     private final DateTime occurredOn

     public InvitationBulkRequest(MeetingId meetingId, DateTime occurredOn) {
         this.meetingId = Objects.requireNonNull(meetingId);
         this.occurredOn = Objects.requireNonNull(occurredOn);
     }

     public void invite(User recipient) {
         invitations.add(new Invitation(meetingId, occurredOn, recipient);
     }

     public void send() {
         invitations.forEach(Invitation::send);
     }
}

这样,您就可以很好地封装所有邀请之间的公共数据,并确保它们都是一致的。

不过你确定你的设计合理吗?您可能正在尝试将稍微偏斜的设计硬塞进 DDD 语言中。

我认为会议有一些不变性。也许我们至少需要 2 名参与者。与一个人开会是相当乏味的。现在我假设邀请有 some 与此相关。难道邀请是用来通知参与者和跟踪接受的机制?

整理其中的一些内容可能会让您了解如何解决您的问题。也许你可以让 Meeting 1-* ParticipantInvitation 表示,就像 Order 1-* ProductOrderItem.

addAll 方法的正确实现是一种不关心业务规则的方法。如果邀请是独立的聚合,那么存储库应该这样对待它们。在循环中调用 add 或调用 addAll 之间不应有不同的语义。

Set<Invitation> invitations = meeting.inviteAll(invitees);           

repository.addAll(invitations);

此处 inviteAll 方法封装了必要的业务逻辑,以确保所有邀请都链接到同一会议。

如果当所有邀请都共享同一个时可以进行一些持久性优化 meetingId 那么没有什么可以阻止您检查存储库中的所有邀请并查看是否有可能的优化。

例如,您可以循环所有邀请并使用 Map.

meetingId 对它们进行分组