asp.net-mvc - tutorial - que es mvc en programacion
¿Dónde generar eventos de dominio dependientes de la persistencia: servicio, repositorio o interfaz de usuario? (6)
Creo que deberías tener un Servicio de Dominio, como dijo David Glenn.
El problema que encontré fue que la capa de servicio terminaría teniendo que extraer una copia de algunos objetos antes de guardar la versión modificada para comparar la nueva con la anterior y luego decidir qué eventos deberían activarse.
Su servicio de dominio debe contener métodos que establezcan claramente lo que quiere hacer con su entidad de dominio, como: RegisterNewOrder, CreateNoteForOrder, ChangeOrderStatus, etc.
public class OrderDomainService()
{
public void ChangeOrderStatus(Order order, OrderStatus status)
{
try
{
order.ChangeStatus(status);
using(IUnitOfWork unitOfWork = unitOfWorkFactory.Get())
{
IOrderRepository repository = unitOfWork.GetRepository<IOrderRepository>();
repository.Save(order);
unitOfWork.Commit();
}
DomainEvents.Publish<OrderStatusChnaged>(new OrderStatusChangedEvent(order, status));
}
}
}
Mi aplicación ASP.NET MVC3 / NHibernate tiene un requisito para activar y manejar una variedad de eventos relacionados con los objetos de mi dominio. Por ejemplo, un objeto Order
podría tener eventos como OrderStatusChanged
o NoteCreatedForOrder
. En la mayoría de los casos, estos eventos hacen que se envíe un correo electrónico, por lo que no puedo dejarlos en la aplicación MVC.
He leído los Eventos de Dominio de Udi Dahan y muchas otras ideas sobre cómo hacer este tipo de cosas, y decidí usar un host basado en NServiceBus que maneje los mensajes de eventos. He hecho algunas pruebas de prueba de concepto y esto parece funcionar bien.
Mi pregunta es qué capa de aplicación debería elevar los eventos . No deseo activar los eventos hasta que el objeto en cuestión se haya conservado correctamente (no se puede enviar un correo electrónico que indique que se creó una nota si falló la persistencia).
Otra preocupación es que, en algunos casos, un evento está vinculado a un objeto que está debajo de una raíz agregada. En el ejemplo anterior, una Note
se guarda agregándola a la colección Order.Notes
y guardando la orden. Esto plantea un problema, ya que dificulta evaluar qué eventos deben activarse cuando se guarda un Order
. Me gustaría evitar tener que extraer una copia actual del objeto y buscar diferencias antes de guardar la copia actualizada.
¿Es apropiado que la UI plantee estos eventos? Sabe qué eventos se han producido y puede dispararlos solo después de que la capa de servicio haya guardado el objeto con éxito. Algo simplemente parece incorrecto sobre tener un controlador disparando eventos de dominio.
¿Debería el repositorio disparar eventos después de persistir con éxito?
¿Debo separar los eventos por completo, hacer que el repositorio almacene un objeto
Event
que luego es recogido por un servicio de sondeo y luego convertido en un evento para NServiceBus (o manejado directamente desde el servicio de sondeo)?¿Hay una mejor manera de hacer esto? ¿Tal vez tener mis objetos de dominio en cola eventos que se activan por la capa de servicio solo después de que el objeto se persiste?
Actualización: tengo una capa de servicio, pero parece incómodo y excesivo hacer que pase por un proceso de comparación para determinar qué eventos deben activarse cuando se guarda una raíz agregada determinada. Debido a que algunos de estos eventos son granulares (por ejemplo, "estado de orden cambiado"), creo que tendré que recuperar una copia de DB del objeto, comparar las propiedades para crear eventos, guardar el nuevo objeto y luego enviar los eventos a NServiceBus cuando la operación de salvar se completó con éxito.
Actualizar
Lo que terminé haciendo, luego de la respuesta que publiqué a continuación (a continuación ), fue construir en las entidades de mi dominio una propiedad EventQueue
que era un List<IDomainEvent>
. Luego agregué eventos a medida que los cambios en el dominio lo merecían, lo que me permitió mantener la lógica dentro del dominio, lo que creo que es apropiado, ya que estoy activando eventos en función de lo que está sucediendo dentro de una entidad.
Luego, cuando persisto el objeto en mi capa de servicio, proceso esa cola y en realidad envío los eventos al bus de servicio. Inicialmente, estaba planeando usar una base de datos heredada que usaba PK de identidad, por lo que tuve que post-procesar estos eventos para completar el ID de la entidad, pero finalmente decidí cambiar a un Guid.Comb
PK que me permite omitir ese paso.
La solución resultó estar basada en la implementación de estos métodos de extensión en el objeto de sesión NHibernate.
Probablemente estaba un poco confuso con la redacción de la pregunta. El motivo principal de la cuestión arquitectónica fue el hecho de que los objetos NHibernate están siempre en el mismo estado, a menos que los elimine de forma manual y los realice todo tipo de maquinaciones. Eso era algo que no quería hacer para determinar qué propiedades se habían cambiado y, por lo tanto, qué eventos disparar.
La activación de estos eventos en los establecedores de propiedades no funcionaría porque los eventos solo deberían activarse después de que los cambios hayan persistido, para evitar la activación de eventos en una operación que podría fallar.
Entonces, lo que hice fue agregar algunos métodos a mi base de repositorio:
public bool IsDirtyEntity(T entity)
{
// Use the extension method...
return SessionFactory.GetCurrentSession().IsDirtyEntity(entity);
}
public bool IsDirtyEntityProperty(T entity, string propertyName)
{
// Use the extension method...
return SessionFactory.GetCurrentSession().IsDirtyProperty(entity, propertyName);
}
Luego, en el método Save
mi servicio, puedo hacer algo como esto (recuerda que estoy usando NServiceBus aquí, pero si estuvieras usando la clase estática de eventos de dominio de Udi Dahan, funcionaría de manera similar):
var pendingEvents = new List<IMessage>();
if (_repository.IsDirtyEntityProperty(order, "Status"))
pendingEvents.Add(new OrderStatusChanged()); // In reality I''d set the properties of this event message object
_repository.Save(order);
_unitOfWork.Commit();
// If we get here then the save operation succeeded
foreach (var message in pendingEvents)
Bus.Send(message);
Debido a que en algunos casos el Id
de la entidad podría no establecerse hasta que se guarde (estoy usando columnas de enteros de Identity
), podría tener que ejecutar después de confirmar la transacción para recuperar el Id. A fin de completar las propiedades en mis objetos de evento. Dado que se trata de datos existentes, no puedo cambiar fácilmente a un tipo de hilo de identificación asignado por el cliente.
Los Eventos de Dominio deben plantearse en ... el Dominio. Por eso son eventos de dominio.
public void ExecuteCommand(MakeCustomerGoldCommand command)
{
if (ValidateCommand(command) == ValidationResult.OK)
{
Customer item = CustomerRepository.GetById(command.CustomerId);
item.Status = CustomerStatus.Gold;
CustomerRepository.Update(item);
}
}
(y luego en la clase Cliente, lo siguiente):
public CustomerStatus Status
{
....
set
{
if (_status != value)
{
_status = value;
switch(_status)
{
case CustomerStatus.Gold:
DomainEvents.Raise(CustomerIsMadeGold(this));
break;
...
}
}
}
El método Raise almacenará el evento en el Almacén de eventos. También puede ejecutar controladores de eventos registrados localmente.
Mi solución es que críe eventos tanto en la capa de dominio como en la capa de servicio.
Tu dominio:
public class Order
{
public void ChangeStatus(OrderStatus status)
{
// change status
this.Status = status;
DomainEvent.Raise(new OrderStatusChanged { OrderId = Id, Status = status });
}
public void AddNote(string note)
{
// add note
this.Notes.Add(note)
DomainEvent.Raise(new NoteCreatedForOrder { OrderId = Id, Note = note });
}
}
Tu servicio:
public class OrderService
{
public void SubmitOrder(int orderId, OrderStatus status, string note)
{
OrderStatusChanged orderStatusChanged = null;
NoteCreatedForOrder noteCreatedForOrder = null;
DomainEvent.Register<OrderStatusChanged>(x => orderStatusChanged = x);
DomainEvent.Register<NoteCreatedForOrder>(x => noteCreatedForOrder = x);
using (var uow = UnitOfWork.Start())
{
var order = orderRepository.Load(orderId);
order.ChangeStatus(status);
order.AddNote(note);
uow.Commit(); // commit to persist order
}
if (orderStatusChanged != null)
{
// something like this
serviceBus.Publish(orderStatusChanged);
}
if (noteCreatedForOrder!= null)
{
// something like this
serviceBus.Publish(noteCreatedForOrder);
}
}
}
Parece que necesitas una capa de servicio . Una capa de servicio es otra abstracción que se encuentra entre su front-end o controladores y su capa de negocios o modelo de dominio. Es como una API en tu aplicación. Sus controladores solo tendrán acceso a su capa de servicio.
Entonces se convierte en la responsabilidad de su capa de servicio interactuar con su modelo de dominio.
public Order GetOrderById(int id) {
//...
var order = orderRepository.get(id);
//...
return order;
}
public CreateOrder(Order order) {
//...
orderRepositroy.Add(order);
if (orderRepository.Submitchanges()) {
var email = emailManager.CreateNewOrderEmail(order);
email.Send();
}
//...
}
Es común terminar con objetos ''gestores'' como OrderManager
para interactuar con los pedidos y la capa de servicio para lidiar con POCO.
¿Es apropiado que la UI plantee estos eventos? Sabe qué eventos se han producido y puede dispararlos solo después de que la capa de servicio haya guardado el objeto con éxito. Algo simplemente parece incorrecto sobre tener un controlador disparando eventos de dominio.
No. Usted terminará con problemas si se agregan nuevas acciones y un desarrollador desconoce o olvida que un correo electrónico debe ser activado.
¿Debería el repositorio disparar eventos después de persistir con éxito?
No. La responsabilidad del repositorio es proporcionar una abstracción sobre el acceso a los datos y nada más
¿Hay una mejor manera de hacer esto? ¿Tal vez tener mis objetos de dominio en cola eventos que se activan por la capa de servicio solo después de que el objeto se persiste?
Sí, parece que esto debería ser manejado por su capa de servicio.
Tener eventos de dominio funciona bien si tiene comandos , enviados a su capa de servicio . Luego puede actualizar las entidades de acuerdo con un comando y generar los eventos de dominio correspondientes en una sola transacción.
Si está moviendo entidades entre la IU y la capa de servicio , se vuelve muy difícil (y algunas veces incluso imposible) determinar qué eventos de dominio se han producido, ya que no se hacen explícitos, sino que se ocultan bajo el estado de las entidades.