Pregunta

Estoy buscando algunos consejos sobre cuánto debería preocuparme por evitar el modelo de dominio anémico. Recién estamos comenzando en DDD y estamos luchando con la parálisis de análisis con respecto a las decisiones de diseño simples. El último punto en el que nos estamos quedando es donde pertenece cierta lógica de negocios, por ejemplo, tenemos un Order objeto, que tiene propiedades como Status etc. Ahora diga que tengo que realizar un comando como UndoLastStatus Porque alguien cometió un error con un pedido, esto no es tan simple como simplemente cambiar el Status Como otra información debe registrarse y las propiedades cambiadas. Ahora en el mundo real esta es una tarea de administración pura. Entonces, como lo veo, tengo dos opciones en las que puedo pensar:

  • Opción 1: Agregue el método para ordenar para que algo como Order.UndoLastStatus(), Si bien esto tiene sentido, en realidad no refleja el dominio. También Order es el objeto principal en el sistema y si todo lo que involucra el pedido se coloca en la clase de pedido, las cosas podrían salirse de control.

  • Opción 2: crear un Shop objeto, y con eso tiene diferentes servicios que representan roles diferentes. Entonces podría tener Shop.AdminService, Shop.DispatchService, y Shop.InventoryService. Entonces en este caso lo habría hecho Shop.AdminService.UndoLastStatus(Order).

Ahora, la segunda opción tenemos algo que refleja el dominio mucho más, y permitiría a los desarrolladores hablar con expertos de negocios sobre roles similares que realmente existe. Pero también se dirige hacia un modelo anémico. ¿Cuál sería la mejor manera de ir en general?

¿Fue útil?

Solución

La opción 2 conduciría al código de procedimiento seguro.
Puede ser más fácil de desarrollar, pero mucho más difícil de mantener.

Ahora en el mundo real esta es una tarea de administración pura

Las tareas de "administración" deben ser privadas e invocadas a través de acciones públicas, completamente "de dominio". Preferiblemente, todavía escrito en un código fácil de entender que se conduce desde el dominio.

Tal como lo veo, el problema es que UndoLastStatus tiene poco sentido para el experto en dominio.
Lo más probable es que estén hablando de hacer, cancelar y completar pedidos.

Algo en este sentido podría encajar mejor:

class Order{
  void CancelOrder(){
    Status=Status.Canceled;
  }
  void FillOrder(){
    if(Status==Status.Canceled)
      throw Exception();
    Status=Status.Filled;
  }
  static void Make(){
    return new Order();
  }
  void Order(){
    Status=Status.Pending;
  }
}

Personalmente, no me gusta el uso de "estados", se comparten automáticamente con todo lo que los usa, lo veo como como acoplamiento innecesario.

Entonces tendría algo como esto:

class Order{
  void CancelOrder(){
    IsCanceled=true;
  }
  void FillOrder(){
    if(IsCanceled) throw Exception();
    IsFilled=true;
  }
  static Order Make(){
    return new Order();
  }
  void Order(){
    IsPending=true;
  }
}

Para cambiar las cosas relacionadas cuando el estado del pedido cambia, la mejor opción es usar así eventos de dominio.
Mi código miraría en este sentido:

class Order{
  void CancelOrder(){
    IsCanceled=true;
    Raise(new Canceled(this));
  }
  //usage of nested classes for events is my homemade convention
  class Canceled:Event<Order>{
    void Canceled(Order order):base(order){}
  }     
}

class Customer{
  private void BeHappy(){
    Console.WriteLine("hooraay!");
  }
  //nb: nested class can see privates of Customer
  class OnOrderCanceled:IEventHandler<Order.Canceled>{
   void Handle(Order.Canceled e){
    //caveat: this approach needs order->customer association
    var order=e.Source;
    order.Customer.BeHappy();
   }
  }
}

Si el pedido se vuelve demasiado enorme, es posible que desee ver qué contextos limitados son (como dice Eric Evans, si tuviera la oportunidad de volver a escribir su libro, movería contextos limitados al principio).

En resumen, es una forma de descomposición impulsada por el dominio.

