Domain driven design 处理聚合根

Domain driven design 处理聚合根,domain-driven-design,aggregateroot,Domain Driven Design,Aggregateroot,我是DDD的新手,所以我正在做一些练习来了解更多一点。我有以下规则的课程BC: 必须先创建课程,然后他们才能创建一门课程的模块 每个模块将由用户在上传作业时完成 当用户完成所有模块时,课程将由用户完成 定义: 课程涵盖特定主题,由模块组成。例如,sap课程有10个模块,如:模块1:it是什么?模块2:如何使用it 在此之后,我意识到课程是模块的聚合根,因为模块已完成,我必须关闭课程的用户状态 该模式将是: public class Course : AggregateRoot { pri

我是DDD的新手,所以我正在做一些练习来了解更多一点。我有以下规则的课程BC:

必须先创建课程,然后他们才能创建一门课程的模块 每个模块将由用户在上传作业时完成 当用户完成所有模块时,课程将由用户完成 定义: 课程涵盖特定主题,由模块组成。例如,sap课程有10个模块,如:模块1:it是什么?模块2:如何使用it

在此之后,我意识到课程是模块的聚合根,因为模块已完成,我必须关闭课程的用户状态

该模式将是:

public class Course : AggregateRoot
{
    private string title;
    private List<Module> modules;
}
但模块也是作业的聚合根,因为当用户上传作业时,模块必须关闭。这使我认为这种方法是错误的,因为在DDD中不可能有嵌套的聚合根。有人知道怎么了

[更新]

好了,现在我明白了工作是如何进行的,以及为什么你把它分成了公元前2年。然而,我做了一些改变,一些问题浮现在我的脑海中

-我已将enroll方法创建为静态,并将构造函数设置为私有

-课程必须是一个数组,因为一个学生可以有多个

-我已经设置了更多与课程和老师相关的参数。当然是老师和实体,对吗

-我创建status of course是为了在模块完成时更新它,这样我就不必阅读所有模块就能知道它。行吗

-如何传递每个模块的更多信息,如标题和说明?课程实体是如何创建所有模块的,对吗

public class StudentEnrolment: AggregateRoot
{
    private StudentId studentId;
    private Course courses;

    private constructor(
        StudentId studentId, 
        Course course, 
       ){
        this.studentId= studentId;
        this.courses[] = course;
    }

    public statuc function enroll(
        StudentId studentId,
        CourseId courseId, 
        string courseTitle,
        string courseLink,
        string teacherId,
        string teacherName,
        List<Tuple<ModuleId, string>> modules) {
        teacher = new Teacher(...);
        courseStatus = new courseStatus();
        new course(courseTitle, courseLink, courseStatus, teacher);
        return new self(studentId, course);
    }

    public function void uploadModuleHomework(ModuleId moduleId, Homework homework){ 
        /* forward to course.uploadModuleHomework */ 
    }

    public boolean isCourseFinished(){ 
         /* forward to course.isFinished */ 
    }

    public List<Tuple<ModuleId, string>> getModules(){ 
         /* forward to course.getModules */ 
    }
}

有两个不同的子域,因此我们有两个有界上下文:

1.课程和模块管理,教师可以管理这些课程和模块;在这里,课程和模块可以是聚合根,课程可以包含对模块ID的引用,而不是对实例的引用

public class Course: AggregateRoot
{
    private string title;
    private List<ModuleId> modules;
}

如果需要查询注册以获取家庭作业,则不应返回家庭作业列表,因为客户端代码会认为它可以直接调用家庭作业.uploadfile,这是不允许的,只有聚合根可以修改其内部实体。相反,您可以返回元组或更好的方法,您可以创建家庭作业类的不可变版本。

您不能有嵌套的AR,但可以在AR中有嵌套的实体。好的,您的意思是模块将是一个实体,该实体具有另一个称为家庭作业的实体。但是你怎么看待这种方法呢?你会用不同的方式吗?我会问业务专家一致性要求是什么。我已经解释过了。在上面的文本中有3条规则。当一个新模块被添加到课程中时会发生什么?@AgustinCastro在我的回答中,StudentenRolliment只适用于一门课程,它可以被重命名为StudentenRollimentToCourse。不需要AR建模学员的所有课程注册,也不需要商业不变量protect@AgustinCastro我修改了我的答案以更好地模拟你的案例。我不明白两件事:第一,为什么你认为家庭作业比模块更重要?对我来说,作业必须在模块内。因为我要向学生展示所有课程,每门课程都有模块,每个模块都有家庭作业。这是自然的流动,对吗?第二个问题:如果课程和模块都有ID,为什么它们是VO而不是实体。我在想的一件事是,保存每个实体课程、模块的ID并不是更好。。。不是保存标题,而是保存描述。然后我们需要额外的信息,我可以点击其他BC课程和模块admin@AgustinCastro1.在这个有界上下文BC2中,模块和过程是值对象。在BC1中,它们是聚合体。这不是一个重要的问题,而是一个分离的问题。一个BC中的聚合是其他BC中的值对象。在BC2中,只需要他们的ID和头衔,而且它们也是不可变的。@AgustinCastro 2。他们有身份证是无关紧要的;需要该ID来链接到BC1中的聚合;如果你不需要它,你可以删除它,但我认为你会这样做,即在UI中显示某种链接。标题是为了不去BC1并得到它,同样,是为了更好地分离BCs。正如您所说,您可以放弃它,但这样,如果BC1(即DB)下降,您将有更好的恢复能力,它也不会使BC2下降。
public class StudentEnrolment: AggregateRoot
{
    private StudentId studentId;
    private Course course;
    private List<Homework> homeworks;

    // initialize a student enrolment as public constructor or make constructor private and use a static method
    // here is important to notice that only this AR creates its entities, it does not receive them as parameter
    public constructor(
        StudentId studentId, 
        Course course, 
        List<Module> modules){
        this.studentId = studentId;
        this.course = course;
        //build the the homeworks entity list based on the modules parameter
        //for each module create a Homework entity, that initially is not uploaded, like:
        this.homeworks  = modules.map(module => new Homework(module))
     }

    public function void uploadFileForHomework(ModuleId moduleId, string file){ 
        /* find the homework by module Id and upload file*/ 
    }

    public boolean isCourseFinished(){ 
         /*returns true if all homeworks are uploaded*/
         /*optimization: you could have a status that is updated when a homework's file is uploaded*/
    }

    public List<Tuple<ModuleId, string, boolean>> getHomeworks(){ 
         /* returns a list of readonly Homeworks, i.e. Tuple<ModuleId, string /*module title*/, boolean /*is uploaded*/> */
    }
}


public class Homework: Entity
{
    private Module module;
    private string file;
    public constructor(Module module){
        this.module = module;
    }

    public void upload(string file){ this.file = file;}

    public boolean isUploaded(){return (boolean)this.file;}

    public string getUploadedFile(){return this.file;}

    public ModuleId getModuleId(){return this.module.getId();}
}

public class Course: ValueObject
{
    private string title;
    private CourseId id;
    public constructor(id, title){...}
    public string getTitle(){return this.title;}
    public string getId(){return this.title;}
}

public class Module: ValueObject
{
    private string title;
    private string description;
    private ModuleId id;
    public constructor(id, title, description){...}
    public string getTitle(){return this.title;}
    public string getDescription(){return this.description;}
    public string getId(){return this.title;}
}