如何“加入”准备视图模型时有两个聚合根?

发布于 2024-10-17 20:19:09 字数 734 浏览 7 评论 0原文

假设 BookAuthor 是我的模型中的聚合根。

在读取模型中,我有表AuthorsAndBooks,它是由Book.AuthorId加入的作者和书籍列表,

当触发BookAdded事件时,我想接收Author 数据来创建新的 AuthorsAndBooks 行。

由于 Book 是聚合根,因此有关 Author 的信息不包含在 BookAdded 事件中。我无法包含它,因为 Author root 没有 getter(根据有关 CQRS 和事件溯源的所有示例和帖子的指南)。

通常我会收到关于此问题的两种类型的答案:

  1. 使用事件处理程序中所需的所有数据丰富您的域事件。但正如我所说,我不能为聚合根做到这一点。
  2. 使用视图模型中的可用数据。即从视图模型加载 Author 并使用它来构建 AuthorsAndBooks 行。

最后一个在并发方面存在一些问题。处理 BookAdded 事件时,作者数据在视图模型中不可用。

你用什么方法来解决这个问题?谢谢。

Assume that Book and Author are Aggregate Roots in my model.

In read model i have table AuthorsAndBooks which is a list of Authors and Books joined by Book.AuthorId

When BookAdded event is fired i want to receive Author data to create a new AuthorsAndBooks line.

Because Book is an Aggregate Root, information about Author doesn't included in BookAdded event. And i cannot include it because Author root doesn't have getters (according to guidelines of all examples and posts about CQRS and Event Sourcing).

Usually i receive two types of answers on this question:

  1. Enrich your domain event with all data you need in event handlers. But as i said i cannot do it for Aggregates Roots.
  2. Use available data from View Model. I.e. load Author from View Model and use it to build AuthorsAndBooks row.

The last one has some problems with concurrency. Author data can be not available in View Model at the time BookAdded event is handling.

What approach do you use to solve this? Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

九歌凝 2024-10-24 20:19:10

恕我直言,从作者/书籍事件填充读取模型,使用重新排序来处理事件无序的情况(视图处理程序位于其自己的一致性边界内,无论如何都应该处理排序/重复数据删除情况)。

