从领域驱动设计(DDD)中访问存储库的替代方案

3
我看到了很多这样的问题,但它们总是似乎与我的想法不符。我认为这是由于我对聚合-聚合根-实体-值对象之间关系的理解不够清晰。
对我来说,最终的解决方案看起来就像DTOs,因为所有逻辑似乎都在存储库中结束。也许我看了太多EF教程。
假设我们有一个非常简单的类,带有第一个版本的repo(让我们忽略它只处理一个人的事实):
class Person 
{
    int Age;
    void MakeOlder() { Age++; }
}
interface IPersonRepository
{
    Person GetAPerson();
}

现在,我想要从UI(比如,按下一个按钮)来做的事情不是

person.MakeOlder();
_repo.Save(person);

但也许只需要:
person.MakeOlder();

对我来说,“变老”这个动作应该触发保存。但是这需要在Person中引用repo。 我能想到的唯一解决方案是:

_repo.MakePersonOlder(person);

(似乎很糟糕。)
person.MakeOlder(_repo);

就用户界面而言,我现在看到的没有任何收益。

class Person : IMyEntityBaseType { ...
    void MakeOlder() { 
        Age++; 
        EntityDataWasChangedNowIWantToBeSaved(); 
    }
}

或者某种变体;事件、AOP等等。以某种方式发出或捕获信号,表明应该进行保存。
我认为我可能把我的DDD观点和事件溯源等概念混淆了。
如果在UI代码中调用SavePerson感觉不好,那么我的想法完全错误吗?
做正确的事情是什么?
2个回答

4

你不应该将Repository传递给Person。使用这种模型,你需要将Repository传递给每个方法。通常情况下,如果使用EF或其他ORM,则会有内置的 工作单元 的概念。工作单元跟踪在一个定义的交互中发生变化的所有对象。当你提交一个工作单元时,它会提交其中的所有更改。代码看起来应该像这样:

person.MakeOlder();
_unitOfWork.Commit();

当有多个实体需要更改时,可以使用此方法。由于ORM跟踪更改,因此您不必显式保存属于工作单元的每个对象。

更好的选择是使用一个应用程序服务来封装用例。应用程序服务将具有以下方法,从而解决您的问题:

public void MakeOlder(int personId)
{
   var person = this.personRepository.Get(personId);
   person.MakeOlder();
   this.unitOfWork.Commit();
}

UI将调用应用程序服务,而不是直接调用域对象。

1
eulerfx比我快。我只想补充一下,Person不需要使用存储库的原因是关注点分离。Person是一个领域对象,它建模领域,对持久性或实际使用它的人或机构毫不关心。这不是它的责任。 - MikeSW
好的,不错,但我仍然觉得这些示例缺少一些实际代码来获取完整的上下文。那么我假设UI仍将看到领域对象,并且我们将“命令”与查询分开,即cqrs?还是应该UI代码调用ApplicationService.GetPerson()(“洋葱架构”?)? - NiklasJ
您可以让应用程序服务返回领域对象到用户界面或只读对象,使其更像CQRS。我更喜欢后一种方法,因为它可以保持领域完全封装在应用程序服务中。 - eulerfx

0

完全同意。您缺少了ApplicationService的概念。应用层非常重要。

我不同意您的看法:

对我来说,完成的解决方案看起来只是DTO,因为所有逻辑似乎都在存储库内部。

这是因为您需要更好地理解层次结构。演示层(UI)将调用ApplicationService(Facade)。建议使用MakePersonOlder(int personId)。

应用程序层负责协调操作:)

其中一个操作可能是保存到存储库,记录到文本文件,发送电子邮件等。

明白了吗?

Bruno


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接