加强跨多个集合的严格一致性 [英] Enforce strict consistency spanning multiple aggregates
问题描述
请考虑以下业务要求:
我们有可以玩游戏的玩家。玩家一次只能玩一个游戏。一个游戏需要两个玩家。
We have players which can play games. A player can play only one game at a time. A game needs two players.
系统将包含数百万个玩家,游戏大约需要两分钟。并发问题可能会出现。
The system will contain millions of players, and games take about two minutes. Concurrency issues are likely to emerge.
我们要遵守单笔交易涉及单个汇总的规则。此外,最终的一致性不能导致接受的游戏,该游戏必须由于并发问题而在之后(即使是很短的时间)取消。因此,最终的一致性并不是真正合适的。
We want to comply to the rule that a single transaction involves a single aggregate. Further, eventual consistency must not lead to accepted games which must be cancelled afterwards (even if a short period of time) due to concurrency issues. Thus, eventual consistency is not really appropriate.
我们如何定义集合及其边界来实施这些业务规则?
How do we need to define the aggregates and their boundaries to enforce these business rules?
我构思了两种方法:
1。基于事件的握手
总计 Player
,总计 Game
。
请求游戏时,它会推送 GameRequested
事件。 Player
订阅此事件并响应相应的事件,即 GamePlayerAccepted
或 GamePlayerRejected
。仅当两个 Player
都接受时, Game
才会启动( GameStarted
)。
When a game is requested, it pushed a GameRequested
-event. The Player
s subscribe this event and respond with a corresponding event, either GamePlayerAccepted
or GamePlayerRejected
. Only if both Player
s have accepted, the Game
starts (GameStarted
).
优点:
-
玩家
负责管理自己的与域模型相对应的可用性
- The aggregate
Player
is responsible for managing his own availability which corresponds to the domain model
缺点:
- 启动
游戏的责任
分散在多个聚合中(似乎是伪造-最终一致性) - 很多通信开销
- 需要采取一致性措施,例如如果出现问题,请释放
Player
s
- The responsibility of starting a
Game
is scattered throughout multiple aggregates (it seems like "fake"-eventual-consistency) - Much communication overhead
- Consistency measures needed, e.g. freeing up the
Player
s if something went wrong
2。集合汇总
汇总玩家
,汇总 GamesManager
(带有值对象 ActiveGamePlayers
的集合),总计 Game
。
Aggregate Player
, aggregate GamesManager
(with a collection of value-objects ActiveGamePlayers
), aggregate Game
.
要求 GameManager
启动一个新的 Game
,其中两个给定的播放器
s。 GameManager
能够确保 Player
一次只能播放一次,因为它是单个集合。
The GameManager
is requested to start a new Game
with two given Player
s. The GameManager
is able to ensure that a Player
only plays once at a time since it's a single aggregate.
优点:
- 没有诸如
GamePlayerAccepted
,GamePlayerRejected
等
- No consistency-enforcing events such as
GamePlayerAccepted
,GamePlayerRejected
and so forth
缺点:
- 域模型似乎模糊了
-
播放器
的可用性管理职责转移 - 我们必须确保只有一个
实例GameManager
的创建并引入了域机制,该机制使客户不必担心中介集合 - 独立
Game
-start彼此干扰,因为GameManager
-聚合自身锁定 - 由于
GameManager
-聚合收集所有活跃的游戏玩家,这些玩家将达到数千万
- The domain model seems obscured
- The responsibility of the
Player
to manage availability shifted - We have to ensure that only one instance of
GameManager
is created and introduce domain-mechanisms which let the client not worry about the intermediary-aggregate - Independent
Game
-starts disrupt each other because theGameManager
-aggregate locks itself - Need for performance optimization since the
GameManager
-aggregate collects all active game players which will be tens of millions
似乎这些方法都不适合解决问题。我不知道如何设置边界以确保模型的严格一致性和清晰度以及性能。
It seems like none of these approaches are appropriate to solve the problem. I don't know how to set the boundaries to ensure both strict consistency and clarity of the model, and performance.
推荐答案
我将使用基于事件的握手,这就是我要实现的方式:
据我了解,您将需要 Game
流程作为 Saga
实施。您还必须定义一个 Player
聚合,一个 RequestGame
命令,一个 GameRequested
事件, GameAccepted
事件, GameRejected
事件, MarkGameAsAccepted
命令, MarkGameAsRejected
命令, GameStarted
事件和 GameFailed
事件。
From what I understand you would need a Game
process implemented as a Saga
. You will also have to define a Player
aggregate, a RequestGame
command, a GameRequested
event, a GameAccepted
event, a GameRejected
event, a MarkGameAsAccepted
command, a MarkGameAsRejected
command, a GameStarted
event and a GameFailed
event.
因此,当玩家A
想要与玩家B
,玩家A
收到 RequestGame
命令。如果此玩家正在玩其他游戏,则会抛出 PlayerAlreadyPlaysAGame
异常,否则将引发 GameRequested
事件并更新其内部状态为玩
。
So, when the Player A
want's to play a game with Player B
, Player A
receives the RequestGame
command. If this player is playing something else then a PlayerAlreadyPlaysAGame
exception is thrown, otherwise it raises the GameRequested
event and update it's internal state as playing
.
游戏
传奇抓住了 GameRequested
事件,并将 RequestGame
命令发送到 B玩家
聚合(这是 Player
聚合,其 ID
等于 A
)。然后:
The Game
saga catches the GameRequested
event and send the RequestGame
command to the Player B
aggregate (this is a Player
aggregate with ID
equal to A
). Then:
-
如果
玩家B
正在玩其他游戏(它通过查询其内部播放状态
知道这一点),然后引发GameRejected
事件;Game
传奇抓住了这个事件,并向玩家A $ c发送了
MarkGameAsRejected
命令$ c>;然后玩家A
引发GameFailed
事件,并将其内部状态更新为not_playing
。
If the
Player B
is playing another game (it knows this by querying its internalplaying
state) then it raises theGameRejected
event; theGame
saga catches this event and send aMarkGameAsRejected
command toPlayer A
; thenPlayer A
raises theGameFailed
event and updates its internal state asnot_playing
.
如果玩家B
没有玩其他游戏,则加注 GameAccepted
事件; Game
传奇抓住了这个事件,并将 MarkGameAsAccepted
命令发送给玩家A
总计; 玩家A
然后发出 GameStarted
事件,并将其内部状态更新为正在播放
。
If the Player B
is not playing another game then it raises the GameAccepted
event; the Game
saga catches this event and send the MarkGameAsAccepted
command to Player A
aggregate; Player A
then emits the GameStarted
event and update its internal state as playing
.
为了理解这一点,您应该尝试对用例进行建模,就好像没有计算机会
In order to understand this you should try to model the use-case as if no computers would exist and players would be humans that communicate over printed mail.
此解决方案具有可扩展性,我知道这是必需的。
This solution is scalable and I understand that this is required.
另一种解决方案似乎无法吸引数百万玩家。
第三种解决方案是在SQL表中使用活动参与者的集合或在NoSQL中使用集合,而不使用聚合战术模式。为了确保一致性,将一对播放器设置为活动状态时,可以在支持(低可伸缩性)或两阶段提交(有点丑陋)的地方使用光学标记锁定或事务。
A third solution would be to use a colection of active players in a SQL table or a NoSQL colection, without using the Aggregate tactical pattern. For concurency, when setting a pair of players as active, you could use optimistick locking or transactions where supported (low scalable) or two-phase commits (kind of ugly).
这篇关于加强跨多个集合的严格一致性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!