2012-05-13 20 views
5

He leído Evans, Nilsson y McCarthy, entre otros, y entiendo los conceptos y el razonamiento detrás de un diseño impulsado por el dominio; sin embargo, me resulta difícil reunir todos estos elementos en una aplicación real. La falta de ejemplos completos me ha dejado rascándome la cabeza. He encontrado una gran cantidad de marcos y ejemplos simples, pero nada hasta el momento que realmente demuestre cómo crear una aplicación comercial real después de un DDD.Conectando los puntos con DDD

Utilizando el típico sistema de gestión de pedidos como ejemplo, tome el caso de cancelación de la orden. En mi diseño, puedo ver un OrderCancellationService con un método CancelOrder que acepta el orden # y un motivo como parámetros. A continuación, tiene que realizar los 'pasos' siguientes:

  1. verificar que el usuario actual tiene los permisos necesarios para cancelar un pedido
  2. recuperar la entidad Orden con el orden especificado # Del OrderRepository
  3. Verificar que la orden puede ser cancelada (¿el servicio debe interrogar el estado de la orden para evaluar las reglas o la orden debe tener una propiedad CanCancel que encapsula las reglas?)
  4. Actualice el estado de la entidad de orden llamando a Order.Cancel (razón)
  5. Persista la actualización d Solicitar al almacén de datos
  6. en contacto con el CreditCardService para revertir cualquier cargo de tarjeta de crédito que ya han sido procesados ​​
  7. Añadir una entrada de auditoría para la operación

Por supuesto, todo esto debe suceder en una transacción y ninguna de las operaciones debería permitirse que ocurra de manera independiente. Lo que quiero decir es que debo revertir la transacción de la tarjeta de crédito si cancelo el pedido, no puedo cancelar y no realizar este paso. Esto, imo, sugiere una mejor encapsulación pero no quiero tener una dependencia del CreditCardService en mi objeto de dominio (Order), por lo que parece que esto es responsabilidad del servicio de dominio.

Estoy buscando a alguien que me muestre ejemplos de código de cómo esto podría/debería ser "ensamblado". El proceso de pensamiento detrás del código sería útil para lograr que conecte todos los puntos por mí mismo. ¡Gracias!

Respuesta

2

Su servicio de dominio puede verse así. Tenga en cuenta que queremos mantener la mayor lógica posible en las entidades, manteniendo el servicio del dominio delgado. También tenga en cuenta que no existe una dependencia directa en la implementación de tarjeta de crédito o auditor (DIP). Solo dependemos de las interfaces que están definidas en nuestro código de dominio. La implementación puede luego ser inyectada en la capa de aplicación. La capa de aplicación también sería responsable de encontrar la orden por número y, lo que es más importante, para envolver la llamada 'Cancelar' en una transacción (retrocediendo en las excepciones).

class OrderCancellationService { 

    private readonly ICreditCardGateway _creditCardGateway; 
    private readonly IAuditor _auditor; 

    public OrderCancellationService(
     ICreditCardGateway creditCardGateway, 
     IAuditor auditor) { 
     if (creditCardGateway == null) { 
      throw new ArgumentNullException("creditCardGateway"); 
     } 
     if (auditor == null) { 
      throw new ArgumentNullException("auditor"); 
     } 
     _creditCardGateway = creditCardGateway; 
     _auditor = auditor; 
    } 

    public void Cancel(Order order) { 
     if (order == null) { 
      throw new ArgumentNullException("order"); 
     } 
     // get current user through Ambient Context: 
     // http://blogs.msdn.com/b/ploeh/archive/2007/07/23/ambientcontext.aspx 
     if (!CurrentUser.CanCancelOrders()) { 
      throw new InvalidOperationException(
       "Not enough permissions to cancel order. Use 'CanCancelOrders' to check."); 
     } 
     // try to keep as much domain logic in entities as possible 
     if(!order.CanBeCancelled()) { 
      throw new ArgumentException(
       "Order can not be cancelled. Use 'CanBeCancelled' to check."); 
     } 
     order.Cancel(); 

     // this can throw GatewayException that would be caught by the 
     // 'Cancel' caller and rollback the transaction 
     _creditCardGateway.RevertChargesFor(order); 

     _auditor.AuditCancellationFor(order); 
    } 
} 
+0

¿Por qué no querría la 'búsqueda', la gestión de transacciones y la llamada para persistir en los cambios dentro del servicio? Parece que garantizaría un uso adecuado en todo momento. – SonOfPirate

