0

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:

  1. Course has to be created first and then they can create the modules of one course
  2. Every module will be finished by the user when he upload the homework
  3. 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 */ 
    }
}
Agustin Castro
  • 439
  • 1
  • 6
  • 20

1 Answers1

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.

Constantin Galbenu
  • 16,951
  • 3
  • 38
  • 54
  • @AgustinCastro in my answer a `StudentEnrolment` is only for a course (it could be renamed `StudentEnrolmentToACourse`). There is no need for an AR modeling all of the stdent's enrolments to courses (there are no business invariants to protect) – Constantin Galbenu May 23 '18 at 00:05
  • @AgustinCastro I have modified my answer to better model your case. – Constantin Galbenu May 23 '18 at 00:51
  • I don't understand two things: first, why did you think that the homework is most important than module? For me homework has to be inside of module. Because I'm gonna show to the students all the course, every course has modules and every module has homework. This is the natural flow, right? And the second: why course and module is a VO instead of entity if both have ids. One thing that I was thinking, is not better to save just the ID of every entity (course, module...) instead of save title, description. Then we I need that extra information I can hit the other BC (course and module admin) – Agustin Castro May 23 '18 at 07:16
  • @AgustinCastro 1. In this bounded context (BC2), module and course are value objects. In the BC1 they are Aggregates. It's not a matter of *importance* but a matter of **separation**. Aggregates in one BC are Value Objects in other BCs. In the BC2, only their ID and title is needed and they are also immutable. – Constantin Galbenu May 23 '18 at 07:21
  • @AgustinCastro 2. It's irrelevant that they have IDs; that ID is needed to make a *link* to the Aggregates in the BC1; you can drop it if you don't need it but I think you will do (i.e. to display some kind of links in the UI). The `title` is needed in order to not *go* to the BC1 and get it, again, is to better separate the BCs. You may drop it, as you said, but in this way you have better *resilience* (if the BC1 goes down, i.e. the DB, it would not bring down the BC2 as well). – Constantin Galbenu May 23 '18 at 07:25
  • Galberu. OK, I see your point it makes sanse. One question that you didn't answer me, why module has to be inside of homework? Is the homework that has to be inside of module. Because I wanna show every course with every module in UI, that means that for get whole the modules of one course first I have to get homework. – Agustin Castro May 23 '18 at 08:22
  • @AgustinCastro In BC2 the module is just a Value object, it's immutable. From the point of view of a Student, all he cares are its homeworks, and every module has exactly one homework. – Constantin Galbenu May 23 '18 at 08:28
  • @AgustinCastro `Because I wanna show every course with every module in UI, that means that for get whole the modules of one course first I have to get homework` - but you should not use this BC2 to show in the UI the list of all courses with all their modules. For that you use the Aggregates from the BC1. You use the `StudentEnrolment` to show to a particular student his status on his homeworks for a course. – Constantin Galbenu May 23 '18 at 08:29
  • Ok, that's true. Ans if I wanna get the title of the module I should add getTitleModule in homework. The las question is: if the teacher wants to see the homework that the students uploaded in every module. Should I create teacher entity in BC2? – Agustin Castro May 23 '18 at 09:02
  • @AgustinCastro no, I don't think so. You just fetch all the `StudentEnrolments`. If every course has a teacher then you can add the teacher in the BC2 also, in the `StudentEnrolment` along/inside the course, but not as an Entity but a Value object. So you add this only for filtering. (Note: When I say Entity I speak from the DDD point of view. What in DDD is a Value object, it can be a DB-Entity from the DB point of view; it depends on the type of persistence that you have) – Constantin Galbenu May 23 '18 at 09:23
  • ok, but then the course is a Entity. because now the course has id, title, status and teacherId(vo). the status can change, this means that is not immutable and also the course id is using as identity not just as a link, right? – Agustin Castro May 23 '18 at 13:10
  • @AgustinCastro the `status` is in fact in `StudentEnrolment`, not in `course`. `the course id is using as identity not just as a link` - I meant that you could use that identity to show a link to the user in the UI, like `View course` – Constantin Galbenu May 23 '18 at 13:14
  • so if course id is identity, course has to be entity. I see that course immutable because id and title don't change, but it has identity and the VO can have identity this is just for an entity. – Agustin Castro May 23 '18 at 13:48
  • @AgustinCastro An immutable Entity is hardly an Entity but it's irrelevant what you name it as long as it's clear that it does not change, at least not in this BC – Constantin Galbenu May 23 '18 at 14:04
  • ok, I've been reading articles to expose the endpoint when you apply ddd and recording this: http://no-kill-switch.ghost.io/how-not-to-hurt-yourself-while-building-restful-api/ that says: "Expose your AR as API's entity (resource)" thats means that for this example my endpoints should be /students/id/enrolment/ and use path method to upload the homework. But can't I expose the service like this: /students/id/enrolment/id/homework and post method? – Agustin Castro May 23 '18 at 14:33
  • @AgustinCastro I guess you can – Constantin Galbenu May 23 '18 at 14:35
  • @AgustinCastro if you have a Restful architecture then the URLs don't matter much – Constantin Galbenu May 23 '18 at 14:46