2010-05-24 15 views
26

Estamos siguiendo Domain Driven Design para la implementación de un sitio web grande.Cómo evitar tener objetos muy grandes con Domain Driven Design

Sin embargo, al poner el comportamiento en los objetos de dominio estamos terminando con algunas clases muy grandes.

Por ejemplo, en nuestro objeto WebsiteUser, tenemos muchos métodos, por ejemplo. manejo de contraseñas, historial de pedidos, reembolsos, segmentación de clientes. Todos estos métodos están directamente relacionados con el usuario. Muchos de estos métodos se delegan internamente a otro objeto secundario, pero
esto todavía da como resultado algunas clases muy grandes.

Me complace evitar la exposición de muchos objetos secundarios p. user.getOrderHistory(). getLatestOrder().

¿Qué otras estrategias se pueden utilizar para evitar este problema?

Respuesta

2

Me encontré con el mismo problema, y ​​descubrí que el uso de objetos "gerentes" secundarios era la mejor solución en nuestro caso.

Por ejemplo, en su caso, es posible que tenga:

User u = ...; 
OrderHistoryManager histMan = user.getOrderHistoryManager(); 

continuación, puede utilizar la histMan para cualquier cosa que desee. Obviamente pensaste en esto, pero no sé por qué quieres evitarlo. Separa las preocupaciones cuando tienes objetos que parecen hacer demasiado.

Piénselo de esta manera. Si tenía un objeto "Humano", y tenía que implementar el método chew(). ¿Lo pondría en el objeto Human o en el objeto hijo Mouth?

19

Los problemas que está viendo no son causados ​​por el Diseño Dirigido por Dominio, sino más bien por la falta de separación de las preocupaciones. El Diseño Dirigido por Dominio no se trata solo de colocar datos y comportamiento en conjunto.

Lo primero que recomendaría es tomar un día y leer Domain Driven Design Quickly disponible como descarga gratuita desde Info-Q. Esto proporcionará una descripción general de los diferentes tipos de objetos de dominio: entidades, objetos de valor, servicios, repositorios y fábricas.

Lo segundo que recomendaría es leer en el Single Responsibility Principle.

Lo tercero que recomendaría es que empiece a sumergirse en Test Driven Development. Si bien aprender a diseñar al escribir pruebas primero no necesariamente hará que los diseños sean geniales, tienden a guiarlo hacia diseños poco compactos y a revelar problemas de diseño más temprano.

En el ejemplo que proporcionó, WebsiteUser definitivamente tiene demasiadas responsabilidades. De hecho, es posible que no necesite WebsiteUser, ya que los usuarios generalmente están representados por un ISecurityPrincipal.

Es un poco difícil sugerir exactamente cómo debe acercarse a su diseño dada la falta de contexto comercial, pero primero recomendaría hacer una tormenta de ideas creando algunas fichas que representen cada uno de los sustantivos principales que tiene en su sistema (por ejemplo, cliente, orden, recibo, producto, etc.). Escriba los nombres de las clases candidatas en la parte superior, qué responsabilidades considera que son inherentes a la clase a la izquierda y las clases con las que colaborará a la derecha. Si algún comportamiento no parece pertenecer a ninguno de los objetos, es probable que sea un buen candidato para el servicio (es decir, AuthenticationService).Extiende las cartas sobre la mesa con tus universidades y discute. Sin embargo, no hagas demasiado de esto, ya que esto solo pretende ser un ejercicio de diseño de tormenta de ideas. Puede ser un poco más fácil hacer esto a veces que usar una pizarra porque puedes mover cosas.

A largo plazo, realmente deberías retirar el libro Domain Driven Design de Eric Evans. Es una gran lectura, pero vale la pena su tiempo. También te recomendaría que retires Agile Software Development, Principles, Patterns, and Practices o Agile Principles, Patterns, and Practices in C# dependiendo de tu preferencia de idioma.

+0

