我是DDD的新手,所以我正在做一些练习再多做一点。我有BC课程,遵守以下规则:
定义: 课程涵盖特定主题,它由模块组成。例如,sap课程有10个模块,例如:模块1:它是什么?,模块2:如何使用它?...
在此之后,我意识到课程是模块的聚合根,因为模块已经完成,我必须关闭用户的课程状态。
该模型将是:
public class Course : AggregateRoot
{
private string title;
private List<Module> modules;
}
但模块也是家庭作业的集合根,因为当用户上传他的作业时,必须关闭模块。这让我觉得这种方法是错误的,因为在DDD中不可能有嵌套的聚合根。有人知道这是错的吗?
[增订]
好的,现在我明白了工作是怎样的以及为什么你在公元前2年拆分它。但是我做了一些改变,我想到了一些问题。
- 我已将enroll方法创建为static,并将构造函数设置为private。
-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 */
}
}
答案 0 :(得分:1)
有两个不同的子域(因此我们有两个有界的上下文):
1.教师可以管理的课程和模块管理;此处Course
和Module
可以是聚合根,course
可以保存对Modules
ID的引用(而不是实例!)。
public class Course: AggregateRoot
{
private string title;
private List<ModuleId> modules;
}
2.学生参加课程。这里有一个StudentEnrolment
聚合根,它包含对来自其他BC的Course
和Module
的引用,但是作为Value对象;它模拟学生参与单一课程;在这个有限的背景下,有一个新的实体,家庭作业,跟踪学生的家庭作业上传和课程参与状态。
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;}
}
如果您需要查询注册以获取主页,则不应返回Homeworks列表,因为客户端代码会认为它可以直接调用Homework.upload(file)
,这是不允许的(只有聚合根可以修改)它的内部实体)。相反,你可以返回一个或更好的元组,你可以创建一个Homework类的不可变版本。