【问题标题】:DDD using repository in entity for validation before updateDDD 在更新前使用实体中的存储库进行验证
【发布时间】:2019-04-06 13:39:07
【问题描述】:

假设我不想更新 Person 实体的昵称,并规定此昵称最多可供 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 } } }

    这对我来说似乎是最明显和最直接的,好处是逻辑包含在实体中,但困扰我的是这种将存储库传递到实体的可悲传递以及这种感觉埃文斯不鼓励这样做

  • 不是将 personRepository 传递给 Person Entity,而是将 personService 传递给 Person Entity(类似于 @plalx 的示例)——将服务传递给 Entity 比存储库更好吗?

  • 在服务中进行验证类似于@plalx 示例changePersonNickname(personId, newNickname){...} 但在@plalx 的示例中使用服务似乎是合理的,因为它在两个实体上运行,这里我们只有一个实体,如果把这个逻辑放在它担心服务而不是实体中的服务不会转向贫血域模型并离开 DDD。

【问题讨论】:

  • 注入服务的缺点是它使测试更加乏味(需要更多的模拟/存根)。您不通过整个存储库的原因是尊重接口隔离原则 (ISP);支持特定于任务的接口(例如NicknameUsageProvider,可以由存储库类实现)。最后,您还可以在应用服务中获取数据并将其传递给 AR 的方法。 nicknameUsage = …; person.changeNickname(newNickname, nicknameUsage).

标签: oop domain-driven-design repository-pattern ddd-repositories aggregateroot


【解决方案1】:

或者最好将 personRepository 传递给 PersonEntity.update(...) 方法,并进行搜索和验证 内部实体更新方法?

不过,这并不能防止规则因并发而被违反,因为一旦您检查了personRepo.usageCountOfNickname(nickname) &lt;= 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;
    } 
}

如果您希望消除NicknameUsageUser-Nickname 关系不同步的所有风险,您可以设计使NicknameUsage 是跟踪用户与其昵称之间关系的唯一实体(昵称不属于@987654329 @AR)。

最后,我在最终一致性方面没有太多经验,希望其他人能阐明什么是正确的方法,但如果您不想在每个事务中修改许多 AR,那么我认为您可以使用一些策略.

例如,您可以让 > 6 个人使用相同的昵称,但随后有一个流程来检测违规行为并将这些人标记为违反昵称政策,他们有宽限期来更改他们的昵称,否则它将被自动设置为其他内容(或任何其他补偿操作)。请注意,您仍然可以使用域服务进行检查以限制违规次数。

如果你想防止违规,你也可以使用某种saga,首先保留新昵称,然后释放旧昵称,最后更改该人的昵称。一个人会在短时间内实际保留 2 个昵称,但绝不会出现昵称被使用超过 6 次的情况。

【讨论】:

  • 感谢您的好评。我在我的问题中添加了更多说明。请看一看。 Saga 和进程检测违规也是个好主意,但我想知道,如果足够我们可以将存储库传递给实体,或者我们应该通过一切手段避免它,即使通过添加额外的 NicknameUsage 实体,
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-05
  • 1970-01-01
  • 2018-08-03
  • 2010-11-24
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
相关资源
最近更新 更多