DDD 在更新前使用实体中的存储库进行验证
DDD using repository in entity for validation before update
假设我不想更新 Person 实体的 nickName,规则是这个 nickName 最多被 5 个其他人使用。我应该怎么做?
在调用更新之前,我应该在域服务中进行验证吗?
还是将 personRepository 传递给 PersonEntity.update(...) 方法,并在实体更新方法中进行搜索和验证更好?
补充说明:
@plalx 创建额外实体来执行此规则的想法很聪明。
当我阅读 DDD 时,我经常读到不推荐将存储库传递给实体(如果不是邪恶的话)。这就是为什么我试图找到一个我们需要实体中的存储库或其他服务的例子)。我实际上不知道是否只传递存储库不好而服务还可以,或者同样不鼓励将服务传递到实体中。
让我们假设这条规则不是那么严格和重要的业务规则,我们可以有多个类似的规则(这么多,以至于为我们要验证的每个属性的每个规则创建一个额外的实体有点过度设计).假设我们可以在并发更新的情况下允许 6 或 7 个相同的昵称(我们只想将它们限制在相当小的数量)并检查
personRepository.usageOfNickname(this/person.nickname) < 5
足够了。在这种情况下,从 DDD 的角度来看,哪种设计更好?
将 personRepository 传递到 Person 实体中
class Person {
...
boolean changeNickname(newNickname, nicknameUsageService) {
if (personRepository.usageOfNickname(this.nickname) < 5) {
this.nickname = newNickname;
return true;
} else {
return false; //or throw
}
}
}
这对我来说似乎是最明显和最直截了当的,好处是逻辑包含在实体中,但让我困扰的是这种将存储库传递到实体的可悲行为,而且这种感觉是 Evans 不鼓励的
不是将 personRepository 传递到 Person 实体,而是将 personService 传递到 Person 实体(类似于 @plalx 的示例)- 将服务传递到实体比存储库更好吗?
- 在服务中进行验证类似于@plalx 示例
changePersonNickname(personId, newNickname){...}
但在@plalx 的示例中使用服务似乎是合理的,因为它在两个实体上运行,这里我们只有一个实体,我担心如果将此逻辑放在服务中而不是它所关注的实体中不会走向贫血领域模型并离开 DDD。
Or is it better to pass a personRepository into
PersonEntity.update(...) method, and do the search and validation
inside entity update method?
不过,这并不能防止通过并发违反规则,因为一旦您检查 personRepo.usageCountOfNickname(nickname) <= 5
它可能会立即更改。
如果你想要强一致性,你可以引入一个 NicknameUsage
聚合根来执行该策略。您将在一笔交易中修改超过 1 个 AR,但这可能没什么大不了的,因为无论如何都不太可能对相同的昵称进行大量争用,对吗?
例如
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);
}
}
您也可以将昵称的使用管理逻辑封装在域服务中,然后将其注入到 AR 的 changeNickname
操作中。
例如
class Person {
...
void changeNickname(newNickname, nicknameUsageService) {
nicknameUsageService.reserveAndRelease(newNickname, this.nickname);
this.nickname = newNickname;
}
}
如果您希望消除 NicknameUsage
与 User-Nickname
关系不同步的所有风险,您可以设计 NicknameUsage
是跟踪用户与其用户之间关系的唯一实体昵称(昵称根本不是 User
AR 的一部分)。
最后,我在最终一致性方面没有太多经验,希望其他人能阐明什么是正确的方法,但是如果您不想在每个事务中修改很多 AR,那么我认为有一些策略适合您可以用。
例如,您可以让超过 6 个人使用同一个昵称,但随后有一个流程可以检测违规行为并将这些人标记为违反昵称政策,他们可以在宽限期内更改昵称,否则将自动设置为其他内容(或任何其他补偿操作)。请注意,您仍然可以使用域服务进行检查以限制违规次数。
如果你想防止违规,你也可以使用某种传奇,先保留新昵称,然后释放旧昵称,最后更改昵称。会有一小段时间,一个人实际上会保留 2 个昵称,但绝不会出现昵称使用超过 6 次的情况。
假设我不想更新 Person 实体的 nickName,规则是这个 nickName 最多被 5 个其他人使用。我应该怎么做?
在调用更新之前,我应该在域服务中进行验证吗?
还是将 personRepository 传递给 PersonEntity.update(...) 方法,并在实体更新方法中进行搜索和验证更好?
补充说明:
@plalx 创建额外实体来执行此规则的想法很聪明。
当我阅读 DDD 时,我经常读到不推荐将存储库传递给实体(如果不是邪恶的话)。这就是为什么我试图找到一个我们需要实体中的存储库或其他服务的例子)。我实际上不知道是否只传递存储库不好而服务还可以,或者同样不鼓励将服务传递到实体中。
让我们假设这条规则不是那么严格和重要的业务规则,我们可以有多个类似的规则(这么多,以至于为我们要验证的每个属性的每个规则创建一个额外的实体有点过度设计).假设我们可以在并发更新的情况下允许 6 或 7 个相同的昵称(我们只想将它们限制在相当小的数量)并检查
personRepository.usageOfNickname(this/person.nickname) < 5
足够了。在这种情况下,从 DDD 的角度来看,哪种设计更好?
将 personRepository 传递到 Person 实体中
class Person { ... boolean changeNickname(newNickname, nicknameUsageService) { if (personRepository.usageOfNickname(this.nickname) < 5) { this.nickname = newNickname; return true; } else { return false; //or throw } } }
这对我来说似乎是最明显和最直截了当的,好处是逻辑包含在实体中,但让我困扰的是这种将存储库传递到实体的可悲行为,而且这种感觉是 Evans 不鼓励的
不是将 personRepository 传递到 Person 实体,而是将 personService 传递到 Person 实体(类似于 @plalx 的示例)- 将服务传递到实体比存储库更好吗?
- 在服务中进行验证类似于@plalx 示例
changePersonNickname(personId, newNickname){...}
但在@plalx 的示例中使用服务似乎是合理的,因为它在两个实体上运行,这里我们只有一个实体,我担心如果将此逻辑放在服务中而不是它所关注的实体中不会走向贫血领域模型并离开 DDD。
Or is it better to pass a personRepository into PersonEntity.update(...) method, and do the search and validation inside entity update method?
不过,这并不能防止通过并发违反规则,因为一旦您检查 personRepo.usageCountOfNickname(nickname) <= 5
它可能会立即更改。
如果你想要强一致性,你可以引入一个 NicknameUsage
聚合根来执行该策略。您将在一笔交易中修改超过 1 个 AR,但这可能没什么大不了的,因为无论如何都不太可能对相同的昵称进行大量争用,对吗?
例如
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);
}
}
您也可以将昵称的使用管理逻辑封装在域服务中,然后将其注入到 AR 的 changeNickname
操作中。
例如
class Person {
...
void changeNickname(newNickname, nicknameUsageService) {
nicknameUsageService.reserveAndRelease(newNickname, this.nickname);
this.nickname = newNickname;
}
}
如果您希望消除 NicknameUsage
与 User-Nickname
关系不同步的所有风险,您可以设计 NicknameUsage
是跟踪用户与其用户之间关系的唯一实体昵称(昵称根本不是 User
AR 的一部分)。
最后,我在最终一致性方面没有太多经验,希望其他人能阐明什么是正确的方法,但是如果您不想在每个事务中修改很多 AR,那么我认为有一些策略适合您可以用。
例如,您可以让超过 6 个人使用同一个昵称,但随后有一个流程可以检测违规行为并将这些人标记为违反昵称政策,他们可以在宽限期内更改昵称,否则将自动设置为其他内容(或任何其他补偿操作)。请注意,您仍然可以使用域服务进行检查以限制违规次数。
如果你想防止违规,你也可以使用某种传奇,先保留新昵称,然后释放旧昵称,最后更改昵称。会有一小段时间,一个人实际上会保留 2 个昵称,但绝不会出现昵称使用超过 6 次的情况。