Supongamos que tenemos un sistema de registro de tareas, cuando se registra una tarea, el usuario especifica una categoría y la tarea por defecto es un estado de "Excepcional". Suponga en este caso que la Categoría y el Estado deben implementarse como entidades. Normalmente haría esto:
Capa de aplicación:
public class TaskService
{
//...
public void Add(Guid categoryId, string description)
{
var category = _categoryRepository.GetById(categoryId);
var status = _statusRepository.GetById(Constants.Status.OutstandingId);
var task = Task.Create(category, status, description);
_taskRepository.Save(task);
}
}
Entidad:
public class Task
{
//...
public static void Create(Category category, Status status, string description)
{
return new Task
{
Category = category,
Status = status,
Description = descrtiption
};
}
}
Lo hago así porque constantemente me dicen que las entidades no deberían acceder a los repositorios, pero tendría mucho más sentido para mí si hiciera esto:
Entidad:
public class Task
{
//...
public static void Create(Category category, string description)
{
return new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
}
}
El repositorio de estado es la dependencia inyectada de todos modos, por lo que no hay una dependencia real, y esto me parece más, ya que es el dominio el que está tomando la decisión de que una tarea predeterminada sea sobresaliente. La versión anterior se siente como si la capa de aplicación tomara esa decisión. ¿Por qué los contratos de repositorio a menudo están en el dominio si esto no debería ser una posibilidad?
Aquí hay un ejemplo más extremo, aquí el dominio decide la urgencia:
Entidad:
public class Task
{
//...
public static void Create(Category category, string description)
{
var task = new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
if(someCondition)
{
if(someValue > anotherValue)
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
No hay forma de que desee pasar en todas las versiones posibles de Urgencia, y no hay forma de que desee calcular esta lógica de negocios en la capa de aplicación, por lo que seguramente sería la forma más adecuada.
Entonces, ¿es esta una razón válida para acceder a los repositorios desde el dominio?
EDITAR: Este también podría ser el caso en los métodos no estáticos:
public class Task
{
//...
public void Update(Category category, string description)
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
if(someCondition)
{
if(someValue > anotherValue)
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
fuente
Status = _statusRepository.GetById(Constants.Status.OutstandingId)
es una regla comercial , una que podría leer como "La empresa dicta que el estado inicial de todas las tareas será excepcional" y es por eso esa línea de código no pertenece dentro de un repositorio, cuyas únicas preocupaciones son la gestión de datos a través de operaciones CRUD.No sé si su ejemplo de estado es código real o aquí solo por el bien de la demostración, pero me parece extraño que deba implementar Status como una Entidad (sin mencionar una Raíz Agregada) cuando su ID es una constante definida en código -
Constants.Status.OutstandingId
. ¿Eso no vence el propósito de los estados "dinámicos" que puede agregar tantos como desee en la base de datos?Agregaría que en su caso, la construcción de un
Task
(incluido el trabajo de obtener el estado correcto de StatusRepository si es necesario) podría merecer unTaskFactory
lugar en lugar de quedarse enTask
sí mismo, ya que es un conjunto no trivial de objetos.Pero :
Esta declaración es imprecisa y demasiado simplista en el mejor de los casos, engañosa y peligrosa en el peor.
En las arquitecturas basadas en dominios se acepta con bastante frecuencia que una entidad no debería saber cómo almacenarse a sí misma ; ese es el principio de ignorancia de persistencia. Entonces no hay llamadas a su repositorio para agregarse al repositorio. ¿Debería saber cómo (y cuándo) almacenar otras entidades ? Una vez más, esa responsabilidad parece pertenecer a otro objeto, tal vez un objeto que conoce el contexto de ejecución y el progreso general del caso de uso actual, como un servicio de capa de aplicación.
¿Podría una entidad usar un repositorio para recuperar otra entidad ? El 90% de las veces no debería tener que hacerlo, ya que las entidades que necesita generalmente están dentro del alcance de su agregado o pueden obtenerse atravesando otros objetos. Pero hay momentos en que no lo son. Si toma una estructura jerárquica, por ejemplo, las entidades a menudo necesitan acceder a todos sus antepasados, un nieto en particular, etc. como parte de su comportamiento intrínseco. No tienen una referencia directa a estos parientes remotos. Sería inconveniente pasarles a estos parientes como parámetros de la operación. Entonces, ¿por qué no usar un repositorio para obtenerlos, siempre que sean raíces agregadas?
Hay algunos otros ejemplos. La cuestión es que a veces hay un comportamiento que no puede ubicar en un servicio de Dominio, ya que parece encajar perfectamente en una entidad existente. Y, sin embargo, esta entidad necesita acceder a un Repositorio para hidratar una raíz o una colección de raíces que no se le pueden pasar.
Por lo tanto, acceder a un Repositorio desde una Entidad no es malo en sí mismo , puede tomar diferentes formas que resultan de una variedad de decisiones de diseño que van desde catastróficas hasta aceptables.
fuente
Esta es una razón por la que no uso Enums o tablas de búsqueda pura dentro de mi dominio. La urgencia y el estado son ambos estados y hay una lógica asociada con un estado que pertenece directamente al estado (por ejemplo, ¿a qué estados puedo hacer la transición dado mi estado actual?). Además, al registrar un estado como un valor puro, pierde información como cuánto tiempo estuvo la tarea en un estado determinado. Represento los estados como una jerarquía de clases así. (Cía#)
La implementación de CompletedTaskStatus sería prácticamente la misma.
Hay varias cosas a tener en cuenta aquí:
Hago que los constructores predeterminados estén protegidos. Esto es para que el marco pueda llamarlo cuando saca un objeto de la persistencia (tanto EntityFramework Code-first como NHibernate usan proxies que se derivan de los objetos de su dominio para hacer su magia).
Muchos de los establecedores de propiedades están protegidos por la misma razón. Si quiero cambiar la fecha de finalización de un intervalo, tengo que llamar a la función Interval.End () (esto es parte del diseño dirigido por dominio, que proporciona operaciones significativas en lugar de objetos de dominio anémico).
No lo muestro aquí, pero la Tarea también ocultaría los detalles de cómo almacena su estado actual. Por lo general, tengo una lista protegida de HistoricalStates que le permito al público consultar si están interesados. De lo contrario, expongo el estado actual como un captador que consulta HistoricalStates.Single (state.Duration.End == null).
La función TransitionTo es importante porque puede contener lógica sobre qué estados son válidos para la transición. Si solo tienes una enumeración, esa lógica tiene que estar en otra parte.
Con suerte, esto lo ayudará a comprender un poco mejor el enfoque DDD.
fuente
He estado tratando de resolver el mismo problema durante algún tiempo, decidí que quería poder llamar a Task.UpdateTask () así, aunque preferiría que fuera específico del dominio, en su caso tal vez lo llamaría Task.ChangeCategory (...) para indicar una acción y no solo CRUD.
de todos modos, probé tu problema y se me ocurrió esto ... toma mi pastel y cómelo también. La idea es que las acciones se realicen en la entidad pero sin la inyección de todas las dependencias. En cambio, el trabajo se realiza en métodos estáticos para que puedan acceder al estado de la entidad. La fábrica pone todo junto y normalmente tendrá todo lo que necesita para hacer el trabajo que la entidad necesita hacer. El código del cliente ahora se ve limpio y claro y su entidad no depende de ninguna inyección de repositorio.
fuente