Как избежать масс загрузки данных в CQRS / DDD Commands / Events? - PullRequest
2 голосов
/ 16 мая 2019

У нас есть DDD AppDomain, содержащий семинары, участников и их еду.У нас до 1000 участников на семинар, до 50 блюд на участника.Мы решили, что Семинары, Участники и Питание - это совокупности, чтобы эти совокупности были небольшими.Пользователь может перенести весь семинар со всеми участниками или перенести одного участника.Таким образом, у нас есть команды "RescheduleSeminarCommand" и "RescheduleParticipantCommand".

Проблема возникает при перепланировании семинара: "RescheduleSeminarCommand" приводит к "SeminarRescheduledEvent", которая приводит к "RescheduleParticipantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandantCommandCountЭто будет означать загрузку каждого участника из хранилища - так 1000 запросов к базе данных.Каждый «RescheduleParticipantCommand» приводит к «ParticipantRescheduledEvent», который запускает «RescheduleMealsCommand», который загружает питание для каждого отдельного участника - так еще 1000 запросов к базе данных.

Как мы можем уменьшить количество запросов к базе данных?

1) Мы подумали о расширении «RescheduleParticipantCommand» и «RescheduleMealsCommand» с помощью SeminarId, чтобы мы могли загружать не только одного участника / еду, но и всех участников / еду для всего семинара.

2) Другим способомбыло бы создать дополнительные события / команды, такие как «RescheduleParticipantsForSeminarCommand», «PlayersForSeminarRescheduleEvent» и «RescheduleMealsForSeminarCommand» и т. д.

Что, по вашему мнению, лучше?1), 2) или что-то другое, о чем мы не думали?


Хорошо, я приведу некоторые детали, которые я пропустил в моем первом описании:

Если есть следующееклассы

class Seminar
{
    UUID SeminarId,
    DateTime Begin,
    DateTime End
}

// Arrival/Departure of a participant may differ
// from Begin/End of the seminar
class Participant
{
    UUID ParticipantId
    UUID SeminarId,
    DateTime Arrival,
    DateTime Departure
}

// We have one Meal-Object for breakfast, one for lunch and 
// one for dinner (and additional for other meals) per day 
// of the stay of the participant
class Meal
{
    UUID MealId,
    UUID ParticipantId,
    DateTime Date,
    MealType MealType
}

Пользователи могут

  • изменить Прибытие / Депатирование одного участника с помощью "RescheduleParticipantCommand", который также изменит их питание на новые даты.

  • изменить начало / конец семинара с помощью "RescheduleSeminarCommand", который изменит Прибытие / Депатирование всех участников на новый Начало / Конец и соответственно изменит их питание.

Ответы [ 2 ]

0 голосов
/ 17 мая 2019

Команды - это вещи, которые могут быть отклонены правилами вашего домена.Если вы вызываете команду из-за события (что-то, что уже сделано и не может быть отклонено, поскольку оно проходит все правила домена), имейте это в виду;даже если новая команда ничего не делает, потому что отклонена;ваша система должна быть в согласованном состоянии.Основное правило: если вы вызываете событие, это потому, что система находится в согласованном состоянии, даже если это событие подразумевает больше команд в системе, которые могут быть отклонены или ничего не изменить в системе.

Итак, согласноВаши комментарии после того, как агрегат семинара принимает новые даты в соответствии со своими правилами;Вы изменяете даты участников без необходимости проверять больше правил.

Тогда решение состоит в том, чтобы просто изменить все в постоянстве, а не спамить детализированные команды для каждого изменения, которое вы хотите.

Пример реляционной базы данных:

Update Seminar ( Begin , End) Values ( '06/02/2019' ,06/06/2019 ) where SeminarID = @SeminarID;

Update Participant ( Arrival , Departure ) Values ( '06/02/2019' ,06/06/2019 ) where SeminarId = @SeminarID

PS: Почему бы не провести семинар «Старт / Конец» в настойчивости и не перенести эти данные в совокупность гидратаций Участников (Прибытие / Отъезд)?Таким образом, вы всегда будете иметь согласованное состояние в вашей системе, не беспокоясь об изменении нескольких вещей.

0 голосов
/ 16 мая 2019

Возможно, вам не хватает понятия SeminarSchedule .Сначала давайте зададим пару вопросов, которые повлияют на модель

  • Если у вас есть семинар, разделен ли он на какую-то лекцию, презентацию и т. Д., Или это семинар по тому же принципу?только для разных людей в разное время?

  • Можете ли вы сначала подписать человека на семинар, а затем решить, во сколько этот человек будет присутствовать?

