Zacznę od przeprosin za długość posta, ale naprawdę chciałem przekazać z góry tyle szczegółów, aby nie tracić czasu na komentarze.
Projektuję aplikację zgodnie z podejściem DDD i zastanawiam się, jakie wskazówki mogę zastosować, aby ustalić, czy Root Aggregate powinien zawierać inny AR, czy też powinny być pozostawione jako osobne, „wolnostojące” AR.
Weźmy na przykład prostą aplikację Zegar czasu, która pozwala Pracownikom wejść lub wyjść na cały dzień. Interfejs użytkownika pozwala im wprowadzić identyfikator pracownika i kod PIN, który jest następnie sprawdzany i pobierany jest bieżący stan pracownika. Jeśli pracownik jest obecnie zalogowany, interfejs użytkownika wyświetla przycisk „Wyrejestruj”; i odwrotnie, jeśli nie są one taktowane, przycisk wyświetla „Clock In”. Działanie podejmowane przez przycisk odpowiada również stanowi pracownika.
Aplikacja jest klientem WWW, który wywołuje serwer zaplecza udostępniony przez interfejs usługi RESTful. Mój pierwszy krok w tworzeniu intuicyjnych, czytelnych adresów URL zaowocował następującymi dwoma punktami końcowymi:
http://myhost/employees/{id}/clockin
http://myhost/employees/{id}/clockout
UWAGA: Są one używane po sprawdzeniu poprawności ID pracownika i PIN-u, a „token” reprezentujący „użytkownika” jest przekazywany w nagłówku. Wynika to z faktu, że istnieje „tryb menedżera”, który pozwala menedżerowi lub przełożonemu zarejestrować lub wylogować innego pracownika. Ale ze względu na tę dyskusję staram się to uprościć.
Na serwerze mam usługę ApplicationService, która zapewnia interfejs API. Mój początkowy pomysł na metodę ClockIn jest mniej więcej taki:
public void ClockIn(String id)
{
var employee = EmployeeRepository.FindById(id);
if (employee == null) throw SomeException();
employee.ClockIn();
EmployeeRepository.Save();
}
Wygląda to dość prosto, dopóki nie uświadomimy sobie, że informacje o karcie czasu pracownika są faktycznie przechowywane jako lista transakcji. Oznacza to, że za każdym razem, gdy dzwonię do ClockIn lub ClockOut, nie zmieniam bezpośrednio stanu pracownika, ale dodajemy nowy wpis do arkusza czasu pracownika. Bieżący stan pracownika (zarejestrowany w zegarze lub nie) pochodzi z najnowszego wpisu w arkuszu czasu.
Tak więc, jeśli pójdę z kodem pokazanym powyżej, moje repozytorium musi rozpoznać, że trwałe właściwości pracownika nie uległy zmianie, ale nowy wpis został dodany do arkusza czasu pracownika i wykonał wstawienie do magazynu danych.
Z drugiej strony (i oto ostateczne pytanie dotyczące postu) TimeSheet wygląda na to, że jest to Root Aggregate, a także ma tożsamość (identyfikator pracownika i okres) i mógłbym równie łatwo zaimplementować tę samą logikę co TimeSheet.ClockIn (numer identyfikacyjny pracownika).
Dyskutuję o zaletach obu podejść i, jak stwierdzono w pierwszym akapicie, zastanawiam się, jakie kryteria powinienem ocenić, aby ustalić, które podejście jest bardziej odpowiednie dla problemu.