CQRS事件来源:验证用户名的唯一性 [英] CQRS Event Sourcing: Validate UserName uniqueness

查看:151
本文介绍了CQRS事件来源:验证用户名的唯一性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们以一个简单的帐户注册示例为例,该流程如下:

Let's take a simple "Account Registration" example, here is the flow:


  • 用户访问网站

  • 单击注册按钮并填写表单,然后单击保存按钮

  • MVC控制器:通过读取ReadModel来验证用户名的唯一性

  • RegisterCommand:再次验证用户名唯一性(这里是问题)

  • User visit website
  • Click "Register" button and fill form, click "Save" button
  • MVC Controller: Validate UserName uniqueness by reading from ReadModel
  • RegisterCommand: Validate UserName uniqueness again (here is the question)

当然,我们可以验证用户名唯一性通过读取MVC控制器中的ReadModel来改善性能和用户体验。但是,我们仍然需要在RegisterCommand中再次验证唯一性,显然,我们不应该在Commands中访问ReadModel。

Of course, we can validate UserName uniqueness by reading from ReadModel in MVC controller to improve performance and user experience. However, we still need to validate the uniqueness again in RegisterCommand, and obviously, we should NOT access ReadModel in Commands.

如果我们不使用事件源,则可以查询域模型,所以没有问题。但是,如果使用事件源,则无法查询域模型,因此如何在RegisterCommand中验证用户名的唯一性?

If we do not use Event Sourcing, we can query the domain model, so that's no a problem. But if we're using Event Sourcing, we are not able to query domain model, so how can we validate UserName uniqueness in RegisterCommand?

注意::用户类具有Id属性,而UserName不是User类的关键属性。使用事件源时,我们只能通过ID获取域对象。

Notice: User class has an Id property, and UserName is not the key property of User class. We can only get the domain object by Id when using event sourcing.

BTW:在要求中,如果输入的UserName已被使用,网站应向访问者显示错误消息对不起,用户名XXX不可用。向访问者显示一条消息,例如:我们正在创建您的帐户,请稍候,我们将稍后通过电子邮件将注册结果发送给您。

BTW: In the requirement, if the entered UserName is already taken, the website should show error message "Sorry, the user name XXX is not available" to the visitor. It's not acceptable to show a message, say, "We are creating your account, please wait, we will send the registration result to you via Email later", to the visitor.

有什么想法吗?非常感谢!

Any ideas? Many thanks!

[UPDATE]

一个更复杂的示例:

要求:

下订单时,系统应检查客户的订购历史,如果他是一位有价值的客户(如果该客户在去年每月至少下达10笔订单,那么他就是有价值的客户),那么我们就可以从该订单中扣除10%。

When placing an order, the system should check the client's ordering history, if he is a valuable client (if the client placed at least 10 orders per month in the last year, he is valuable), we make 10% off to the order.

实现:

我们创建PlaceOrderCommand,在该命令中,我们需要查询订购历史记录以查看客户端是否有价值。但是,我们该怎么做呢?我们不应该在命令中访问ReadModel!正如Mikael 所说所述,我们可以在帐户注册示例中使用补偿命令,但如果在此订购示例中也使用补偿命令,

We create PlaceOrderCommand, and in the command, we need to query the ordering history to see if the client is valuable. But how can we do that? We shouldn't access ReadModel in command! As Mikael said, we can use compensating commands in the account registration example, but if we also use that in this ordering example, it would be too complex, and the code might be too difficult to maintain.

推荐答案

如果使用读取模型验证用户名,则可能会太复杂,并且代码可能很难维护。在发送命令之前,我们正在谈论一个可能发生实际竞争状况的数百毫秒的竞争状况窗口,而在我的系统中这是无法处理的。与处理它的成本相比,它不太可能发生。

If you validate the username using the read model before you send the command, we are talking about a race condition window of a couple of hundred milliseconds where a real race condition can happen, which in my system is not handled. It is just too unlikely to happen compared to the cost of dealing with it.

但是,如果您认为由于某种原因必须处理它,或者只是觉得自己想要要知道如何掌握这种情况,这是一种方法:

However, if you feel you must handle it for some reason or if you just feel you want to know how to master such a case, here is one way:

使用事件源时,不应从命令处理程序或域访问读取模型。但是,您可以使用域服务来监听UserRegistered事件,在该事件中您再次访问读取模型,并检查用户名是否仍然不是重复的。当然,您需要在此处使用UserGuid,以及您刚刚创建的用户可能已经更新了读取模型。如果找到重复项,则您有机会发送补偿命令,例如更改用户名并通知用户该用户名已被使用。

