Enforce strict consistency spanning multiple aggregates

爱⌒轻易说出口 提交于 2019-12-08 10:58:14

问题


Consider the following business requirements:

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?

I conceived two approaches:

1. Event-based handshake

Aggregate Player, aggregate Game.

When a game is requested, it pushed a GameRequested-event. The Players subscribe this event and respond with a corresponding event, either GamePlayerAccepted or GamePlayerRejected. Only if both Players have accepted, the Game starts (GameStarted).

Pros:

  • The aggregate Player is responsible for managing his own availability which corresponds to the domain model

Cons:

  • 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 Players if something went wrong

2. Collection-aggregate

Aggregate Player, aggregate GamesManager (with a collection of value-objects ActiveGamePlayers), aggregate Game.

The GameManager is requested to start a new Game with two given Players. The GameManager is able to ensure that a Player only plays once at a time since it's a single aggregate.

Pros:

  • No consistency-enforcing events such as GamePlayerAccepted, GamePlayerRejected and so forth

Cons:

  • 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 the GameManager-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.


回答1:


I would go with the event-based handshake and this is how I would implement:

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.

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.

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:

  • If the Player B is playing another game (it knows this by querying its internal playing state) then it raises the GameRejected event; the Game saga catches this event and send a MarkGameAsRejected command to Player A; then Player A raises the GameFailed event and updates its internal state as not_playing.

  • 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.

The other solution doesn't seem doable for milion of players.

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).



来源:https://stackoverflow.com/questions/44413766/enforce-strict-consistency-spanning-multiple-aggregates

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!