Quando una radice di aggregazione dovrebbe contenere un'altra AR (e quando non dovrebbe)

14

Vorrei iniziare prima scusandomi per la lunghezza del post, ma volevo davvero trasmettere il maggior numero di dettagli in modo che non prendessi il tuo tempo andando avanti e indietro nei commenti.

Sto progettando un'applicazione seguendo un approccio DDD e mi sto chiedendo quale guida posso seguire per determinare se una radice di aggregazione dovrebbe contenere un'altra AR o se dovrebbero essere lasciate come AR separate, "indipendenti".

Prendi il caso di una semplice applicazione Time Clock che consente ai Dipendenti di attivarsi o meno per il giorno. L'interfaccia utente consente loro di inserire l'ID e il PIN del dipendente che viene quindi convalidato e lo stato corrente del dipendente recuperato. Se il dipendente è attualmente in time-in, l'interfaccia utente visualizza un pulsante "Clock Out"; e, al contrario, se non sono sincronizzati, il pulsante legge "Clock In". L'azione eseguita dal pulsante corrisponde anche allo stato del dipendente.

L'applicazione è un client Web che chiama un server back-end esposto tramite un'interfaccia di servizio RESTful. Il mio primo passaggio alla creazione di URL intuitivi e leggibili ha portato ai seguenti due endpoint:

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

NOTA: Questi vengono utilizzati dopo che l'ID e il PIN del dipendente sono stati convalidati e un "token" che rappresenta l'utente viene passato in un'intestazione. Questo perché esiste una "modalità manageriale" che consente a un manager o supervisore di inserire o ritirare un altro dipendente. Ma, per il gusto di questa discussione, sto cercando di renderlo semplice.

Sul server, ho un ApplicationService che fornisce l'API. La mia idea iniziale per il metodo ClockIn è qualcosa del tipo:

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

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

    employee.ClockIn();

    EmployeeRepository.Save();
}

Sembra piuttosto semplice fino a quando non ci rendiamo conto che le informazioni sulla carta del tempo del dipendente sono effettivamente mantenute come un elenco di transazioni. Ciò significa che ogni volta che chiamo ClockIn o ClockOut, non sto cambiando direttamente lo stato del Dipendente ma, invece, aggiungo una nuova voce nel TimeSheet del dipendente. Lo stato corrente del Dipendente (con o senza clock) è derivato dalla voce più recente nel TimeSheet.

Quindi, se vado con il codice mostrato sopra, il mio repository deve riconoscere che le proprietà persistibili del Dipendente non sono cambiate ma che una nuova voce è stata aggiunta nel TimeSheet del dipendente ed eseguito un inserimento nell'archivio dati.

D'altro canto (ed ecco l'ultima domanda del post), TimeSheet sembra essere una radice di aggregazione così come ha l'identità (l'ID e il periodo del dipendente) e potrei altrettanto facilmente implementare la stessa logica di TimeSheet.ClockIn (employeeId).

Mi trovo a discutere i meriti dei due approcci e, come affermato nel paragrafo di apertura, mi chiedo quali criteri dovrei valutare per determinare quale approccio sia più adatto al problema.

    
posta SonOfPirate 13.04.2012 - 20:18
fonte

3 risposte

4

Sarei propenso a implementare un servizio per il monitoraggio del tempo:

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

   void TrackClockOut(Employee employee, Timesheet timesheet);

}

Non penso che sia la responsabilità della scheda orario a scadere o a scadere, né è responsabilità del dipendente.

    
risposta data 07.05.2012 - 19:43
fonte
3

Le radici aggregate non devono contenerle a vicenda (sebbene possano contenere ID ad altri).

In primo luogo, TimeSheet è davvero una radice aggregata? Il fatto che abbia identità lo rende un'entità, non necessariamente un AR. Consulta una delle regole:

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

Definisci l'identità del TimeSheet come ID dipendente e amp; periodo di tempo, che suggerisce che TimeSheet è una parte del Dipendente con periodo di tempo che è l'identità locale. Poiché questa è un'applicazione Time Clock , lo scopo principale di Employee è di essere un contenitore per i TimeSheets?