You shouldn't access the read model from the command handler nor the domain when using event sourcing. However, what you could do is to use a domain service that would listen to the UserRegistered event in which you access the read model again and check whether the username still isn't a duplicate. Of course you need to use the UserGuid here as well as your read model might have been updated with the user you just created. If there is a duplicate found, you have the chance of sending compensating commands such as changing the username and notifying the user that the username was taken.

这是解决问题的一种方法。

That is one approach to the problem.

如您所见,这是不可能的以同步的请求-响应方式。为了解决这个问题,只要有什么我们想推送到客户端的东西(如果它们仍处于连接状态),我们就会使用SignalR更新UI。我们要做的是让Web客户端预订包含事件的信息,这些事件包含对客户端立即有用的信息。

As you probably can see, it is not possible to do this in a synchronous request-response manner. To solve that, we are using SignalR to update the UI whenever there is something we want to push to the client (if they are still connected, that is). What we do is that we let the web client subscribe to events that contain information that is useful for the client to see immediately.

更新

更复杂的情况:

For the more complex case:

I会说订单放置不太复杂,因为在发送命令之前,您可以使用读取模型来找出客户是否有价值。实际上,您可以在加载订单时进行查询,因为您可能想向客户表明,他们在下订单之前会获得10%的折扣。只需在 PlaceOrderCommand 中添加折扣,并可能要选择折扣的原因,以便您可以追踪为什么要减少利润。

I would say the order placement is less complex, since you can use the read model to find out if the client is valuable before you send the command. Actually, you could query that when you load the order form since you probably want to show the client that they'll get the 10% off before they place the order. Just add a discount to the PlaceOrderCommand and perhaps a reason for the discount, so that you can track why you are cutting profits.

但是,如果由于某些原因您确实需要在下订单后计算折扣,请再次使用域服务,该服务将监听 OrderPlacedEvent 和在这种情况下,补偿命令可能是 DiscountOrderCommand 之类的东西。该命令将影响Order Aggregate根目录,并且该信息可能会传播到您的读取模型。

But then again, if you really need to calculate the discount after the order was places for some reason, again use a domain service that would listen to OrderPlacedEvent and the "compensating" command in this case would probably be a DiscountOrderCommand or something. That command would affect the Order Aggregate root and the information could be propagated to your read models.

对于重复的用户名情况:

For the duplicate username case:

您可以从域服务发送 ChangeUsernameCommand 作为补偿命令。或更具体的说,这将描述用户名更改的原因,这也可能导致创建Web客户端可以订阅的事件,以便您可以让用户看到用户名是重复的。

You could send a ChangeUsernameCommand as the compensating command from the domain service. Or even something more specific, that would describe the reason why the username changed which also could result in the creation of an event that the web client could subscribe to so that you can let the user see that the username was a duplicate.

在域服务上下文中,我想您还可以使用其他方式来通知用户,例如发送电子邮件,这可能很有用,因为您不能知道用户是否仍处于连接状态。也许通知功能可以由Web客户端订阅的同一事件启动。

In the domain service context I would say that you also have the possibility to use other means to notify the user, such like sending an email which could be useful since you cannot know if the user is still connected. Maybe that notification functionality could be initiated by the very same event that the web client is subscribing to.

在SignalR方面,我使用的是SignalR Hub,用户可以通过它连接当他们加载某种形式。我使用SignalR Group功能,该功能允许我创建一个组,该组命名为我在命令中发送的Guid的值。在您的情况下,这可能是userGuid。然后,我有Eventhandler订阅了可能对客户端有用的事件,当事件到达时,我可以在SignalR组中的所有客户端上调用javascript函数(在这种情况下,只有一个客户端在您的帐户中创建重复的用户名)案件)。我知道这听起来很复杂,但实际上并非如此。我在下午设置了所有东西。 SignalR Github页面上有很棒的文档和示例。

When it comes to SignalR, I use a SignalR Hub that the users connects to when they load a certain form. I use the SignalR Group functionality which allows me to create a group which I name the value of the Guid I send in the command. This could be the userGuid in your case. Then I have Eventhandler that subscribe to events that could be useful for the client and when an event arrives I can invoke a javascript function on all clients in the SignalR Group (which in this case would be only the one client creating the duplicate username in your case). I know it sounds complex, but it really isn't. I had it all set up in an afternoon. There are great docs and examples on the SignalR Github page.

这篇关于CQRS事件来源:验证用户名的唯一性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