DDD使用实体中的存储库在更新之前进行验证

时间:2019-04-06 13:39:07

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

让我们说我将不更新Person实体的昵称,但规则是该昵称最多可被其他5个Person使用。我该怎么办?

在调用更新之前,我应该在域服务中进行验证吗?

还是将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 Entity(类似于@plalx的示例)-到底是将服务传递给Entity还是比存储库更好?

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

1 个答案:

答案 0 :(得分:3)

  

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

但是,这不会阻止并发违反规则,因为一旦您选中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次使用该昵称。