¿Cuándo debería una raíz agregada contener otra AR (y cuándo no debería)

14

Permítame comenzar por disculparme primero por la longitud de la publicación, pero realmente quería transmitir tantos detalles por adelantado para no tomarme el tiempo de ir y venir en comentarios.

Estoy diseñando una aplicación siguiendo un enfoque de DDD y me pregunto qué orientación puedo seguir para determinar si una raíz agregada debe contener otra AR o si deben dejarse como AR independientes "independientes".

Tome el caso de una sencilla aplicación de reloj de tiempo que permite a los empleados registrar su entrada o salida durante el día. La IU les permite ingresar su ID de empleado y PIN, que luego se valida y se recupera el estado actual del empleado. Si el empleado está registrado actualmente, la IU muestra un botón de "Salida de reloj"; y, a la inversa, si no están sincronizados, el botón lee "Clock In". La acción realizada por el botón también corresponde al estado del empleado.

La aplicación es un cliente web que llama a un servidor de servicios de fondo expuesto a través de una interfaz de servicio RESTful. Mi primer paso para crear URL intuitivas y legibles resultó en los siguientes dos puntos finales:

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

NOTA: Estos se usan después de que el ID y el PIN del empleado se hayan validado y se pase un "token" que representa al "usuario" en un encabezado. Esto se debe a que hay un "modo de administrador" que permite a un administrador o supervisor registrar o retirar a otro empleado. Pero, por el bien de esta discusión, estoy tratando de mantenerlo simple.

En el servidor, tengo un ApplicationService que proporciona la API. Mi idea inicial para el método ClockIn es algo como:

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

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

    employee.ClockIn();

    EmployeeRepository.Save();
}

Esto parece bastante sencillo hasta que nos damos cuenta de que la información de la tarjeta de tiempo del empleado en realidad se mantiene como una lista de transacciones. Eso significa que cada vez que llamo ClockIn o ClockOut, no estoy cambiando directamente el estado del empleado, sino que, en cambio, estoy agregando una nueva entrada en la hoja de tiempo del empleado. El estado actual del empleado (registrado o no) se deriva de la entrada más reciente en la hoja de tiempo.

Entonces, si voy con el código que se muestra arriba, mi repositorio debe reconocer que las propiedades persistentes del Empleado no han cambiado, pero que se agregó una nueva entrada a la Hoja de tiempo del empleado y se realiza una inserción en el almacén de datos.

Por otro lado (y aquí está la última pregunta de la publicación), TimeSheet parece que es una raíz agregada y tiene identidad (la identificación del empleado y el período) y podría implementar la misma lógica con la misma facilidad. TimeSheet.ClockIn (employeeId).

Me encuentro debatiendo los méritos de los dos enfoques y, como se indica en el párrafo inicial, me pregunto qué criterios debería evaluar para determinar qué enfoque es más adecuado para el problema.

    
pregunta SonOfPirate 13.04.2012 - 20:18

3 respuestas

4

Me inclinaría por implementar un servicio para el seguimiento del tiempo:

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

   void TrackClockOut(Employee employee, Timesheet timesheet);

}

No creo que sea responsabilidad de la hoja de tiempo registrarse o cerrar el reloj, tampoco es responsabilidad del empleado.

    
respondido por el CodeART 07.05.2012 - 19:43
3

Las raíces agregadas no deben contener una a la otra (aunque pueden contener ID a otros).

Primero, ¿es TimeSheet realmente una raíz agregada? El hecho de que tenga identidad lo convierte en una entidad, no necesariamente en un AR. Echa un vistazo a una de las reglas:

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

Usted define la identidad de TimeSheet como ID de empleado & período de tiempo, lo que sugiere que TimeSheet es una parte del empleado con un período de tiempo que es la identidad local. Dado que se trata de una aplicación de Reloj de tiempo , ¿el objetivo principal de Employee es ser un contenedor para TimeSheets?