+0

Búsqueda - tal vez. La gestión de transacciones no pertenece al servicio de dominio, por lo general se implementa en la capa de aplicación (llamante del servicio de dominio). Los cambios de llamada para persistir son manejados por ORM o UnitOfWork ya que estamos cambiando los objetos existentes, no se necesita una llamada explícita en el caso NHibernate. La idea es mantener el código de dominio tan persistente como sea posible. – Dmitry

+0

Sí, usaría un OrderRepository y UoW para mantener el dominio lo más parecido posible a la persistencia, pero nada impide que el código de la aplicación llame a su servicio de cancelación sin persistir los cambios en la entidad Order. Siendo agnóstico, no pensé que importara hasta ahora que no estamos usando NHibernate, por lo que cualquier suposición basada en ese ORM no es válida. – SonOfPirate

2

Una visión ligeramente diferente en él:

//UI 
public class OrderController 
{ 
    private readonly IApplicationService _applicationService; 

    [HttpPost] 
    public ActionResult CancelOrder(CancelOrderViewModel viewModel) 
    { 
     _applicationService.CancelOrder(new CancelOrderCommand 
     { 
      OrderId = viewModel.OrderId, 
      UserChangedTheirMind = viewModel.UserChangedTheirMind, 
      UserFoundItemCheaperElsewhere = viewModel.UserFoundItemCheaperElsewhere 
     }); 

     return RedirectToAction("CancelledSucessfully"); 
    } 
} 

//App Service 
public class ApplicationService : IApplicationService 
{ 
    private readonly IOrderRepository _orderRepository; 
    private readonly IPaymentGateway _paymentGateway; 

    //provided by DI 
    public ApplicationService(IOrderRepository orderRepository, IPaymentGateway paymentGateway) 
    { 
     _orderRepository = orderRepository; 
     _paymentGateway = paymentGateway; 
    } 

    [RequiredPermission(PermissionNames.CancelOrder)] 
    public void CancelOrder(CancelOrderCommand command) 
    { 
     using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create()) 
     { 
      Order order = _orderRepository.GetById(command.OrderId); 

      if (!order.CanBeCancelled()) 
       throw new InvalidOperationException("The order cannot be cancelled"); 

      if (command.UserChangedTheirMind) 
       order.Cancel(CancellationReason.UserChangeTheirMind); 
      if (command.UserFoundItemCheaperElsewhere) 
       order.Cancel(CancellationReason.UserFoundItemCheaperElsewhere); 

      _orderRepository.Save(order); 

      _paymentGateway.RevertCharges(order.PaymentAuthorisationCode, order.Amount); 
     } 
    } 
} 

Notas:

  • En general, sólo veo la necesidad de un servicio dominio cuando un caso comando/usar implica el cambio de estado de más de un agregado. Por ejemplo, si tuviera que invocar métodos en el agregado del cliente y en el pedido, crearía el servicio de dominio OrderCancellationService que invocó los métodos en ambos agregados.
  • La capa de aplicación se organiza entre la infraestructura (pasarelas de pago) y el dominio. Al igual que los objetos de dominio, los servicios de dominio solo deben preocuparse por la lógica del dominio e ignorar la infraestructura, como las pasarelas de pago; incluso si lo has abstraído usando tu propio adaptador.
  • Con respecto a los permisos, usaría aspect oriented programming para extraer esto de la lógica misma. Como puede ver en mi ejemplo, agregué un atributo al método CancelOrder. Puede usar un interceptor en ese método para ver si el usuario actual (que establecería en Thread.CurrentPrincipal) tiene ese permiso.
  • Con respecto a la auditoría, simplemente dijo 'auditoría para la operación'. Si solo quiere decir auditoría en general, (es decir, para todas las llamadas al servicio de aplicaciones), nuevamente utilizaría interceptores en el método, registrando al usuario, a qué método se llamó y con qué parámetros. Sin embargo, si se refería específicamente a la auditoría para la cancelación de pedidos/pagos, haga algo similar al ejemplo de Dmitry.
+0

+1 distinción clara entre la aplicación y el dominio –

+1

¿qué ocurre con la autorización del usuario de ese recurso en particular (orderId)? –

+0

para obtener permisos para los recursos de datos en lugar de funciones/transacciones Me gustaría tratar como una regla de negocios y hacer los controles en el código al igual que cualquier otra regla –