java - elementos - modelo de dominio sin atributos
¿Cómo puedo resolver el conflicto entre el acoplamiento suelto/inyección de dependencia y un modelo de dominio enriquecido? (6)
El enfoque más simple que puedo pensar es agregar algo de lógica en su capa de acceso a datos que inyectará un objeto de dominio con sus dependencias antes de devolverlo a una capa superior (generalmente llamada capa de servicio). Podría anotar las propiedades de cada clase para indicar qué necesita conectarse. Si no está en Java 5+, puede implementar una interfaz para cada componente que deba inyectarse, o incluso declararlo todo en XML y alimentar esos datos al contexto en el que se realizará el cableado. Si deseaba obtener un diseño sofisticado, podría extraerlo en un aspecto y aplicarlo globalmente a través de su capa de acceso a datos para que todos los métodos que extraigan objetos de dominio los conecten justo después de que se devuelvan.
Edit: Esto no es un conflicto en el nivel teórico sino un conflicto en un nivel de implementación.
Otra edición: el problema es no tener modelos de dominio como datos / solo DTO frente a un mapa de objetos más complejo y rico donde Order tiene artículos de pedido y cierta lógica calculada total. El problema específico es cuando, por ejemplo, ese pedido necesita obtener los últimos precios al por mayor del artículo de pedido de algún servicio web en China (por ejemplo). Por lo tanto, tiene un servicio Spring en ejecución que permite realizar llamadas a este servicio PriceQuery en China. Order ha calculadoTotal que se repite en cada artículo de pedido, obtiene el último precio y lo suma al total.
Entonces, ¿cómo se aseguraría de que cada pedido tenga una referencia a este servicio de PriceQuery? ¿Cómo lo restauraría tras las serializaciones, la carga desde bases de datos y nuevas instancias? Esta es mi pregunta exacta.
La forma más fácil sería pasar una referencia al método calculatotal, pero ¿qué sucede si su Objeto usa este servicio internamente durante su vida útil? ¿Qué pasa si se utiliza en 10 métodos? Se vuelve complicado pasar referencias alrededor cada vez.
Otra forma sería mover a CalcTall fuera de la Orden y al Servicio de Órdenes, pero eso rompe el diseño de OO y nos movemos hacia el viejo "Guión de Transacción".
Publicación original:
Versión corta: los objetos de dominio enriquecidos requieren referencias a muchos componentes, pero estos objetos se conservan o se serializan, por lo que cualquier referencia que tengan a componentes externos (en este caso, los beans Spring: servicios, repositorios, cualquier cosa) son transitorios y se eliminan. Deben ser reinyectados cuando el objeto se des-serializa o se carga desde la base de datos, pero esto es extremadamente feo y no puedo ver una forma elegante de hacerlo.
Versión más larga: desde hace un tiempo he practicado el acoplamiento suelto y DI con la ayuda de Spring. Me ha ayudado mucho a mantener las cosas manejables y comprobables. Hace un tiempo, sin embargo, leí Diseño impulsado por dominios y algo de Martin Fowler. Como resultado, he estado intentando convertir mis modelos de dominio de DTO simples (generalmente representaciones simples de una fila de la tabla, solo datos sin lógica) en un modelo de dominio más rico.
A medida que mi dominio crece y asume nuevas responsabilidades, los objetos de mi dominio comienzan a requerir algunos de los beans (servicios, repositorios, componentes) que tengo en mi contexto de Spring. Esto se ha convertido rápidamente en una pesadilla y en una de las partes más difíciles de convertir a un diseño de dominio rico.
Básicamente, hay puntos en los que estoy inyectando manualmente una referencia al contexto de la aplicación en mi dominio:
- cuando el objeto se carga desde el Repositorio u otra Entidad responsable, ya que las referencias de los componentes son transitorias y, obviamente, no se conservan
- cuando se crea un objeto desde Factory, ya que un objeto recién creado carece de las referencias de los componentes
- cuando el objeto se des-serializa en un trabajo de cuarzo o en algún otro lugar ya que las referencias de los componentes transitorios se borran
Primero, es feo porque le paso al objeto una referencia de contexto de la aplicación y espero que se extraiga por referencias de nombre a los componentes que necesita. Esto no es una inyección, es un tirón directo.
Segundo, es un código feo porque en todos los lugares mencionados necesito lógica para inyectar un appContext
Tercero, es propenso a errores porque tengo que recordar inyectar en todos esos lugares para todos esos objetos, lo que es más difícil de lo que parece.
Tiene que haber una mejor manera y espero que puedas arrojar algo de luz sobre eso.
El patrón del Mapa de Identidad puede ayudar con su escenario. Revise el artículo Patrones en la práctica escrito por Jeremy Miller donde discute acerca de este patrón.
He encontrado la respuesta, al menos para aquellos que usan Spring:
6.8.1. Uso de AspectJ para dependencias inyectar objetos de dominio con Spring
Me atrevería a decir que hay muchos matices de gris entre tener un "modelo de dominio anémico" y agrupar todos sus servicios en los objetos de su dominio. Y muy a menudo, al menos en los dominios de negocios y en mi experiencia, un objeto en realidad podría ser nada más que solo datos; por ejemplo, cuando las operaciones que se pueden realizar en ese objeto en particular dependen de muchos otros objetos y de algún contexto localizado, digamos una dirección, por ejemplo.
En mi revisión de la literatura basada en dominios en la red, he encontrado muchas ideas y escritos vagos, pero no pude encontrar un ejemplo adecuado, no trivial, de dónde deberían estar los límites entre los métodos y las operaciones, y Además, cómo implementar eso con la pila de tecnología actual. Entonces, para el propósito de esta respuesta, crearé un pequeño ejemplo para ilustrar mis puntos:
Considere el antiguo ejemplo de pedidos y artículos de pedido. Un modelo de dominio "anémico" sería algo así como:
class Order {
Long orderId;
Date orderDate;
Long receivedById; // user which received the order
}
class OrderItem {
Long orderId; // order to which this item belongs
Long productId; // product id
BigDecimal amount;
BigDecimal price;
}
En mi opinión, el objetivo del diseño dominado por el dominio es utilizar clases para modelar mejor las relaciones entre entidades. Entonces, un modelo no anémico se vería algo así como:
class Order {
Long orderId;
Date orderDate;
User receivedBy;
Set<OrderItem> items;
}
class OrderItem {
Order order;
Product product;
BigDecimal amount;
BigDecimal price;
}
Supuestamente, estaría usando una solución ORM para hacer el mapeo aquí. En este modelo, podría escribir un método como Order.calculateTotal()
, que resumiría toda la amount*price
para cada artículo del pedido.
Por lo tanto, el modelo sería rico, en el sentido de que las operaciones que tienen sentido desde una perspectiva empresarial, como el calculateTotal
, se ubicarían en un objeto de dominio de Order
. Pero, al menos en mi opinión, el diseño controlado por dominio no significa que la Order
deba saber sobre sus servicios de persistencia. Eso debe hacerse en una capa separada e independiente. Las operaciones de persistencia no son parte del dominio de negocios, son parte de la implementación.
E incluso en este simple ejemplo, hay muchos escollos que considerar. ¿Debe cargarse todo el Product
con cada artículo de OrderItem
? Si hay una gran cantidad de artículos de pedido, y necesita un informe de resumen para una gran cantidad de pedidos, ¿estaría utilizando Java, cargando objetos en la memoria e invocando a calculateTotal()
en cada pedido? O es una consulta SQL una solución mucho mejor, desde todos los aspectos. Es por eso que una solución ORM decente como Hibernate, ofrece mecanismos para resolver precisamente este tipo de problemas prácticos: carga perezosa con proxies para el primero y HQL para el segundo. ¿De qué sirve ser un modelo teóricamente correcto si la generación de informes lleva mucho tiempo?
Por supuesto, todo el tema es bastante complejo, mucho más de lo que puedo escribir o considerar en una sola sesión. Y no estoy hablando desde una posición de autoridad, sino una práctica simple y cotidiana en la implementación de aplicaciones empresariales. Con suerte, obtendrá algo de esta respuesta. Siéntase libre de proporcionar algunos detalles adicionales y ejemplos de lo que está tratando con ...
Edición : con respecto al servicio PriceQuery
, y el ejemplo de enviar un correo electrónico después de que se haya calculado el total, haría una distinción entre:
- El hecho de que un correo electrónico debe ser enviado después del cálculo del precio.
- ¿Qué parte de un pedido debe ser enviado? (Esto también podría incluir, por ejemplo, plantillas de correo electrónico)
- el método real de enviar un correo electrónico
Además, uno tiene que preguntarse, es el envío de un correo electrónico, la capacidad inherente de un Order
, u otra cosa que se puede hacer con él, como persistirlo, la serialización a diferentes formatos (XML, CSV, Excel), etc.
Lo que haría y lo que considero un buen enfoque de POO es lo siguiente. Defina una interfaz que encapsule las operaciones de preparación y envío de un correo electrónico:
interface EmailSender {
public void setSubject(String subject);
public void addRecipient(String address, RecipientType type);
public void setMessageBody(String body);
public void send();
}
Ahora, dentro de la clase Order
, defina una operación mediante la cual una orden "sabe" cómo enviarse como un correo electrónico, utilizando un remitente de correo electrónico:
class Order {
...
public void sendTotalEmail(EmailSender sender) {
sender.setSubject("Order " + this.orderId);
sender.addRecipient(receivedBy.getEmailAddress(), RecipientType.TO);
sender.addRecipient(receivedBy.getSupervisor().getEmailAddress(), RecipientType.BCC);
sender.setMessageBody("Order total is: " + calculateTotal());
sender.send();
}
Finalmente, debe tener una fachada hacia las operaciones de su aplicación, un punto donde ocurre la respuesta real a la acción del usuario. En mi opinión, aquí es donde debe obtener (por Spring DI) las implementaciones reales de los servicios. Esto puede ser, por ejemplo, la clase Spring MVC Controller
:
public class OrderEmailController extends BaseFormController {
// injected by Spring
private OrderManager orderManager; // persistence
private EmailSender emailSender; // actual sending of email
public ModelAndView processFormSubmission(HttpServletRequest request,
HttpServletResponse response, ...) {
String id = request.getParameter("id");
Order order = orderManager.getOrder(id);
order.sendTotalEmail(emailSender);
return new ModelAndView(...);
}
Esto es lo que obtienes con este enfoque:
- Los objetos de dominio no contienen servicios, los usan.
- los objetos de dominio están desacoplados de la implementación del servicio real (por ejemplo, SMTP, envío en un hilo separado, etc.), por la naturaleza del mecanismo de interfaz
- Las interfaces de servicios son genéricas, reutilizables, pero no conocen ningún objeto de dominio real. Por ejemplo, si la orden obtiene un campo adicional, solo necesita cambiar la clase
Order
. - Puedes simular servicios fácilmente y probar objetos de dominio fácilmente.
- Usted puede probar implementaciones de servicios reales fácilmente
No sé si esto es según los estándares de ciertos gurús, pero es un enfoque práctico que funciona razonablemente bien en la práctica.
Regardinig
¿Qué sucede si su orden necesita enviar un correo electrónico cada vez que se calcula el total?
Yo emplearía eventos.
Si tiene algún significado para usted cuando una orden calcula su total, deje que genere un evento como eventDispatcher.raiseEvent (nuevo ComputedTotalEvent (this)).
Luego escuchas este tipo de eventos y devuelves la llamada a tu pedido como se dijo antes para dejar que formatee una plantilla de correo electrónico y la envías.
Los objetos de su dominio siguen siendo magros, sin conocer este requisito.
En resumen, divide tu problema en 2 requisitos:
- Quiero saber cuando una orden calcula su total;
- Quiero enviar un correo electrónico cuando un pedido tiene un total (nuevo y diferente);
Tal vez lo que desea es un tipo de objeto de referencia, que se serializaría como una referencia global (un URI, por ejemplo) y que podría resucitar como un proxy cuando se des-serializa en otro lugar.