Я приведу пример в псевдокоде.

ПРИМЕЧАНИЕ: я пропущу приемы пищи, так как вопрос о расписании, но они вписываются в эту модель.Я также расскажу о логике, связанной с ними, просто пропустите их в коде

Сначала давайте скажем, каковы наши требования.

  • Это семинар с одной стороны(лекция, тренировка все что угодно) разделить на временные интервалы.Одна и та же лекция будет прочитана для разных людей, начиная с разных времен.

  • Участники могут подписаться, не будучи запланированными на определенное время.

  • Когда участник подписывается, нам нужно приготовить ему едуна основе предпочтений (например, он / она может быть вегетарианцем или веганом).

  • Планирование будет выполняться в определенное время от пользователей системы.Они будут принимать информацию участников при выполнении графика.Например, мы можем захотеть, чтобы люди одного возраста были в одном временном интервале или по некоторым другим критериям.

Вот код:

class Seminar {
    UUID ID;
    // other info for seminar like description, name etc.
}

class Participant {
    UUID ID;
    UUID SeminarID;
    // other data for participant, name, age, meal preferences etc.
}

class TimeSlot {

    Time StartTime;
    TimeInterval Duration;
    ReadonlyCollection<UUID> ParticipantIDs;

    void AddParticipant(UUID participantID) { }
}

class SeminarSchedule {

    UUID SeminarID;

    Date Date;
    Time StartTime;
    TimeInterval Duration;

    ReadOnlyCollection<TimeSlot> TimeSlots;

    void ChangeDate(Date newDate) { }
    void ChangeStartTime(Time startTime) { }
    void ChangeDuration(TimeInterval duration) { }
    void ScheduleParticipant(Participant p, Time timeSlotStartTime) { }
    void RemoveParticipantFromSchedule(Participant p) { }
    void RescheduleParticipant(Participant p, Time newTimeSlotStartTime) { }
}

Здесь мы имеем3 агрегата: Seminar, Participant и SeminarSchedule.

Если вам нужно изменить какую-либо информацию, относящуюся к Seminar или Participant, вы ориентируетесь только на эти агрегаты.

С другой стороны, если вам нужно что-то сделать с расписанием, агрегат SeminarSchedule (являющийся границей транзакций для планирования) будет обрабатывать эту команду, обеспечивая согласованность.Вы также можете применить параллельный контроль над расписанием.Возможно, вы не захотите, чтобы несколько человек меняли расписание одновременно.Например, одно изменение StartTime, а другое изменение Duration или два пользователя добавляют одного и того же участника в расписание.Вы можете использовать Оптимистическая автономная блокировка в совокупности SeminarSchedule

Например, изменение Duration из StartTime для SeminarSchedule повлияет на все TimeSlots.

Если вы удалите Participant из Seminar, вам придется удалить его и из расписания.Это может быть реализовано с возможной последовательностью и обработкой события ParticipantRemoved, или вы можете использовать Saga .

Еще одна вещь, которую мы должны принять во внимание при моделировании агрегатов, - это также логика подписина семинар работает.

Предположим, что участник должен подписаться на семинар, прежде чем планировать их.Возможно планирование будет выполнено позже, определяя группы людей по некоторым критериям.Вышеуказанная модель будет работать нормально.Это позволит пользователям подписать Participant на Seminar.Позже, когда расписание назначено, другие пользователи смогут составить расписание, посмотрев, какие участники подписали.

Давайте рассмотрим противоположный случай и скажем, что незапланированные участники не могут присутствовать на семинаре.

В этом случае мы можем добавить сущность Participant к совокупности SeminarSchedule, но это приведет к загрузке всей этой совокупности, даже если вам потребуется изменить некоторую информацию для одного участника.Это не очень практично.

Поэтому, чтобы сохранить хорошее разделение, которое у нас есть, мы можем использовать Saga или ProcessManager для обеспечения согласованности.Мы также можем добавить концепцию ReservedPlace в совокупности SeminarSchedule.Таким образом, вы можете зарезервировать место, затем добавить участника в расписание и затем удалить ReservedPlace, назначив участника временному интервалу.Поскольку это сложный процесс, охватывающий несколько агрегатов, Saga определенно на месте.

Другой способ сделать это - определить концепцию SeminarSignRequest, которую может создать человек.Позже этот запрос может быть одобрен, если есть питание и / или место.Возможно, мы достигли максимального числа людей или не имеем достаточного количества пищи и т. Д. Это, вероятно, также процесс, поэтому вам также может потребоваться Saga здесь.

Для получения дополнительной информации см. эту статью и это видео .

...