Коли Агрегатний корінь повинен містити інший AR (а коли не повинен)


15

Дозвольте спочатку попросити вибачення за тривалість публікації, але я дуже хотів передати якомога більше деталей, щоб я не витрачав час на коментарі.

Я розробляю програму за підходом DDD, і мені цікаво, до яких вказівок я можу керуватися, щоб визначити, чи повинен Кореневий корінь містити інший АР або якщо їх слід залишити як окремі, "стоячі" АР.

Візьміть до уваги просту програму Time Clock, яка дозволяє працівникам прилаштовуватися або виходити протягом дня. Користувацький інтерфейс дозволяє їм вводити ідентифікаційний номер і PIN-код свого працівника, який потім перевіряється та отримується поточний стан працівника. Якщо працівник наразі працює на інтерфейсі, в інтерфейсі відображається кнопка «Годинник»; і, навпаки, якщо вони не зафіксовані, кнопка пише "Clock In". Дія, здійснене кнопкою, відповідає і стану працівника.

Додаток - це веб-клієнт, який викликає бек-сервер, відкритий через інтерфейс служби RESTful. Мій перший пропуск щодо створення інтуїтивно зрозумілих, читаних URL-адрес призвів до наступних двох кінцевих точок:

http://myhost/employees/{id}/clockin
http://myhost/employees/{id}/clockout

ПРИМІТКА. Вони використовуються після підтвердження посвідчення особи та PIN-коду працівника і в заголовку передається "маркер", що представляє "користувача". Це пояснюється тим, що існує "режим менеджера", який дозволяє керівнику чи керівникові залучати до роботи іншого співробітника. Але заради цієї дискусії я намагаюся зробити це просто.

На сервері у мене є ApplicationService, який надає API. Моя початкова ідея методу ClockIn - це щось на зразок:

public void ClockIn(String id)
{
    var employee = EmployeeRepository.FindById(id);

    if (employee == null) throw SomeException();

    employee.ClockIn();

    EmployeeRepository.Save();
}

Це виглядає досить прямо, поки ми не усвідомили, що інформація про карту працівника, що насправді, зберігається як перелік операцій. Це означає, що кожного разу, коли я телефоную на ClockIn або ClockOut, я не змінюю безпосередньо стан працівника, а натомість додаю новий запис у часовий лист працівника. Поточний стан працівника (увійшов чи ні) походить від останнього запису у часовій таблиці.

Отже, якщо я перейду з наведеним вище кодом, моє сховище повинне визнати, що стійкі властивості Співробітника не змінилися, а що додано новий запис у часовий лист працівника та виконати вставку у сховище даних.

З іншого боку (і ось головне питання посади), TimeSheet здається, що це сукупний корінь, а також він має ідентифікацію (ідентифікатор співробітника та період), і я міг би так само легко реалізувати ту саму логіку, що і TimeSheet.ClockIn (ідентифікатор співробітника).

Мені здається, що я обговорюю переваги двох підходів, і, як сказано у вступному пункті, цікаво, які критерії я повинен оцінювати, щоб визначити, який підхід більше підходить для проблеми.


Я відредагував / оновив публікацію, щоб питання було зрозумілішим та використовував кращий сценарій (сподіваємось).
SonOfPirate

Відповіді:


4

Я схильний би впровадити послугу для відстеження часу:

public interface ITimeSheetTrackingService
{
   void TrackClockIn(Employee employee, Timesheet timesheet);

   void TrackClockOut(Employee employee, Timesheet timesheet);

}

Я не думаю, що це відповідальність за реєстрацію та виведення годин, а також відповідальність працівника.


3

Зведені корені не повинні містити одне одного (хоча вони можуть містити ідентифікатори для інших).

По-перше, чи справді TimeSheet є сукупним коренем? Той факт, що він має ідентичність, робить його сутністю, а не обов'язково AR. Ознайомтесь із одним із правил:

Root Entities have global identity.  Entities inside the boundary have local 
identity, unique only within the Aggregate.

Ви визначаєте особу TimeSheet як ідентифікатор працівника та часовий період, що говорить про те, що TimeSheet є частиною Співробітника, а період часу є локальною ідентичністю. Оскільки це програма Time Clock , чи головна мета Employee бути контейнером для TimeSheets?

