在强调事件的架构中,命令和事件有什么区别?我唯一能看出来的区别是,命令通常由系统外的执行者发起/调用,而事件似乎是由系统内的处理程序和其他代码发起的。然而,在许多示例应用程序中,它们具有不同(但功能上类似)的接口。
在强调事件的架构中,命令和事件有什么区别?我唯一能看出来的区别是,命令通常由系统外的执行者发起/调用,而事件似乎是由系统内的处理程序和其他代码发起的。然而,在许多示例应用程序中,它们具有不同(但功能上类似)的接口。
指令可能会被拒绝。
事件已经发生。
这可能是最重要的原因。在事件驱动体系结构中,毫无疑问引发的事件代表了已经发生的事情。
现在,因为指令是我们想要发生的事情,而事件是已经发生的事情,所以我们在命名这些东西时应该使用不同的动词。这驱动着不同的表示。
我可以看到的是,指令通常是由系统外的执行者发起/调用的, 而事件似乎是由处理程序和其他代码在系统内发起的
这是它们分别被表示的另一个原因。概念上的清晰度。
指令和事件都是消息。但它们实际上是不同的概念,并且应该明确地建模。
事件是过去发生的事实。
命令只是一个请求,因此可能会被拒绝。
命令 | 事件 | |
---|---|---|
目的 | 调用行为 | 某事发生了 |
所有权 | 命令由消费者拥有 | 事件由发布者拥有 |
消费者 | 一个消费者 | 零个或多个消费者 |
发送方 | 许多发送方 | 单个发布者 |
命名 | 动词 | 过去时 |
命令的一个重要特性是它应该由单个接收方处理一次。这是因为命令是您想要在应用程序中执行的单个操作或事务。例如,同一订单创建命令不应被处理多次。这是命令和事件之间的重要区别。事件可以被多次处理,因为许多系统或微服务可能对该事件感兴趣。'msdn'
在研究了一些例子,特别是Greg Young的演讲后(http://www.youtube.com/watch?v=JHGkaShoyNs),我得出结论:命令是多余的。它们只是来自用户的事件,表示他们按下了那个按钮。你应该以与其他事件完全相同的方式存储这些数据,因为它是数据,而你不知道将来是否需要使用它。你的用户添加了该商品,然后又从购物篮中删除了该商品,或者至少尝试过。您以后可能希望使用此信息在稍后的日期提醒用户。
补充以下这些精彩的答案。我想指出与耦合相关的差异。
命令是针对特定处理器的。因此,命令发起者和处理器之间存在某种程度的依赖/耦合。
例如,UserService
在创建新用户时向EmailService
发送“发送电子邮件”命令。
UserService
知道它需要EmailService
,这已经是耦合了。如果EmailService
更改其API模式或出现故障,它会直接影响UserService
功能。
事件不针对任何特定的事件处理程序。因此,事件发布者变得松散耦合。它不关心哪个服务消费其事件。甚至可以有0个事件的使用者也是有效的。
例如,UserService
在创建新用户时发布“User Created Event”。可能EmailService
会消费该事件并向用户发送电子邮件。
这里UserService
不知道EmailService
。它们是完全解耦的。如果EmailService
出现故障或更改业务规则,我们只需要编辑EmailService
即可。
这两种方法都有优点。纯事件驱动的设计很难跟踪,因为它过于松散耦合,特别是对于大型系统而言。而命令密集的架构具有高度的耦合性。因此,达到良好平衡是理想的。
希望这有意义。
它们被单独表示是因为它们代表非常不同的事物。如@qstarin所说,命令是可以被拒绝的消息,成功后会产生一个事件。
命令和事件都是Dto(数据传输对象),它们是消息,当创建实体时,它们倾向于看起来非常相似,但从那时起,未必如此。
如果您担心重用,那么您可以使用命令和事件作为(message)负载的信封
class CreateSomethingCommand
{
public int CommandId {get; set;}
public SomethingEnvelope {get; set;}
}
基于命令重新计算状态通常是不可行的,因为它们可能每次被处理时都会产生不同的结果。
例如,想象一个GenerateRandomNumber
命令。每次调用它都会产生一个不同的随机数X。因此,如果您的状态依赖于这个数字,每次从命令历史记录重新计算状态时,您将得到不同的状态。
事件解决了这个问题。当您执行一个命令时,它会产生一系列事件,表示命令执行的结果。例如,GenerateRandomNumber
命令可以生成一个GeneratedNumber(X)
事件,记录生成的随机数。现在,如果您从事件日志中重新计算状态,您将始终获得相同的状态,因为您始终使用特定命令执行生成的相同数字。
换句话说,命令是带有副作用的函数,事件记录了特定命令执行的结果。
注意: 您仍然可以记录命令历史以进行审计或调试。关键是要重新计算状态,您需要使用事件历史记录,而不是命令历史记录。
让我们将函数核心,命令式外壳模式加入其中。事件对应于函数核心,而命令对应于命令式外壳。也就是说,事件驻留在纯净的环境中,而命令则驻留在不纯净的环境中。像任何CLI一样,外壳处理不纯净的部分。
这可以从命令被验证并有时被拒绝来看出。还可以从命令依赖于在事件世界中不允许的操作中看出。例如,在Backgammon游戏中掷骰子涉及发出roll
命令,其生成的结果将被记录在rolled
事件中。这种分离的价值可以进一步体现在源或重放事件的能力上。
虽然实时事件可能会冒泡到外壳并导致发出新的命令,但重放事件则不能。它们必须不会引起进一步的副作用。