【问题标题】:Meeting and invitations / How to model invitations mechanism in a DDD context?会议和邀请/如何在 DDD 上下文中建模邀请机制?
【发布时间】:2016-12-05 09:21:24
【问题描述】:

在领域驱动设计的背景下:

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

用户可以创建一个名为“MyMeeting”的会议,并且他可以通过一些专门的“邀请”来邀请他选择的用户。

我阅读了 Vaughn Vernon 的书 (IDDD),并得出结论,MeetingInvitation 之间不存在不变量

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

  • Meeting(身份证、姓名等)
  • Invitation(meetingId,发生在,接收者(目标用户))

我意识到可能会同时发送多个邀请。
所以我想在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 违规但保持整体不那么丑陋。

你怎么看?

【问题讨论】:

  • 您能演示一下您的 sn-p 如何启用单个查询来提交所有邀请吗?伪代码就够了
  • 我正在使用 Neo4J,并且在 Neo4j 中,有一些很好的关键字用于 Cypher 查询,旨在遍历一些数组(在本例中为邀请列表),整个查询过程完全相同。 (类似于我们可以用 PL/SQL 做的事情)
  • 如果你给我一个例子,我可能会编辑我的答案,以便在封装的邀请列表中使用它
  • 是的,实际上它非常微妙,但它与您的解决方案的本质非常相似,我可以确认。目标是适应/保持 DDD 组件的形态。
  • addAll 方法的正确实现是不关心业务规则的。如果邀请是独立的聚合,那么存储库应该这样对待它们。在循环中调用 add 或调用 addAll 之间不应有不同的语义。 List 邀请 = meeting.inviteAll(invitees); repository.addAll(邀请);

标签: java architecture domain-driven-design aggregateroot


【解决方案1】:

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

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 技术组件(存储库就是其中之一)
  • @Mik378 对我来说,在可能包含来自不同会议的邀请的列表中混合邀请是没有意义的。这是错误的,这就是为什么我提出了一个解决方案来确保它们都是一致的
  • @Mik378 再次,名称无关紧要。你提出的是我在我的问题中用不同的名字实现的
【解决方案2】:

你确定你的设计是合理的吗?您可能正试图将稍微倾斜的设计硬塞进 DDD 语言中。

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

整理其中一些内容可能会为您提供有关如何解决问题的线索。也许你可以拥有Meeting 1-* Participant,它由Invitation 表示,就像Order 1-* ProductOrderItem 表示一样。

【讨论】:

  • 参与是另一个概念,在我的领域中,与 OP 不同。会议和参与嵌套实体确实具有不变性(如预期参与者的最大数量)。我的 OP 是关于邀请的。参与由接受邀请触发。我们可以发送 100 个邀请,但只有 10 个可用位置。因此,接受邀请的前 10 位用户将占据席位。
  • 啊,这就是知道域名如此重要的原因:) --- 似乎邀请将成为Meeting 的一部分。没有Meeting 的邀请真的有意义吗?甚至MeetingInvitation 也可能存在于可以设置HasBeenAccepted 的位置。 Meeting 可能具有Open / Closed / Cancelled 状态以及MaximumNumberOfSeats 或类似的状态。看起来两者之间的关系似乎更密切,也许应该以这种方式建模,因为您似乎必须跟踪每次会议的这些邀请。
  • 邀请和会议之间没有不变量。它们应该是过渡独立的。另外,如果我想在之后添加第 809 个邀请(不强制立即发送邀请),我不想加载所有邀请集合只是为了添加它。它会无缘无故地消耗内存。
  • 我可以向过去的会议添加邀请吗?订满的会议怎么样?我可以向同一个人发送 3 个邀请吗?也许没有真正的不变量,在这种情况下您可以任意添加邀请,当然。拥有大量项目将需要重新考虑设计。也许是MeetingBlock 之类的。同样,有人可能会争辩说,将第 809 项添加到 Order 会浪费内存,不是吗? :) 无论如何,这些只是设计中要考虑的一些事情。不过,看来你已经下定决心了,那就去吧;)
【解决方案3】:

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

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

repository.addAll(invitations);

这里的inviteAll 方法封装了确保所有邀请都链接到同一个会议所必需的业务逻辑。

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

例如,您可以循环所有邀请,并使用Map 将它们按meetingId 分组。

【讨论】:

  • 好答案,完全同意。这正是我今天早上最终所做的。在这种情况下,性能优化与设计一致性正交,不应影响它。
猜你喜欢
  • 1970-01-01
  • 2018-10-03
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多