在 DDD 中访问持久性的位置

Where to access persistance in DDD

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

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

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

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。我可能会从视图模型或通过某种用例服务来做到这一点。

视图模型示例:

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

    private Student _student;
    private Course _selectedCourse;

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

should I call the repository from inside the student domain model, like this?

这不是通常的模式。

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

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

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

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

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

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

What is the root when you get all the way to the bottom?

根是一个域模型实体;它是"in charge",可以说,是那个特定聚合体中所有其他实体的那个。

在这里,我只是用它作为替代。在实际项目中,拼写会反映您所在领域的语言——Student、GradeBook、ReportCard 等

现在是加入派对的最佳时机,不用担心。您应该 直接从您的域模型访问数据持久性,而这应该在基础架构层中完成。如果您熟悉 Onion Architecture,它会很好地解释这一点。你可以查一下here or here。链接中描述的相同原则可以应用于您的案例。

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

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

这是我选择的解决方案:

域层聚合:

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/