I'm trying to understand DDD concept and validation of entities inside aggregate root. Let me explain.
Let's assume we have Web app where user have to register to the app and can join tournament (let's say by scaning qr code). Later on he'll be able to select a team but it's not important in this case.
I have no problem with modeling player/user registration or tournament creation. But I have no clue how to resolve issue with player joining to tournament.
I know player identity (his id from http request) and tournament as well. But where should I validate that user who want to join tournament exists?
I can't assume that in my tournament method
.AddPlayer(Player player)
player who is passed to a method is valid because someone can easly pass null or player who never registered to my app. I know it's a simple app and that case may never happen but it's great case to overcome ;)
Domain service is the right place to do this kind of validation? So should I call it in my application layer to validate player or can we validate in other way somehow? Let's assume that player existance in the system is critical for that app.
If the user is logged in to your system, which I presume is the case before they send a request to join a tournament, then the client should not send the UserId as a property of the request body. You should get this from claims in the security token.
In this way, you can ensure that the UserId you are working with is a valid UserId that is controlled because you are retrieving it from the token that you created, not retrieving it from a value that the client is providing explicitly in a request body.
Your question is not specific to DDD. User authentication is a challenge for any solution.
If you further want to ensure that the Player is valid within the Tournament then you need to ensure that the Player Id provided exists in the database.
My own approach would be as follows:
Domain Entity
class Tournament
{
List<Player> _players = new List<Player>();
public void AddPlayer(Player player)
{
_players.Add(player);
}
}
Command
class AddPlayerToTournamentCommand
{
public int TournamentId { get; set; }
public int PlayerId { get; set; }
}
Command Handler
class AddPlayerToTournamentHandler
{
readonly ITournamentRepository _tournamentRepository;
readonly IPlayerRepository _playerRepository;
public AddPlayerToTournamentHandler(
ITournamentRepository tournamentRepository,
IPlayerRepository playerRepository)
{
_tournameRepository = tournamentRepository;
_playerRepository = playerRepository;
}
public async Task Handle(AddPlayerToTournamentCommand command)
{
Tournament? tournament = await _tournamentRepository.Find(command.TournamentId);
if (tournament is null) throw new Exception("Missing tourney");
Player? player = await _playerRepository.Find(command.PlayerId);
if (player is null) throw new Exception("Missing Player");
tournament.AddPlayer(player):
}
}
In this way you have ensured that the player is in the repository before adding to tournament. To be more DDD, let Tournament aggregate do the null check for player instead of the command handler.