Reputation: 2071
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?
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.Upvotes: 0
Views: 809
Reputation: 43718
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.
Upvotes: 3