为什么命令和事件要分开表示?
在强调事件的架构中,命令和事件有什么区别?我能看到的唯一区别是命令通常由系统外部的参与者获取/调用,而事件似乎由系统中的处理程序和其他代码获取。然而,在我见过的许多示例应用程序中,它们具有不同(但功能相似)的界面。
What is the difference between commands and events in architectures that emphasize events? The only distinction I can see is that commands are usually sourced/invoked by actors outside the system, whereas events seem to be sourced by handlers and other code in a system. However, in many example applications I have seen, they have different (but functionally similar) interfaces.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
命令可以被拒绝。
事件已经发生了。
这可能是最重要的原因。在事件驱动的架构中,毫无疑问,引发的事件代表已经发生的事情。
现在,因为命令是我们希望发生的事情,而事件是已经发生的事情,所以在命名这些事情时我们应该使用不同的动词。这驱动了单独的表示。
这是它们被单独表示的另一个原因。概念清晰。
命令和事件都是消息。但它们实际上是独立的概念,并且应该明确地对概念进行建模。
Commands can be rejected.
Events have happened.
This is probably the most important reason. In an event-driven architecture, there can be no question that an event raised represents something that has happened.
Now, because Commands are something we want to happen, and Events are something that has happened, we should be using different verbs when we name these things. This drives separate representations.
This is another reason they are represented separately. Conceptual clarity.
Commands and Events are both Messages. But they are in fact separate concepts, and concepts should be modeled explicitly.
事件是过去的事实。
命令只是一个请求,因此可能会被拒绝。
The event is a fact from the past.
The command is only a request, and thus may be refused.
此外,除了此处公开的所有答案之外,事件处理程序还可以在收到事件发生的通知后触发命令。
例如,在创建客户后,您还想初始化一些帐户值等。在客户 AR 将事件添加到 EventDispatcher 并由 CustomerCreatedEventHandler 对象接收后,此处理程序可以触发命令的调度,该命令将执行您需要的任何内容等。
此外,还有 DomainEvents 和 ApplicationEvents。区别只是概念上的。您希望首先分派所有域事件(其中一些可能会产生应用程序事件)。我这是什么意思?
发生 CustomerCreatedEvent 后初始化帐户是 DOMAIN 事件。向客户发送电子邮件通知是一个应用程序事件。
不应该混合它们的原因很清楚。如果您的 SMTP 服务器暂时关闭,并不意味着您的域操作会受到影响。您仍然希望保持聚合的未损坏状态。
我通常将事件添加到聚合根级别的调度程序中。该事件是 DomainEvents 或 ApplicationEvents。可以是两者,也可以是其中很多。一旦我的命令处理程序完成并且我回到堆栈中执行命令处理程序的代码,然后我检查我的调度程序并调度任何其他域事件。如果所有这些都成功,那么我将关闭交易。
如果我有任何应用程序事件,现在就是调度它们的时候了。发送电子邮件不一定需要打开与数据库的连接,也不一定需要打开事务范围。
我有点偏离了最初的问题,但对您来说理解如何在概念上以不同的方式对待事件也很重要。
然后你有 Sagas.... 但这超出了这个问题的范围:)
这有意义吗?
Also, in addition to all the answers here exposed, an event handler may be able to trigger a command as well after receiving notification that an event occurred.
Say for example that after you create a Customer, you also want to initialize some accounts values, etc. After your Customer AR add the event to the EventDispatcher and this is received by a CustomerCreatedEventHandler object, this handler can trigger a dispatch of a command which will execute whatever you need, etc.
Also, there are DomainEvents and ApplicationEvents. The difference is simply conceptual. You want to dispatch all your domain events first (some of them may produce Application Events). What do I mean by this?
Initializing an account after a CustomerCreatedEvent has occurred is a DOMAIN event. Sending an email notification to the Customer is an Application Event.
The reason you shouldn't mix them is clear. If your SMTP server is temporarily down, that doesn't mean that your DOMAIN OPERATION should be affected by that. You still want to keep a non-corrupted state of your aggregates.
I usually add events to my Dispatcher at the Aggregate Root level. This events are either DomainEvents or ApplicationEvents. Can be both and can be many of them. Once my command handler is done and I am back in the stack to the code that execute the Command handler, then I check my Dispatcher and dispatch any other DomainEvent. If all of this is successful, then I close the transaction.
If I have any Application Events, this is the time to dispatch them. Sending an email doesn't necessarily need an open connection to a database nor a transaction scope open.
I strayed away a little bit from the original question but it is also important for you to understand how events may also be conceptually treated differently.
Then you have Sagas.... but that's WAYYYY OFF of the scope of this question :)
Does it make sense?
在完成一些示例,特别是 Greg Young 演示文稿 (http://www.youtube.com/watch ?v=JHGkaShoyNs) 我得出的结论是命令是多余的。它们只是来自您的用户的事件,他们确实按下了该按钮。您应该以与其他事件完全相同的方式存储它们,因为它是数据,并且您不知道是否要在将来的视图中使用它。您的用户确实添加了该商品,然后又从购物篮中删除了该商品,或者至少尝试这样做。您稍后可能希望使用此信息来提醒用户这一点。
After working through some examples and especially the Greg Young presentation (http://www.youtube.com/watch?v=JHGkaShoyNs) I've come to the conclusion that commands are redundant. They are simply events from your user, they did press that button. You should store these in exactly the same way as other events because it is data and you don't know if you will want to use it in a future view. Your user did add and then later remove that item from the basket or at least attempt to. You may later want to use this information to remind the user of this at later date.
只是为了补充这些精彩的答案。我想指出耦合方面的差异。
命令针对特定处理器。因此,命令发起者和处理器之间存在一定程度的依赖/耦合。
例如,
UserService
在创建新用户时会向EmailService
发送“发送电子邮件”命令。事实上,
UserService
知道它需要EmailService
,这已经是耦合了。如果EmailService
更改其 API 架构或出现故障,则会直接影响UserService
功能。事件不针对任何特定的事件处理程序。因此,事件发布者变得松散耦合。它不关心什么服务消耗其事件。事件的 0 个消费者甚至是有效的。
例如,
UserService
在创建新用户时会发布“用户创建事件”。EmailService
可能可以使用该事件并向用户发送电子邮件。这里,
UserService
不知道EmailService
。他们是完全脱钩的。如果EmailService
出现故障,或者更改业务规则,我们只需编辑EmailService
这两种方法都有优点。纯粹的事件驱动架构设计很难跟踪,因为它的耦合过于松散,尤其是在大型系统上。而命令重架构具有高耦合度。因此,良好的平衡是理想的。
希望这是有道理的。
Just to add to these great answers. I'd like to point out differences with regards to coupling.
Commands are directed towards a specific processor. Thus there is some level of dependence/coupling with the Command initiator and the processor.
For example, a
UserService
upon creating a new user sends a "Send Email" Command to theEmailService
.The fact that the
UserService
knows that it needs theEmailService
, that is already coupling. IfEmailService
changes its API schema or goes down, it directly affects theUserService
function.Events are not directed towards any specific event handler. Thus the event publisher becomes loosely coupled. It does not care what service consumes its event. It's even valid to have 0 consumer of an Event.
For example, a
UserService
upon creating a new user publishes a "User Created Event". Potentially anEmailService
can consume that event and sends an email to the user.Here the
UserService
is not aware of theEmailService
. They are totally decoupled. If theEmailService
goes down, or changes business rules, we only need to edit theEmailService
Both approaches have merits. A purely Event Driven Architectural design is harder to track since it is too loosely coupled, especially on large systems. And a Command heavy Architecture have high level of coupling. So a good balance is ideal.
Hope that makes sense.
它们分别代表,因为它们代表非常不同的事物。
正如 @qstarin 所说,命令是可以拒绝的消息,成功后将产生一个事件。
命令和事件是Dto,它们是消息,它们在创建实体时看起来非常相似,但从那时起,就不一定了。
如果您担心重用,那么您可以使用命令和事件作为(消息)有效负载的信封
,但是,我想知道的是您为什么问:D,即您有太多命令/事件吗?
They are represented separetly because they represent very different things.
As @qstarin said commands are messages that can be rejected, and that on success will produce an event.
Commands and events are Dtos, they are messages, and they tend to look very similar when creating and entity, however from then on, not necessarily.
If you are worried about reuse, then you could use commands and events as envelopes to your (messge) payload
however, what I d like to know is why are you asking :D ie do you have too many commands/events?
除了上面提到的概念差异之外,我认为还存在与常见实现相关的另一个差异:
事件通常在需要轮询事件队列的后台循环中进行处理。任何有兴趣对事件采取行动的一方通常都可以注册一个回调,该回调作为事件队列处理的结果而被调用。因此一个事件可能是一对多。
命令可能不需要以这种方式处理。命令的发起者通常可以访问命令的预期执行者。例如,这可以采用发送给执行器的消息队列的形式。因此,命令旨在用于单个实体。
In addition to the conceptual differences mentioned above, I think there is another difference related to common implementations:
Events are typically processed in a background loop that needs to poll the event queues. Any party interested in acting on the event may, usually, register a callback that is called as a result of the event queue processing. So an event may be one to many.
Commands may not need to be processed in such a manner. The originator of the command will typically have access to the intended executor of the command. This could be, for example, in the form of a message queue to the executor. Thus a command is intended for a single entity.
您无法根据命令重新计算状态,因为通常每次处理它们时都会产生不同的结果。
例如,想象一个
GenerateRandomNumber
命令。每次调用它时,都会产生不同的随机数 X。因此,如果您的状态取决于该数字,则每次从命令历史记录重新计算状态时,您都会得到不同的状态。事件解决了这个问题。当您执行命令时,它会产生一系列代表命令执行结果的事件。例如,
GenerateRandomNumber
命令可以生成一个GenerateNumber(X)
事件来记录生成的随机数。现在,如果您从事件日志中重新计算状态,您将始终获得相同的状态,因为您将始终使用由特定命令执行生成的相同数字。换句话说,命令是具有副作用的函数,事件记录命令的特定执行的结果。
注意:
您仍然可以记录命令历史记录以用于审核或调试目的。要点是要重新计算状态,您使用事件历史记录,而不是命令历史记录。
You cannot recompute a state based on commands, because in general they can produce different outcomes each time they are processed.
For example, imagine a
GenerateRandomNumber
command. Each time it's invoked it will produce a different random number X. Thus, if your state depends on this number, each time you recompute your state from the commands history, you'll get a different state.Events solve this problem. When you execute a command, it produces a sequence of events that represent the outcome of the command execution. For example, the
GenerateRandomNumber
command could produce aGeneratedNumber(X)
event that logs the generated random number. Now, if you recompute your state from the events log, you'll always get the same state, because you'll always use the same number that was generated by a particular execution of the command.In other words, commands are functions with side-effects, events record the outcome of a particular execution of a command.
Note:
You can still record a history of commands for audit or debugging purposes. The point is that to recompute the state, you use the history of events, not the history of commands.
我认为昆汀-桑汀的答案需要补充的是:
来源。
I think something to add to quentin-santin's answer is that they:
Source.
让我们将功能核心、命令式 shell 模式融入其中。事件与功能核心相关,命令与命令式 shell 相关。也就是说,事件存在于纯粹之中,命令存在于不纯粹之中。与任何 CLI 一样,shell 会处理杂质。
这可以从命令被验证并且有时被拒绝中看出。还可以看出,命令可以依赖事件世界中不允许的操作。例如,双陆棋游戏中的掷骰子涉及发出
roll
命令,其生成结果将在rolled
事件中记录。这种分离的价值可以进一步体现在获取或重播事件的能力上。虽然实时事件可能会冒泡到 shell 并导致发出新命令,但重播事件却不能。它们不得引起进一步的副作用。
Let's throw the functional core, imperative shell pattern into the mix. Events correlate to the functional core and commands to the imperative shell. That is, events reside in the pure and commands in the impure. The shell, like any CLI, handles the impurities.
This can be seen in that commands are validated and sometimes rejected. It can also be seen in that commands can rely on operations not permitted in the world of events. For example, rolling dice in a game of Backgammon involves issuing a
roll
command and its result when generated would be memorialized in arolled
event. The value of this separation can be further seen in the ability to source or replay events.While live events may bubble up to the shell and cause new commands to be issued, replayed events cannot. They must cause no further side effects.
扩展@LeviFuller评论中的概念,我还使用命令和事件模式分别表示“恰好一个”和“0个或多个”侦听器,但与@froi答案不同,两者仍然是解耦的。
在这种架构中,命令发布者仍然不知道谁将处理命令(就像事件一样)。不同之处在于,发布者确信有人将处理该命令。因此,虽然您可以将任何命令建模为事件,但这样做是不可取的,因为发布者没有这种保证。
当考虑命令处理器之间的转换时,这一点变得尤其重要。转变可能在很多场景下发生——例如,如果命令处理器是一个网络服务,它可能需要停机维护;或者,在基于 UI 的应用程序中,命令处理器可以代表用户所在的屏幕,并且用户正在移动到不同的屏幕。
在转换期间,您通常希望确保传入的命令仅由新处理器处理。旧处理器将尽快取消注册,这意味着在没有处理器注册该命令类型时可能会存在间隙 - 并且命令中心(类似于事件中心)应维护在此期间接收到的命令队列。这还可能包括当时部分处理的命令,因为旧处理器应在注销自身后重新提交“仍要做的工作”。
使用事件这一切都是不可能的。如果没有人在监听某个事件,则意味着没有人关心它,这没关系。在这种情况下,事件不需要排队。如果事件订阅者在开始关闭时尚未完成事件处理,则通常无需重新提交“仍需完成的工作”,因为它不再相关。事件是“一劳永逸”的,因为在提交事件后不会发生什么,但是使用(有效的)命令,即使在转换期间,也会期望执行某些操作。
Expanding on the concepts from @LeviFuller comment, I also used command and event patterns to represent "exactly one" and "0 or more" listeners respectively, but in a different way from @froi answer, where both are still decoupled.
In this architecture, a command publisher still does not know who is going to process the command (much like with events). The difference is that the publisher is assured that someone will process the command. So while you could model any command as an event, it would be undesirable to do so, because the publisher doesn't have that assurance.
This becomes especially important when considering the transition between command processors. A transition may occur in many scenarios - for example, if the command processor is a network service, it may need to go down for maintenance; alternatively, in a UI based application, the command processor could represent the screen the user is on, and the user is moving to a different screen.
During a transition, you typically want to ensure that commands coming in are only processed by the new processor. The old processor would deregister itself as soon as possible, meaning that there may be a gap while no processor is registered for the command type - and the command hub (analogous to an event hub) should maintain a queue of commands received during this time. This may also include partially processed commands from that time, as the old processor should resubmit the "work still to do" after deregistering itself.
None of this would be possible using events. If nobody is listening to an event, it means nobody cares about it, and that's okay. Events would not need to be queued in that case. If an event subscriber had not finished processing an event at the time it begins to shut down, there's typically no need to resubmit the "work still to do" as it's no longer relevant. Events are "fire and forget" in that there is no expectation of what happens after they are submitted, but with a (valid) command there is an expectation that something will be done, even during a transition.