Search code examples
domain-driven-designddd-repositoriesddd-service

DDD - validation related entities in aggregate root


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.


Solution

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

    1. User logs in and receives a token, including claim with User Id.
    2. Client stores token.
    3. When user tries to join a tournament, the request body will not include the User Id (because you correctly point out that this could be 'spoofed'). Instead the token will be attached to the http request as a header or cookie.
    4. The Controller will then extract the UserId from a claim in the 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.

    UPDATE

    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.