Search code examples
oopdomain-driven-designrepository-patternddd-repositoriesaggregateroot

DDD using repository in entity for validation before update


Let's say I wan't to update a nickName of Person entity, with rule that this nickName is used by up to 5 other Persons. How should I do it?

Should I do validation in domain service, before calling an update?

Or is it better to pass a personRepository into PersonEntity.update(...) method, and do the search and validation inside entity update method?


Additional clarification:

@plalx idea of creating additional entity for executing this rule is clever.

When I am reading about DDD I often read that passing a repository into entity is not recomended (if not evil). That's why I try to find an example where we need a repository or some other service in entity). I actually don't know if only passing repository is bad and service is ok, or passing a service into entity is similarily discouraged.

Let's assume that this rule is not so hard and important business rule, and we can have multiple similar rules (so many, that creating an additional entity for each rule for each attribute we want to validate is a little bit overengineered). Lets assume we can allow 6 or 7 same nicknames in case of concurrent update (we only want to limit them to reasonably small number) and check of

personRepository.usageOfNickname(this/person.nickname) < 5

is sufficient. In such a case, which design is better from DDD point of view?

  • passing personRepository into Person Entity

    class Person { ... boolean changeNickname(newNickname, nicknameUsageService) { if (personRepository.usageOfNickname(this.nickname) < 5) { this.nickname = newNickname; return true; } else { return false; //or throw } } }

    this seems most obvious and straighforwad to me, benefit is that logic is enclosed in Entity, but what bothers me is this wretched passing of repository into entity and this sense that this is discouraged by Evans

  • instead of passing personRepository into Person Entity passing personService into Person Entity (similar as in example of @plalx) - is passing a service into Entity anyhow better than a repository?

  • doing validation in service similar as in @plalx sample changePersonNickname(personId, newNickname){...} but in @plalx's sample using a service seems justified, as it operates on two Entities, here we have only one Entity and I'm afraid if putting this logic in service instead of in Entity it concerns wouldn't be going towards Anemic Domain Model and leaving DDD.

Solution

  • Or is it better to pass a personRepository into PersonEntity.update(...) method, and do the search and validation inside entity update method?

    That wouldn't prevent the rule from being violated through concurrency though, since as soon as you checked personRepo.usageCountOfNickname(nickname) <= 5 it could have changed right after.

    If you want strong consistency, you could introduce a NicknameUsage aggregate root to enforce that policy. You would be modifying more than 1 AR in a transaction, but that's probably not a big deal since it's very unlikely that there will be a lot of contention on the same nicknames anyway, right?

    E.g.

    changePersonNickname(personId, newNickname) {
        transaction {
            person = personRepository.personOfId(personId);
    
            currentNicknameUsage = nicknameUsageRepository.usageOfNickname(person.nickname);
            currentNicknameUsage.release();
    
            newNicknameUsage = nicknameUsageRepository.usageOfNickname(newNickname); 
            nicknameUsage.reserve(); //throws if 6 already
    
            person.changeNickname(newNickname);
        }
    }
    

    You could as well encapsulate the nickname's usage management logic in a domain service which is then injected in the AR's changeNickname operation.

    E.g.

    class Person {
        ...
        void changeNickname(newNickname, nicknameUsageService) {
            nicknameUsageService.reserveAndRelease(newNickname, this.nickname);
            this.nickname = newNickname;
        } 
    }
    

    If you wish to eliminate all risks of NicknameUsage getting out of sync with User-Nickname relationship you can design so that NicknameUsage is the sole entity tracking the relationship between users and their nicknames (nickname not part of User AR at all).

    Finally, I dont have much experience with eventual consistency and hopefully someone else will shed some light on what would be the right approach, but if you dont want to modify many ARs per transaction then I think there's a few strategies you could use.

    For instance, you can let > 6 persons use the same nickname, but then have a process that detects violations and tag such persons with a nickname policy violation, where they have a grace period to change their nickname or else it will be set to something else automatically (or any other compensating action). Note that you would still have a check in place using a domain service to limit the number of violations though.

    If you want to prevent violations, you could also use some kind of saga, where the new nickname is first reserved, then the old one is released and finally the person's nickname is changed. There will be a short period of time where a person would actually have 2 nicknames under reservation, but there would never be a time where nicknames are used more than 6 times.