【问题标题】:Where to access persistance in DDD在哪里访问 DDD 中的持久性
【发布时间】:2020-05-29 08:18:43
【问题描述】:

我正在研究领域驱动设计(是的,我参加那个聚会已经很晚了),到目前为止,我已经意识到领域模型应该是宇宙的中心。数据持久性只是一个实际的细节。

但是,如果这是真的,那么我很难确定与持久性部分(例如存储库)的通信应该在哪里。

例如,如果我希望能够为学生添加新成绩,我应该像这样从学生域模型内部调用存储库吗?

interface IGradeRepository
{
    void SaveGrade(int courseId, string grade);
    // ...other methods
}

class Student
{
    IGradeRepository _gradeRepository;
    List<Grade> _grades = new List<Grade>();

    public Student(IGradeRepository gradeRepository) 
    {
        _gradeRepository = gradeRepository;
    }

    int StudentId { get; set; }

    void AddGrade(int courseId, string grade)
    {
        var grade = new Grade(this.StudentId, courseId, grade);
        _grades.Add(grade);

        // Do I put the call to the data persistance here?
        _gradeRepository.SaveGrade(grade);
    }
}

如您所见,我最好的选择是从域模型内部访问存储库。但是,我不确定这是否是解决方法。这似乎是许多教程中遗漏的内容之一。

总结一下:在进行领域驱动设计时,我从哪里访问数据层?

更新

正如一些人所评论的,从我的示例中不清楚我将如何访问学生课程。我可能会从视图模型或通过某种用例服务来做到这一点。

视图模型示例:

class StudentGradesViewModel
{
    // (...) all sort of VM-stuff

    private Student _student;
    private Course _selectedCourse;

    public void AddGrade(string grade)
    {
        _student.AddGrade(_course.CourseId, grade);
    }
}

【问题讨论】:

    标签: c# domain-driven-design ddd-repositories


    【解决方案1】:

    首先:存储库应该只引用聚合。在您的情况下,您的学生可以有多个成绩。 因此,您的存储库应该始终是聚合的持久性,而不是聚合内的实体。

    这是我选择的解决方案:

    在域层聚合:

    class Student
    {
        List<Grade> _grades = new List<Grade>();
        int StudentId { get; set; }
    
        public Student() 
        {
        }
    
        void AddGrade(int courseId, string grade)
        {
            var grade = new Grade(this.StudentId, courseId, grade);
            _grades.Add(grade);
        }
    }
    

    领域层中的存储库接口:

    interface IStudentRepository
    {
        void SaveStudent(int studentId);
        // ...other methods
    }
    

    当然,正如上面帖子中提到的,Repository 的实现应该发生在基础设施层。

    您应该创建应用程序服务或 CommandHandler(如果您使用 CQRS)以集成域逻辑和基础结构:

    class AddGradeService
    {    
        private readonly IStudentRepository _studentRepository;
    
        public AddGradeService(IStudentRepository studentRepository) 
        {
            _studentRepository = studentRepository;
        }
    
        void Handle(int studentId, int courseId, string grade)
        {
            Student student = _studentRepository.Get(studentId);
            student.AddGrade(courseId, grade);
            _studentRepository.Save(student);
        }
    }
    

    这应该是怎么做的:) 如果需要,您也可以参考我的示例 od DDD:https://github.com/czarek-szok/DDD.Ecommerce/

    【讨论】:

    • 所以简单地说,你更新聚合(添加成绩),然后保存整个聚合(学生)。对吗?
    • 是的,没错。这是来自 DDD 的原则之一。
    【解决方案2】:

    现在是参加派对的最佳时间,所以不用担心。您应该直接从您的域模型访问数据持久性,而应该在基础设施层完成。如果你熟悉洋葱架构,它很好地解释了这一点。您可以查看herehere。链接中描述的相同原则可以应用于您的案例。

    访问持久层(通常称为基础设施)是通过执行你的用例来实现的,这些用例通常放置在应用层中,并通过命令(操作实体的状态)或查询(读取持久性实体的状态)实现

    【讨论】:

    • 我已经阅读了很多关于洋葱(建筑的)的信息。我明白了原则(我认为),但我错过了细节。您能否解释一下在我的示例中您将在哪里解决持久性问题?
    • 在示例中,您省略了在 Student 实体中调用 AddGrade 方法的代码部分。但我有根据的猜测是,您应该在更改聚合状态后访问那里的存储库。
    • 好点,我已经用一个非常简约的视图模型更新了我的示例 :-)
    【解决方案3】:

    我应该像这样从学生域模型内部调用存储库吗?

    这不是通常的模式。

    相反,与存储库的交互发生在应用程序层而不是域层内更为常见。

    存储库为应用程序提供对聚合根的访问权限,并且应用程序仅与聚合根交互。所以简单的模式看起来像

    using transaction():
        Root root = _repository.get(rootId)
        root.doSomeCoolDomainThing(...)
    

    这里的假设是 root 在做很酷的事情时可以访问保持其域不变性所需的所有持久信息(这通常意味着整个信息图都在内存中可用)。

    在某些情况下,您会看到将根目录保存到存储库中变得更加明确:

    using transaction():
        Root root = _repository.get(rootId)
        root.doSomeCoolDomainThing(...)
        _repository.save(root)
    

    一路到底的根是什么?

    根是一个领域模型实体;可以说,它是该特定聚合中所有其他实体的“负责人”。

    在这里,我只是将其用作替代品。在实际项目中,拼写会反映您所在领域的语言——学生、成绩簿、报告卡等。

    【讨论】:

    • 一路到底的根是什么?它是某种用例服务吗?还是类似于视图模型(如果使用 MVVM)?
    猜你喜欢
    • 1970-01-01
    • 2014-08-11
    • 1970-01-01
    • 2018-11-21
    • 1970-01-01
    • 2010-12-14
    • 2013-02-19
    • 1970-01-01
    • 2013-08-23
    相关资源
    最近更新 更多