Обработка совокупного корня

Я новичок в DDD, поэтому я немного попрактикуюсь, чтобы понять немного больше. У меня есть курс BC со следующими правилами:

  1. Сначала необходимо создать курс, а затем они могут создавать модули одного курса.
  2. Каждый модуль будет завершен пользователем, когда он загрузит домашнее задание.
  3. Курс будет завершен пользователем, когда он завершит все модули.

Определение: курс охватывает конкретную тему и состоит из модулей. Например, курс SAP состоит из 10 модулей, таких как: модуль 1: что это такое?, Модуль 2: как его использовать?…

После этого я понимаю, что курс является совокупным корнем модуля, поскольку модули завершены, мне нужно закрыть статус пользователя с курсом.

модель будет:

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

но также модуль является совокупным корнем домашнего задания, потому что, когда пользователь загружает свое домашнее задание, модуль должен быть закрыт. Это заставляет меня думать, что этот подход неправильный, потому что в DDD невозможно иметь вложенный совокупный корень. Кто-то знает в чем дело?

[ОБНОВЛЕНО]

Хорошо, теперь я понимаю, как работает работа и почему вы разделили ее на 2 года до нашей эры. Однако я внес некоторые изменения, и у меня возникли некоторые вопросы.

-Я создал метод регистрации как статический, а конструктор поместил в частный.

-Курс должен быть массивом, потому что у одного ученика может быть больше одного.

-Я поставил больше параметров, связанных с курсом, а также с учителем. Учитель и сущность, конечно, правильно?

-Я создал статус, конечно, чтобы обновить его, когда модуль будет завершен таким образом, мне не нужно читать все модули, чтобы узнать это. нормально?

-Как я могу передать дополнительную информацию для каждого модуля, например заголовок и описание? и как объект курса создает все модули, верно?

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 */ 
    }
}

person Agustin Castro    schedule 21.05.2018    source источник
comment
У вас не может быть вложенных AR, но вы можете иметь вложенные объекты внутри AR.   -  person Constantin Galbenu    schedule 21.05.2018
comment
хорошо, вы имеете в виду, что этот модуль будет сущностью, у которой есть другая сущность, называемая домашним заданием. а что вы думаете об этом подходе? Вы бы поступили иначе?   -  person Agustin Castro    schedule 21.05.2018
comment
Я бы спросил у бизнес-специалистов, каковы требования к согласованности.   -  person Constantin Galbenu    schedule 21.05.2018
comment
Что ж, я их уже объяснил. В приведенном выше тексте есть 3 правила.   -  person Agustin Castro    schedule 21.05.2018
comment
Что происходит, когда к курсу добавляется новый модуль?   -  person Constantin Galbenu    schedule 21.05.2018
comment
Вы не можете добавлять модули после того, как курс был активирован. Активировать означает, что курс будет показан студенту в порядке его зачисления.   -  person Agustin Castro    schedule 22.05.2018
comment
Итак, после того, как курс активирован, он доступен только для чтения, верно?   -  person Constantin Galbenu    schedule 22.05.2018
comment
Да, это учитель, создавший курс, но как только он добавит в курс несколько модулей и наступит дата начала, курс активируется, и он не сможет его изменить.   -  person Agustin Castro    schedule 22.05.2018
comment
Можно ли отменить курс? Если да, что происходит с участием студентов?   -  person Constantin Galbenu    schedule 22.05.2018
comment
Нет, после того, как курс был активирован, учитель не может ничего изменить в курсе.   -  person Agustin Castro    schedule 22.05.2018


Ответы (1)


Есть два разных субдомена (так что у нас есть два ограниченных контекста):

1. Администрирование курсов и модулей, где учителя могут управлять ими; Здесь Course и Module могут быть агрегированными корнями, а course может содержать ссылки на Modules ID (не на экземпляры!).

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

2. Участие студентов на курсах. Здесь есть StudentEnrolment Aggregate root, который содержит ссылки на Course и Module из других BC, но как объекты 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;}
}

Если вам нужно запросить регистрацию, чтобы получить домашние задания, вы не должны возвращать список домашних работ, потому что клиентский код будет думать, что он может вызывать Homework.upload(file) напрямую, что недопустимо (только корень Aggregate может изменять свои внутренние объекты). Вместо этого вы можете вернуть Tuple или лучше, вы можете создать неизменяемую версию класса Homework.