Suponiendo que tiene que ARs, ClockIn y ClockOut parecen más operaciones de TimeSheet que de Employee. El método de la capa de servicio podría tener este aspecto:

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

Si realmente necesita realizar un seguimiento del estado registrado tanto en Employee como en TimeSheet, entonces eche un vistazo a los Eventos de Dominio (no creo que estén en el libro de Evans, pero hay numerosos artículos en línea). Se vería algo como: Employee.ClockIn () genera el evento EmployeeClockedIn, que un controlador de eventos recoge y, a su vez, llama a TimeSheet.LogEmployeeClockIn ().

    
respondido por el Misko 07.05.2012 - 19:38
3
  

Estoy diseñando una aplicación siguiendo un enfoque de DDD y me pregunto qué orientación puedo seguir para determinar si una raíz agregada debe contener otra AR o si deben dejarse como AR independientes "independientes".

Una raíz agregada nunca debe contener otra raíz agregada.

En su descripción, tiene una entidad empleada, y también una entidad de parte de horas. Estas dos entidades son distintas, pero podrían incluir referencias entre sí (por ejemplo, esta es la hoja de tiempo de Bob).

Eso es mucho el modelado básico.

La pregunta de la raíz agregada es ligeramente diferente. Si estas dos entidades son distintas entre sí transaccionalmente, entonces pueden modelarse correctamente como dos agregados distintos. Por transacción distinta, quiero decir que no hay una invariante comercial que requiera conocer el estado actual del empleado y el estado actual de TimeSheet simultáneamente.

En base a lo que describe, Timesheet.clockIn y Timesheet.clockOut nunca necesitan verificar ningún dato en el Empleado para determinar si el comando está permitido. Así que para esta gran parte del problema, dos AR diferentes parecen razonables.

Otra forma de considerar los límites de AR sería preguntar qué tipo de ediciones pueden ocurrir simultáneamente. ¿Se le permite al gerente despedir al empleado mientras que, al mismo tiempo, RR.HH. está jugando con el perfil del empleado?

  

Por otro lado (y aquí está la última pregunta de la publicación), TimeSheet parece que es una raíz agregada y tiene identidad (la identificación del empleado y el período)

Identidad solo significa que es una entidad, es la invariante de negocios que determina si se debe considerar una raíz agregada separada.

  

Sin embargo, hay ciertas reglas que restringen si un Empleado puede registrarse. Estas reglas son impulsadas principalmente por el estado actual del Empleado, por ejemplo, si están despedidos, ya están programados, están programados para trabajar ese día. o incluso el cambio actual, etc. ¿Debería TimeSheet poseer este conocimiento?

Tal vez - el agregado debería. Eso no significa necesariamente que la parte de horas deba hacerlo.

Es decir, si las reglas para modificar un parte de horas dependen del estado actual de la entidad del empleado, entonces la hoja de horas y el empleado definitivamente deben formar parte del mismo agregado, y el conjunto en su conjunto es responsable de garantizar que Se siguen las reglas.

Un agregado tiene una entidad raíz; identificarlo es parte del rompecabezas. Si un empleado tiene más de una parte de horas, y ambas son parte del mismo agregado, entonces la parte de horas definitivamente no es la raíz. Lo que significa que la aplicación no puede modificar o enviar comandos directamente a la hoja de tiempo, ya que deben enviarse al objeto raíz (probablemente el empleado), que puede delegar parte de la responsabilidad.

Otro control sería considerar cómo se crean las Hojas de tiempo. Si se crean de forma implícita cuando un empleado se registra, entonces esa es otra sugerencia de que son una entidad subordinada en el agregado.

Además, es poco probable que sus agregados tengan su propio sentido del tiempo. En su lugar, el tiempo se les debe pasar

employee.clockIn(when);
    
respondido por el VoiceOfUnreason 28.02.2016 - 08:55

Lea otras preguntas en las etiquetas