Якщо припустити, що у вас є AR, ClockIn та ClockOut здаються більш схожими на операції TimeSheet, ніж на службовців. Ваш метод рівня обслуговування може виглядати приблизно так:

public void ClockIn(String employeeId)
{
    var timeSheet = TimeSheetRepository.FindByEmployeeAndTime(employeeId, DateTime.Now);    
    timeSheet.ClockIn();    
    TimeSheetRepository.Save();
}

Якщо вам дійсно потрібно відстежувати стан зайнятості як у службовця, так і в часовій таблиці, тоді подивіться доменні події (я не вірю, що вони знаходяться в книзі Еванса, але в мережі є численні статті). Це виглядатиме приблизно так: Employee.ClockIn () викликає подію EmployeeClockedIn, яку обробник події піднімає і, в свою чергу, викликає TimeSheet.LogE EmployeeClockIn ().


Я бачу ваші моменти. Однак існують певні правила, які обмежують, коли і коли працівника можна запустити. Ці правила здебільшого керуються поточним станом працівника, наприклад, чи припиняються вони, вже надіслані, планується працювати в цей день або навіть поточний зсув і т. д. Чи повинен ТаймШест володіти цими знаннями?
SonOfPirate

3

Я розробляю програму за підходом DDD, і мені цікаво, до яких вказівок я можу керуватися, щоб визначити, чи повинен Кореневий корінь містити інший АР або якщо їх слід залишити як окремі, "стоячі" АР.

Сукупний корінь ніколи не повинен містити іншого сукупного кореня.

В описі, у вас є Employee сутність , також і сутність Timesheet. Ці дві сутності є різними, але можуть містити посилання один на одного (наприклад: це часовий графік Боба).

Це багато є базовим моделюванням.

Сукупне корінне питання дещо інше. Якщо ці два об'єкти трансакційно відрізняються один від одного, то вони можуть бути правильно змодельовані як два різних агрегати. Під транзакційним розрізненням я маю на увазі, що не існує інваріанта бізнесу, який вимагає одночасно знати поточний стан працівника та поточний стан TimeSheet.

Виходячи з того, що ви описуєте, Timesheet.clockIn та Timesheet.clockOut ніколи не потрібно перевіряти будь-які дані в Employee, щоб визначити, чи дозволена команда. Тож для цієї більшої частини проблем два різних АР видаються розумними.

Іншим способом розгляду меж AR було б запитати, які види змін можуть дозволитись одночасно. Чи дозволено керівникові відслідковувати працівника, в той же час HR співпадає з профілем працівника?

З іншого боку (і ось головне питання посади), TimeSheet здається, що це сукупний корінь, а також має особистість (посвідчення працівника та період)

Ідентичність означає лише, що це сутність - бізнес-інваріант, який визначає, чи слід вважати її окремим сукупним коренем.

Однак існують певні правила, які обмежують, коли і коли працівника можна запустити. Ці правила здебільшого керуються поточним станом працівника, наприклад, чи припиняються вони, вже надіслані, планується працювати в цей день або навіть поточний зсув і т. д. Чи повинен ТаймШет володіти цими знаннями?

Можливо - сукупність повинна. Це не обов'язково означає, що повинен бути Таблиця.

Що означає, якщо правила зміни часового журналу залежать від поточного стану підрозділу працівника, то працівник та "Таблиця", безумовно, повинні бути частиною одного агрегату, а сукупність в цілому несе відповідальність за забезпечення відповідності правил слідували.

Сукупність має одну кореневу сутність; її ідентифікація є частиною головоломки. Якщо у працівника є більше одного Табеля, і вони обидва є одним і тим же агрегатом, то Timesheet, безумовно, не є коренем. Це означає, що програма не може безпосередньо змінювати або відправляти команди до часового діапазону - їх потрібно відправити до кореневого об’єкта (імовірно, працівника), який може делегувати частину відповідальності.

Ще однією перевіркою було б врахувати, як створюються часові таблиці. Якщо вони створюються неявно, коли працівник працює, то це ще один натяк на те, що вони є підпорядкованим суб'єктом у сукупності.

В бік навряд чи ваші агрегати повинні мати власне відчуття часу. Натомість їм слід провести час

employee.clockIn(when);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.