Gracias por este comentario. He leído el libro del Sr. Evans. Supongo que el problema es cuando una entidad tiene muchos colaboradores. p.ej. un usuario del sitio web (o el principal) tiene un último pedido, un primer pedido, un carrito de compras, una contraseña, etc. Todos estos están directamente relacionados con el usuario de un sitio web. – Pablojim

+1

La autenticación, el estado de pedido pendiente (es decir, el carrito de compras) y el historial de pedidos son preocupaciones realmente diferentes. Considere extraer la autenticación en un IAuthenticationService y crear un IOrderHistoryRepository (o quizás un IOrderHistoryService) para encapsular el comportamiento de recuperación del historial de pedidos para un usuario determinado. –

2

Una regla muy simple a seguir es "la mayoría de los métodos en su clase TIENEN que usar la mayoría de las variables de instancia en su clase" - si sigue esta regla las clases serán automáticamente del tamaño correcto.

+0

Esa es una observación interesante. ¿Puedes proporcionar enlaces? – ya23

+0

Básicamente es una manera más simple de recordar a SRP: lo leí en uno de los libros, pero me pareció muy interesante y se me quedó grabado. – OpenSource

+0

esto también se conoce como alta cohesión – beluchin

2

Es posible que desee considerar invertir algunas cosas. Por ejemplo, un Cliente no necesita tener una propiedad de Pedido (o un historial de pedidos); puede dejarlos fuera de la clase Cliente. Así que en lugar de

 
public void doSomethingWithOrders(Customer customer, Calendar from, Calendar to) { 
    List = customer.getOrders(from, to); 
    for (Order order : orders) { 
     order.doSomething(); 
    } 
} 

en su lugar podría hacer:

 
public void doSomethingWithOrders(Customer customer, Calendar from, Calendar to) { 
    List = orderService.getOrders(customer, from, to); 
    for (Order order : orders) { 
     order.doSomething(); 
    } 
} 

Este es el acoplamiento más 'suelto', pero aún así se pueden obtener todos los pedidos que pertenecen a un cliente. Estoy seguro de que hay personas más inteligentes que yo que tienen los nombres correctos y los enlaces que se refieren a lo anterior.

9

Aunque los humanos de verdad tienen muchas responsabilidades, se dirigen hacia el God object anti-pattern.

Como otros han dado a entender, se debe extraer esas responsabilidades en distintos repositorios y/o servicios de dominio . Ej .:

SecurityService.Authenticate(credentials, customer) 
OrderRepository.GetOrderHistoryFor(Customer) 
RefundsService.StartRefundProcess(order) 

ser específico con las convenciones de nomenclatura (es decir, utilizar OrderRepository o OrderService, en lugar de OrderManager)

le han acabado en este problema debido a la conveniencia . es conveniente tratar un WebsiteUser como una raíz agregada , y acceder a todo a través de ella.

Si coloca más énfasis en la claridad en lugar de conveniencia, debería ayudar a separar estas preocupaciones. Desafortunadamente, significa que los miembros del equipo ahora deben conocer los nuevos Servicios.

Otra forma de pensar en él: así como Entidades no deben realizar su propia persistencia (que es por eso que usamos repositorios), sus WebsiteUser no deben manipular reembolsos/Segmentación/etc.

Espero que ayude!

1

Creo que su problema está realmente relacionado con Contextos acotados.Por lo que veo, "manejo de contraseñas, historial de pedidos, reembolsos, segmentación de clientes", cada uno de estos puede ser un contexto limitado. Por lo tanto, podría considerar dividir el usuario de su sitio web en varias entidades, cada una de las cuales corresponde a un contexto. Puede surgir cierta duplicación, pero obtienes atención en tu dominio y deshaces de clases muy grandes con múltiples responsabilidades.

+0

+1 Esto es bastante parecido a lo que DDD con DCI se vería. Separa lo que el sistema es de lo que hace el sistema. – Gordon

Cuestiones relacionadas