kenik
kenik

Reputation: 316

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.

Upvotes: 1

Views: 1198

Answers (2)

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57249

You'll want to look into Helland 2005

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.

Basic idea: the aggregate has its own information. Anything other information it needs to enact some change needs to either (a) be passed to it or (b) it needs to look up that information itself after being passed the capability to look it up.

If you want to pass the capability, then you normally use a "stateless" domain service; see Evans 2004. If you want to pass the value, then you fetch the value in your application layer, and pass it to the domain model as an argument.

Possible problems to consider: if the player registration is "somewhere else"; ie you need to make a filesystem call or a network call that might fail, then you need to think about how that failure might pollute your "pure" domain model. That's not so much a problem in the application layer, because we expect to be orchestrating I/O there anyway.

If looking up a player registration is expensive, or if complicated domain logic is needed to determine which player registration to look up, replicating that code into the application layer isn't so great (our "domain logic" is leaking into other places). On the other hand, domain logic belongs in the domain model, so we still respect cohesion.

Then there's a third variation, where you perform I/O in the application code, and domain logic in the domain model, and have a more sophisticated protocol between the two so that the domain model can ask for the data that it needs when it needs it, and the application code can fetch that information and pass it back to the domain model. Big problem here is that there are almost no examples of people implementing this design - you can get a taste of it from Cory Benfield's 2016 talk on protocol libraries.

There isn't a single answer that's right always, just trade offs that you need to consider when finding a solution that is fit for purpose in your context.

Upvotes: 2

Neil W
Neil W

Reputation: 9137

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.

Upvotes: 2

Related Questions