Supponendo che devi AR, ClockIn e ClockOut sembrano più operazioni di TimeSheet rispetto a quelle di Dipendenti. Il tuo metodo del livello di servizio potrebbe essere simile a questo:

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

Se hai davvero bisogno di tenere traccia dello stato di clock in Employee e TimeSheet, dai un'occhiata a Domain Events (non credo che siano nel libro di Evans, ma ci sono numerosi articoli online). Sembrerebbe qualcosa del tipo: Employee.ClockIn () solleva l'evento EmployeeClockedIn, che viene raccolto da un gestore di eventi che a sua volta chiama TimeSheet.LogEmployeeClockIn ().

    
risposta data 07.05.2012 - 19:38
fonte
3

I am designing an application following a DDD approach and am wondering what guidance I can follow to determine whether an Aggregate Root should contain another AR or if they should be left as separate, "free-standing" ARs.

Una radice aggregata non dovrebbe mai contenere un'altra radice aggregata.

Nella tua descrizione, hai un Employee entity , e allo stesso modo un'entità Timesheet. Queste due entità sono distinte, ma potrebbero includere riferimenti l'una all'altra (es .: questa è la scheda attività di Bob).

Questa è la modellazione di base.

La domanda radice aggregata è leggermente diversa. Se queste due entità sono transazionali distinte l'una dall'altra, possono essere modellate correttamente come due distinti aggregati. Per distinzione transazionale, intendo che non esiste alcuna invarianza di business che richieda la conoscenza dello stato attuale del Dipendente e lo stato corrente di TimeSheet contemporaneamente.

In base a ciò che descrivi, Timesheet.clockIn e Timesheet.clockOut non devono mai controllare i dati in Employee per determinare se il comando è consentito. Quindi per questo gran parte del problema, due diverse AR sembrano ragionevoli.

Un altro modo di considerare i confini dell'AR sarebbe chiedersi quali tipi di modifiche possono accadere simultaneamente. Il gestore ha il permesso di ritirare il dipendente mentre allo stesso tempo le risorse umane stanno manipolando il profilo del dipendente?

On the other hand (and here's the ultimate question of the post), TimeSheet seems like it is an Aggregate Root as well as it has identity (the employee ID and period)

Identità significa solo che è un'entità: è l'invarianza di business che determina se deve essere considerata una radice aggregata separata.

However, there are certain rules that constrain if and when an Employee can be clocked in. These rules are mostly driven by the current state of the Employee such as whether they are terminated, already clocked-in, scheduled to work that day or even the current shift, etc. Should the TimeSheet possess this knowledge?

Forse - l'aggregato dovrebbe. Ciò non significa necessariamente che la scheda attività dovrebbe.

Vale a dire, se le regole per la modifica di una scheda attività dipendono dallo stato corrente dell'entità Dipendente, Dipendente e Scheda attività devono necessariamente far parte dello stesso aggregato e l'aggregato nel suo insieme è responsabile di garantire che le regole sono seguite.

Un aggregato ha un'entità radice; identificarlo fa parte del puzzle. Se un dipendente ha più di una scheda attività e entrambi fanno parte dello stesso aggregato, allora Timesheet non è sicuramente la radice. Ciò significa che l'applicazione non può modificare direttamente o inviare comandi alla scheda attività - devono essere inviati all'oggetto radice (presumibilmente l'impiegato), che può delegare parte della responsabilità.

Un altro controllo dovrebbe essere quello di considerare come vengono creati i timesheet. Se vengono creati in modo implicito quando un dipendente esegue un clock, allora è un altro suggerimento che si tratti di un'entità subordinata nell'aggregato.

Per inciso, è improbabile che i tuoi aggregati debbano avere il loro senso del tempo. Invece, il tempo dovrebbe essere passato a loro

employee.clockIn(when);
    
risposta data 28.02.2016 - 08:55
fonte

Leggi altre domande sui tag