IMHO, populate read model from author/book events, using reordering to handle cases, where events get out of order (view handler is within it's own consistency boundary and should handle ordering/deduplication cases anyway).

青丝拂面 2024-10-24 20:19:10

我要问的第一件事是为什么读取模型中存在并发问题。如果客户端在 AddBook 命令内发送对作者聚合的引用,那么它从哪里获取信息?如果书和作者同时创作,那么你的活动或许可以更加丰富。如果我在这里遗漏了什么,请告诉我。

The first thing I would ask is why there are concurrency issues in the read model. If the client is sending a reference to the author aggregate inside the AddBook command, where did it get the information from? If the book and author are created at the same time, then your event can probably be enriched. Let me know if I'm missing something here.

耳根太软 2024-10-24 20:19:10

最后一个有一些问题
并发。作者数据可以不
当时在视图模型中可用
BookAdded 事件正在处理。

那么“稍后处理事件”呢?因此,您只需将其放在队列的后面,直到该数据可用(可能有 x 次尝试的限制以及每次尝试之间的 x 时间)。

The last one has some problems with
concurrency. Author data can be not
available in View Model at the time
BookAdded event is handling.

What about "handling the event later"? So you simply put it to the back of the queue until this data is available (maybe with a limit of x tries and x time between each try).

看海 2024-10-24 20:19:09

您的问题缺少一些上下文,例如导致此事件的用户场景是什么以及您从什么状态开始?如果您为此案例编写 BDD 测试,它们会是什么样子?了解这一点对于回答你的问题会有很大帮助。

如何解决将一本书与作者联系起来的问题取决于领域。首先,我们假设您的域拥有“作者”的聚合和“书籍”的聚合是有意义的,例如,如果我正在编写一个图书馆系统,我怀疑我是否会有作者的聚合,因为我不关心一个没有书的作家,我关心的是书。

至于缺少吸气剂,值得一提的是,聚合根没有吸气剂,因为它更喜欢 告诉-不要询问 的 OOP 风格。不过,您可以告诉一个 AR 做某事,然后如果需要,该 AR 会告诉另一台 AR 一些事情。重要的部分是 AR 会告诉其他人关于它自己的信息,而不是在您询问它然后传递它的地方编写代码。

最后我想问一下,为什么你添加书的时候没有作者ID呢?那你怎么知道作者是谁呢?我假设你可以执行以下操作(我的代码假设你正在使用流畅的界面来创建 AR,但你可以替换工厂、构造函数,无论你使用什么):

CreateNew.Book()
  .ForAuthor(command.AuthorId)
  .WithContent(command.Content);

现在,也许场景是你正在添加一本书和一个全新作者。我要么将其作为两个单独的命令处理(这可能对您的域更有意义),要么按以下方式处理命令:

var author = CreateNew.Author()
  .WithName(command.AuthorName);

var book = CreateNew.Book()
  .ForAuthor(author.Id)
  .WithContent(command.Content);

也许问题是您在聚合根 Id 上没有 getter,我认为这是不必要的或常见。但是,假设 Id 封装对您很重要,或者您的 BookAdded 事件需要比 Id 提供的更多有关作者的信息,那么您可以执行以下操作:

var author = CreateNew.Author()
  .WithName(command.AuthorName);
var book = author.AddBook(command.Content);

// Adds a new book belonging to this Author
public Book AddBook(BookContent content) {
  var book = CreateNew.Book()
    .ForAuthor(this.Id)
    .WithContent(command.Content);
}

这里我们告诉作者添加一本书,此时它创建书籍的聚合根并将其 Id 传递给书籍。然后我们可以使用 BookAddedForAuthor 事件,其中包含作者的 id。

但最后一个也有缺点,它创建的命令必须通过多个聚合根起作用。我会尽可能地尝试找出为什么第一个示例不适合您。

另外,我无法充分强调您正在寻找的实现是如何由您的特定域上下文决定的。

Your question is missing some context, for example what is the user scenario that leads to this event and what is the state you are starting from? If you were writing the BDD tests for this case, what would they look like? Knowing this would help a lot in answering your question.

How you solve the problem of relating an book to an author is domain dependent. First we are assuming it makes sense for your domain to have an aggregate for Author and an aggregate for Book, for example, if I was writing a library system, I doubt I would have an aggregate for authors, since I don't care about an author without his/her book, what I care about is books.

As for the lack of getters, it's worth mentioning that aggregate roots don't have getters because of a preference for a tell-don't-ask style of OOP. However you can tell one AR to do something which then then tells something to another AR if you need. Part of what is important is the AR tells the others about itself rather than writing code where you ask it and then pass it along.

Finally, I have to ask why you don't have the author's ID at the time you are adding the book? How would you even know who the author is then? I would assume you could just do the following (my code assumes you are using a fluent interface for creation of AR, but you can substitute factories, constructors, whatever you use):

CreateNew.Book()
  .ForAuthor(command.AuthorId)
  .WithContent(command.Content);

Now perhaps the scenario is you are adding a book along with a brand new author. I would either handle this as two separate commands (which may make more sense for your domain), or handle the command the following way:

var author = CreateNew.Author()
  .WithName(command.AuthorName);

var book = CreateNew.Book()
  .ForAuthor(author.Id)
  .WithContent(command.Content);

Perhaps the problem is you have no getter on the aggregate root Id, which I don't believe is necessary or common. However, assuming Id encapsulation is important to you, or your BookAdded event needs more information about the author than the Id along can provide, then you could do something like this:

var author = CreateNew.Author()
  .WithName(command.AuthorName);
var book = author.AddBook(command.Content);

// Adds a new book belonging to this Author
public Book AddBook(BookContent content) {
  var book = CreateNew.Book()
    .ForAuthor(this.Id)
    .WithContent(command.Content);
}

Here we are telling the author to add a book, at which point it creates the aggregate root for the book and passes it's Id to the book. Then we can have the event BookAddedForAuthor which will have the id of the author.

The last one has downsides though, it creates a command that must act through multiple aggregate roots. As much as possible I would try to figure out why the first example isn't working for you.

Also, I can't stress enough how the implementation you are looking for is dictated by your specific domain context.

梦明 2024-10-24 20:19:09

作为一般建议,让事件处理程序具有幂等性,并确保您可以处理无序消息处理(通过重新排队或构建机制来填充丢失的数据)。
另一方面,要质疑为什么作者和书籍是如此绝望的聚合根源。也许你应该在添加一本书时从作者那里复制(f * 是什么“添加一本书”,这是一个命令)。问题在于所有这些编造的例子。下降到现实世界,我怀疑你的问题是否存在。

As a general advice, let the event handlers be idempotent and make sure you can deal with out of order message handling (either by re-queuing or building in mechanisms to fill in missing data).
On the other hand, do question why author and book are such desperate aggregate roots. Maybe you should copy from the author upon adding a book (what the f* is "adding a book", how's that a command). The problem is all these made-up examples. Descend to the real world, I doubt your problem exists.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文