La idea es relativamente simple: está bien tener múltiples pedidos desde diferentes puntos de vista, también conocidos como contextos.

Por ejemplo, ordene desde el contexto de compras, ordene desde el contexto contable.

namespace Shopping{
 class Order{
  //association with shopping cart
  //might be vital for shopping but completely irrelevant for accounting
  ShoppingCart Cart;
 }
}
namespace Accounting{
 class Order{
  //something specific only to accounting
 }
}

Pero, por lo general, suficiente dominio en sí evita la complejidad y se puede descomponible fácilmente si lo escucha lo suficientemente de cerca. Por ejemplo, es posible que escuche de términos de expertos como OrderLifecycle, Orderhistory, OrderDescription que puede aprovechar como anclajes para la descomposición.

NB: Tenga en cuenta: obtuve cero comprensión sobre su dominio.
Es muy probable que esos verbos que estoy usando sean completamente extraños.

Otros consejos

Me guiaría por el SUJETAR principios. Aplica el Experto en información Principio de diseño, es decir, debe asignar la responsabilidad a la clase que naturalmente tiene la mayor información requerida para cumplir con el cambio.

En este caso, dado que cambiar el estado del pedido involucra a otras entidades, haría que cada uno de estos objetos de dominio de bajo nivel respalde un método para aplicar el cambio con respecto a sí mismo. Luego, también use una capa de servicio de dominio como usted describe en la Opción 2, que abstrae toda la operación, que abarca múltiples objetos de dominio según sea necesario.

Ver también el Fachada patrón.

Creo que tener un método como Undolaststatus en la clase de pedido se siente un poco equivocado porque las razones de su existencia están en cierto sentido fuera del alcance de un pedido. Por otro lado, tener un método responsable de cambiar el estado de un orden, orden.changestatus, se ajusta muy bien como modelo de dominio. El estado de un pedido es un concepto de dominio adecuado y que cambia ese estado debe hacerse a través de la clase de pedido, ya que posee los datos asociados con un estado de pedido: es responsabilidad de la clase de pedido mantenerse consistente y en un estado adecuado .

Otra forma de pensarlo es que el objeto de orden es lo que persiste en la base de datos y es la 'última parada' para todos los cambios aplicados a un pedido. Es más fácil razonar sobre cuál podría ser un estado válido para un pedido desde la perspectiva de un pedido en lugar de desde la perspectiva de un componente externo. De esto se trata DDD y OOP, lo que facilita que los humanos razonen sobre el código. Además, es posible que el acceso a miembros privados o protegidos ejecute un cambio de estado, en cuyo caso tener el método en la clase de pedido es una mejor opción. Esta es una de las razones por las cuales los modelos de dominio anémico están mal vistos: cambian la responsabilidad de mantener el estado consistente lejos de la clase de propiedad, rompiendo así la encapsulación entre otras cosas.

Una forma de implementar una operación más específica, como UndolastStatus, sería crear un servicio de órdenes que exponga el dominio y es cómo funcionan los componentes externos sobre el dominio. Entonces puede crear un objeto de comando simple como este:

class UndoLastStatusCommand {
  public Guid OrderId { get; set; }
}

Y el servicio de orden tendría un método para procesar ese comando:

public void Process(UndoLastStatusCommand command) {
  using (var unitOfWork = UowManager.Start()) {
    var order = this.orderRepository.Get(command.OrderId);
    if (order == null)
      throw some exception

    // operate on domain to undo last status

    unitOfWork.Commit();
  }
}

Por lo tanto, ahora el modelo de dominio para el orden expone todos los datos y comportamientos que corresponden a un orden, pero el servicio OrderService, y la capa de servicio en general, declaran el diferente tipo de operaciones que se realizan en un orden y exponen el dominio para la utilización de Componentes externos, como la capa de presentación.

Considere también investigar el concepto de eventos de dominio lo que considera modelos de dominio anémico y formas de mejorarlos.

Parece que no está conduciendo este dominio desde las pruebas. Echa un vistazo al trabajo de Rob Vens, especialmente su trabajo en modelado exploratorio, inversión de tiempo y pasivo activo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top