问题
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 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
).
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
Player
s 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 Player
s. 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 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.
回答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 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
.If the
Player B
is not playing another game then it raises theGameAccepted
event; theGame
saga catches this event and send theMarkGameAsAccepted
command toPlayer A
aggregate;Player A
then emits theGameStarted
event and update its internal state asplaying
.
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