【问题标题】:DDD approach where to enforce business rules without aggregate?DDD 方法在哪里执行没有聚合的业务规则?
【发布时间】:2019-05-29 13:13:39
【问题描述】:

DDD 新手我有一个简单的案例,我想使用 DDD 方法进行建模

2 个实体 StudentCourse

Student 的相关属性是 StudentId 和 Budget

Course 的相关属性是 CourseId 和 Price

Student 和 Course 是可以独立存在并拥有自己生命周期的实体

业务要求:

1) 学生可以预订一门课程(CourseId 是学生表的 fk)

2) 只有当用户的预算高于或等于课程价格时,学生才能预订课程。

3) 课程价格的变化不影响已预订课程的学生。

4)当学生预订课程时,他的预算保持不变(可能会在课程结束后更改)

5) 可以修改学生预算设置不同的金额,但新金额必须高于或等于用户预订课程的价格。
设置较低的数量会引发运行时错误。

按照领域驱动设计对这个简单案例进行建模的方法是什么?在哪里执行这两条商业规则(第 2 点和第 5 点)?

由于课程可以在没有学生的情况下存在,所以我无法定义学生是根实体而课程是子实体的聚合。可以吗?

但与此同时,在第 5 点定义的业务规则在我看来似乎是一个不变量。是吗?

那么在哪里以及如何应用这些规则呢?

我尝试了一种服务方法,可以适用于第一个简单规则(第 2 点),但对于第 5 点描述的规则失败

var student = studentRepository.Get(srtudentId);
var course = courseRepository.Get(courseId)

var studentService = new StudentService();

studentService.SubScribeStudentToCourse(student, course);

studentRepository.Update(student);


StudentService.ChangeStudentBudget(student, 100000);

studentRepository.Update(student);  

当我用新预算更新学生时,其他人可以更改课程价格,导致学生预算不一致

public class StudentService
{
    SubScribeStudentToCourse(Studen student, Course course)
    {
        if (studentt.Budget >= course.Price)
        {
            student.CourseId = course.CourseId
        }
    }

    ChangeStudentBudget( Student student, decimal budgetAmount)
    {
        if (student.CourseId != null)
        {
            var studentCourse = courseRepository.Get(student.CourseId);
            if ( studentCourse.Price <= budgetAmount)
            {
                student.Budget = budgetAmount;
            }
            else
            {
                throw new Exception("Budget should be higher than studentCourse.Price");
            }
        }
    }
}

【问题讨论】:

  • 你试过什么?你的问题看起来像家庭作业
  • 我正在接近 DDD,我正在想象一个假设的情况,就像描述的在哪里定义两个简单的业务规则(第一个可能是一个简单的验证规则,另一个是在任何时候都需要遵守的不变量)你改变 Student.Budget 的值
  • 设置较低的数量会引发运行时错误。这就是问题#1。我稍后会跟进答案,但现在只想指出 SetAmount 是基于“Crud”的思想。你的学生应该有存款和取款方法。不是设置金额。实际的“SetAmount”逻辑将根据不变量封装在这些方法中。

标签: oop design-patterns architecture domain-driven-design


【解决方案1】:

你的聚合必须最终一致,而不是强一致,除非它是一个非常重要的场景。如果是,那么考虑使用 Saga,或者在一个事务中更新它们。你应该在这里做的很简单:StudentService.SubscribeTo() 和 CourceService.Enroll()。这两种方法应该发生在 2 个不同的事务中。首先,在 StudentService.SubscribeTo 中,您从持久性中获取学生和课程模型,然后调用 student.SubscribeTo(course)。操作完成后,您提出 student assignedToCourse 域事件,StudentDomainEventsHandler 捕获它,并调用 CourceService.Enroll() 从持久性中获取学生和课程模型,然后调用 course.Enroll(student)。

【讨论】:

    【解决方案2】:

    第 5 点是另一个验证规则。但如果课程价格可以修改,您也必须检查那里的规则,而不仅仅是在修改学生预算时。

    它不是一个不变量。不变量只涉及一个聚合。你有两个聚合。

    这个问题听起来好像我已经回答过了。

    【讨论】:

    • 它不是一个不变量。不变量只涉及一个聚合。你有两个聚合。 - 不变量也可以在多个聚合之间,即确保具有相同代码的客户在系统中是唯一的。
    • @DmitriBodiu“具有相同代码的客户在系统中是唯一的”只会影响客户聚合。据我所知,不变量是关于聚合类型状态的业务规则,必须在事务上保持一致。它只影响定义事务边界的聚合类型。请参阅“实施领域驱动设计”一书的第 205 和 353 页
    • “具有相同代码的客户在系统中是唯一的”仅影响客户聚合。没门。它是影响系统中所有客户的不变量。除非您使用 Customers[] 创建一个巨大的 Company 聚合,否则它会成为 Company 聚合内部的一个不变量。 (enterprisecraftsmanship.com/2016/09/22/…) 是一篇很棒的文章,解释了跨聚合不变量。
    • @DmitriBodiu 我的意思是它只会影响客户聚合类型。无论如何,我会在聚合的存储库中进行唯一性检查,当您尝试保留客户时,您会检查是否没有其他客户已经存储了相同的代码。没有检查其他聚合类型
    【解决方案3】:

    假设的场景通常很难评论,但我会添加我的 ZAR0.02 :)

    您将同时拥有 Student Course 聚合根。如果您需要与另一方定义的关系,则存储一个 id 列表或代表另一方的值对象。

    要执行某些不能重叠的规则,在Student 上设置有关预算的状态可能会更简单。例如,如果课程不在BudgetApproved 状态,则您无法添加到课程。为了更改预算,您首先需要将状态更改为“Budgeting”。通过这种方式,您可以引入更多不同的步骤,从而更好地控制您的不变量。

    关于价格变化的另一个说明。无论如何,这些东西可能会在“引用”的基础上起作用。一旦您“接受”报价,任何价格变化都无关紧要,除非存在可以而且应该使用某些业务流程处理的“错误”或“遗漏”,或者如果系统中未定义,则超出-乐队。 Order 可能是 Cancelled 或“已放弃”,然后会启动其他一些流程,例如报销。

    【讨论】:

      猜你喜欢
      • 2011-07-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      • 2021-05-03
      • 1970-01-01
      • 2017-11-08
      • 2014-07-05
      • 1970-01-01
      相关资源
      最近更新 更多