person Constantin Galbenu    schedule 22.05.2018
comment
@AgustinCastro в моем ответе StudentEnrolment предназначен только для курса (его можно было бы переименовать в StudentEnrolmentToACourse). Нет необходимости в AR, моделирующем все зачисления студентов на курсы (нет бизнес-инвариантов, которые нужно защищать) - person Constantin Galbenu; 23.05.2018
comment
@AgustinCastro Я изменил свой ответ, чтобы лучше смоделировать ваш случай. - person Constantin Galbenu; 23.05.2018
comment
Я не понимаю двух вещей: во-первых, почему вы думали, что домашнее задание важнее модуля? Для меня домашнее задание должно быть внутри модуля. Потому что я собираюсь показать студентам весь курс, в каждом курсе есть модули, и в каждом модуле есть домашнее задание. Это естественный поток, правда? И второе: почему курс и модуль - это ВО, а не сущность, если у обоих есть идентификаторы. Одна вещь, о которой я подумал, - не лучше сохранять только идентификатор каждой сущности (курса, модуля ...) вместо сохранения заголовка, описания. Затем нам нужна дополнительная информация, которую я могу нажать на другой BC (администратор курса и модуля) - person Agustin Castro; 23.05.2018
comment
@AgustinCastro 1. В этом ограниченном контексте (BC2) модуль и курс являются объектами значений. В BC1 они являются агрегатами. Это не вопрос важности, это вопрос разделения. Агрегаты в одном BC являются объектами-значениями в других BC. В BC2 необходимы только их идентификатор и заголовок, и они также неизменяемы. - person Constantin Galbenu; 23.05.2018
comment
@AgustinCastro 2. Неважно, что у них есть идентификаторы; этот идентификатор необходим для создания ссылки на агрегаты в BC1; вы можете отбросить его, если он вам не нужен, но я думаю, вы подойдете (т.е. для отображения каких-то ссылок в пользовательском интерфейсе). title нужен для того, чтобы не перейти к BC1 и получить его, опять же, чтобы лучше разделить BC. Вы можете отбросить его, как вы сказали, но в этом случае у вас будет лучшая устойчивость (если BC1 выйдет из строя, то есть DB, это не повлияет на BC2). - person Constantin Galbenu; 23.05.2018
comment
Гальберу. Хорошо, я понимаю вашу точку зрения, это делает сансе. Один вопрос, на который вы мне не ответили, почему модуль должен быть внутри домашнего задания? Это домашнее задание, которое должно быть внутри модуля. Поскольку я хочу показывать каждый курс с каждым модулем в пользовательском интерфейсе, это означает, что для того, чтобы получить все модули одного курса, мне нужно сначала выполнить домашнее задание. - person Agustin Castro; 23.05.2018
comment
@AgustinCastro В BC2 модуль - это просто объект Value, он неизменяемый. С точки зрения ученика, все, что ему нужно, - это его домашние задания, и каждый модуль содержит ровно одно домашнее задание. - person Constantin Galbenu; 23.05.2018
comment
@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 - но вы не должны использовать этот BC2 для отображения в пользовательском интерфейсе списка всех курсов со всеми их модулями. Для этого вы используете агрегаты из BC1. Вы используете StudentEnrolment, чтобы показать конкретному студенту его статус в домашних заданиях по курсу. - person Constantin Galbenu; 23.05.2018
comment
Хорошо, это правда. Ответ: если я хочу получить название модуля, я должен добавить getTitleModule в домашнее задание. Последний вопрос: хочет ли учитель видеть домашнее задание, которое ученики загрузили в каждый модуль. Должен ли я создать учитель в BC2? - person Agustin Castro; 23.05.2018
comment
@AgustinCastro нет, я так не думаю. Вы просто получите все StudentEnrolments. Если у каждого курса есть учитель, вы также можете добавить учителя в BC2, в StudentEnrolment вдоль / внутри курса, но не как объект Entity, а как объект Value. Итак, вы добавляете это только для фильтрации. (Примечание: когда я говорю Entity, я говорю с точки зрения DDD. То, что в DDD является объектом Value, может быть DB-Entity с точки зрения DB; это зависит от типа сохраняемости, которая у вас есть) - person Constantin Galbenu; 23.05.2018
comment
хорошо, но тогда курс - Сущность. потому что теперь у курса есть id, title, status и teacherId (vo). статус может измениться, это означает, что он не является неизменным, а также идентификатор курса используется как идентификатор, а не только как ссылка, верно? - person Agustin Castro; 23.05.2018
comment
@AgustinCastro status находится в StudentEnrolment, а не в course. the course id is using as identity not just as a link - Я имел в виду, что вы можете использовать этот идентификатор для отображения ссылки на пользователя в пользовательском интерфейсе, например <a href="/course/{course.getId()}">View course</a> - person Constantin Galbenu; 23.05.2018
comment
поэтому, если идентификатор курса является идентификатором, курс должен быть сущностью. Я вижу этот курс неизменным, потому что id и title не меняются, но у него есть личность, и VO может иметь идентичность, это только для сущности. - person Agustin Castro; 23.05.2018
comment
@AgustinCastro Неизменяемая сущность вряд ли является сущностью, но не имеет значения, как вы ее называете, если ясно, что она не меняется, по крайней мере, не в этой BC - person Constantin Galbenu; 23.05.2018
comment
хорошо, я читал статьи, чтобы раскрыть конечную точку при применении ddd и записать это: no-kill-switch.ghost.io/, в котором говорится:« Предоставьте свой AR как объект (ресурс) API », что означает, что в этом примере мои конечные точки должны be / student / id / enrollment / и используйте метод пути для загрузки домашнего задания. Но не могу ли я предоставить такую ​​услугу: / student / id / enrollment / id / homework and post method? - person Agustin Castro; 23.05.2018
comment
@AgustinCastro Думаю, ты можешь - person Constantin Galbenu; 23.05.2018
comment
@AgustinCastro, если у вас архитектура Restful, тогда URL-адреса не имеют большого значения - person Constantin Galbenu; 23.05.2018