问题
I'm new in DDD so I'm doing some practice to undertand a little bit more. I have Course BC with the follow rules:
- Course has to be created first and then they can create the modules of one course
- Every module will be finished by the user when he upload the homework
- The course will be finished by the user when he finished all the modules
Definition: A course covers a particular topic and it is comprised of module. For instance, sap course has 10 modules such as: module 1: what is it?, module 2: how to use it?…
After this, I realize that Course is the aggregate root of module, because the modules are finished I have to close the status of user with the course.
the model would be:
public class Course : AggregateRoot
{
private string title;
private List<Module> modules;
}
but also module is an aggregate root of homework because when the user upload his homework the module has to be closed. This make me think that this approach is wrong because is not possible in DDD have nested aggregate root. Someone knows what is it wrong?
[UPDATED]
Ok, now I understand how is work and why you split it in 2 BC. However I did some changes and some questions come to my mind.
-I've created enroll method as static and I put the constructor as private.
-Course have to be an array because one student can have more than one.
-I've put more parameters related with the course and also the teacher. Is the teacher and entity of course, right?
-I created status of course to update it when the module is finished this way I don't have to read all the modules to know it. is ok?
-How can I pass more information for every module like title and description? and is the course entity how create all the modules, right?
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:
There are two different sub-domains (so we have two bounded contexts):
1.Courses and modules administration where the teachers can administer those; Here Course
and Module
can be Aggregate roots and a course
could hold references to the Modules
IDs (not to instances!).
public class Course: AggregateRoot
{
private string title;
private List<ModuleId> modules;
}
2.Student participations to the courses. Here there is a StudentEnrolment
Aggregate root that contains references to the Course
and Module
from the other BC but as Value objects; it models the student participation to a single course; in this bounded context there is a new Entity, Homework, that track the student homework-upload and course participation status.
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;}
}
If you need to query the Enrolment to get the homeworks you should not return a list of Homeworks because the client code would think that it can call Homework.upload(file)
directly, which is not permitted (only the Aggregate root can modify its internal entities). Instead, you could return a Tuple or better, you can create an immutable version of the Homework class.
来源:https://stackoverflow.com/questions/50455084/handling-aggregate-root