PolishCivil
PolishCivil

Reputation: 141

Finding aggregate id based on dependency

i have a quite messy question since i'm not sure if this is more of global design issue.

I have service which defines some aggregates and those aggregates publish events. Let's pick one aggregate from the service and call it A. Now, im defining another service which has some aggregate that is supposed to work with the A, lets call the second aggregate B.

When A publishes some event i want to send command to the B (ideally through saga) but im having a hard time calculating appropriate id of B (the A does not know that B exists, therefore the events it publishes does not have any hint on how to calculate the id)

I can think of few possible scenarios:

First one would be statically calculating the ID of B based on the id of A, for example in axon i could do something like some-id-${suffix} so when i receive event from A with some-id i can immediately know that it should be dispatched to some-id-B

Second one would be using the read side? (im not sure how its properly called) and query the thing and try to find B id based on the A id but that seems a little bit of overkill.

Is there anything i could read that would navigate me through possible scenarios and give me a hint how to handle them? Thanks!

Upvotes: 0

Views: 378

Answers (1)

expandable
expandable

Reputation: 2300

From what I understand, you have a relationship from aggregate B to aggregate A. These kind of relationships are normal and they occur all the time.

Note: Since this question is very general and without a context, I may be missing something. If there is a more special case than the one described notify my about it.

This is a great read for aggregate design

Note: Check this video from Martin Fowler before reading the rest of this answer, I strongly suggest it as it explains concepts related to events and commands in great detail.

Note: Because the term entity is also very important, from here on I will not use aggregate anymore, so assume that each entity (Player, User, Game) are root of their own aggregate and is a consistency boundary, so eventual consistency in this case by domain events will be used. I will also ignore CQRS at the moment to avoid having to talk about read side and write side. We will discuss and implement a Model

Let take the example with the game. You have a Player that should represent a User in a Game. The Player entity should reference the Game and User in some way. It can be via a direct reference or by ID. In the case of distributed system it will be by ID. For our example Lets use UUIDs (for example 8d670541-aa51-470b-9e72-1d9227669d0c) for ID's so we can generate them randomly without having to define a schema, auto generate sequencial number (like in SQL databases) or a special algorithm. Let's say that a User has UserStatistics. So when that when a Player makes scores (for example by killing other players in a shooting game) the UserStatistics entity should be created if not exists and updated. The UserStatistics should reference User by ID also, so we have a dependency from UserStatistics to User.

UserStatistics will look like this:

UserStatistics {
  UUID UserID,
  uint KillsCount,
  uint GamesPlayedCount
}

Because the Player cannot exists without a User, the User should be created first. Because a Player is part of a Game than means that the Game should be created first. Let define some terminology in our Ubiquitous Language. A User 'joins' a Game by becoming a Player in it. Let say that Game will be created by someone else, not Users to avoid having to discuss the situation when the User creates a game and should join it also at the same time etc. Should this happen in the same transaction etc... The Game will be something like a MMO where it is created by someone and regular users can join.

When a User joins a Game, then the Player entity will be created with a userID and gameID. Creating a Player without an userID and gameID is not valid.

Let discuss the problem with Commands and Events. Commands can be Triggered by Events. Let use the Observer Pattern. One Entity will have to observe another Entity for events. In our example this means that the dependency is from UserStatistics (the observer) to User and Player (the subject/message producer). The fact that a specific Command on a UserStatistics will be executed as a reaction to an Event raised from Player and User should not affect the Player or Player in any way. By using an Event to deliberately trigger a special Command in a passive aggressive style is not a very good strategy. Commands can be triggered by a Event, but not only one specific Command can be Triggered. Many different Commands can be triggered and only the dependent Entities, Services or Systems should care about what happens. The Player and User just provide Events.

When a User joins a Game and the Player is created it will reference both entities by ID, so it will look something like this:

Player {
  UUID GameID,
  UUID UserID
}

Also UserJoinedGameEvent event will be raised from User entity (it cam be raised from Game but we will choose User). It looks like this:

UserJoinedGameEvent {
  UUID GameID,
  UUID UserID,
  UUID PlayerID
}

The UserStatisticsService can subscribe for events and update statistics.

When a User joins a Game, a process of gathering statistics will start and we will update (or create if it doesn't exists) his UserStatistics with how many games the he has played. At the same time when a Player makes a kill we will have to update the statistics again.

StartGatheringUserStatisticsCommand will be triggered from UserJoinedGameEvent event.

Let's add an event PlayerMadeKillEvent that looks like this:

PlayerMadeKillEvent {
 UUID UserID,
 UUID PlayerID,
 UUID GameID
}

UserStatisticsService will subscribe for PlayerMadeKillEvents and update UserStatistics by using PlayerMadeKillEvent.UserID to find the statistics for the specific User.

When a User quits a Game, a UserQuitsGameEvent can be raised and statistics gathering can stop.

In this example we didn't have a specific schema to generate special ID's, we can reference other aggregates that will be created first and then use their ID.

Upvotes: 2

Related Questions