design - Escalar un modelo de dominio rico
scalability domain-driven-design (4)
Domain Driven Design lo alienta a usar un modelo de dominio enriquecido. Esto significa que toda la lógica de dominio se ubica en el modelo de dominio y que el modelo de dominio es supremo. La persistencia se convierte en una preocupación externa, ya que el modelo de dominio idealmente no sabe nada de persistencia (por ejemplo, la base de datos).
He estado usando esto en la práctica en un proyecto de tamaño mediano para un solo hombre (> 100k líneas de Java) y estoy descubriendo muchas ventajas, principalmente la flexibilidad y la refactoribilidad que esto ofrece sobre un enfoque orientado a la base de datos. Puedo agregar y eliminar clases de dominio, presionar algunos botones y se implementa un nuevo esquema de base de datos completo y capa SQL.
Sin embargo, a menudo me enfrento a problemas en los que encuentro difícil conciliar la rica lógica de dominio con el hecho de que hay una base de datos SQL que respalda la aplicación. En general, esto da como resultado el típico "problema de consultas 1 + N", donde se obtienen N objetos y luego se ejecuta un método no trivial en cada objeto que de nuevo activa consultas. Optimizar esto a mano le permite hacer el proceso en un número constante de consultas SQL.
En mi diseño, permito que un sistema conecte estas versiones optimizadas. Hago esto moviendo el código a un "módulo de consulta" que contiene docenas de consultas específicas de dominio (por ejemplo, getActiveUsers), de las cuales tengo ambas en memoria ( ingenuo y no escalable) y las implementaciones basadas en SQL (para uso de implementación). Esto me permite optimizar los hotspots, pero hay dos desventajas principales:
- De hecho, estoy moviendo parte de la lógica de mi dominio a lugares a los que realmente no pertenece y, de hecho, incluso llevándola a las declaraciones SQL.
- El proceso requiere que examine detenidamente los registros de consulta para averiguar dónde están los puntos de acceso, después de lo cual tengo que refactorizar el código, reduciendo su abstracción de nivel al reducirlo a consultas.
¿Existe una forma mejor y más limpia de conciliar el diseño controlado por el dominio y su modelo de dominio enriquecido con el hecho de que no puede tener todas sus entidades en la memoria y, por lo tanto, están confinadas a un back-end de base de datos?
En mi experiencia, esta es la única forma de hacer las cosas. Si escribe un sistema que intenta ocultar o abstraer por completo la capa de persistencia, entonces no hay forma de que pueda optimizar las cosas usando los detalles de la capa de persistencia.
He estado enfrentando recientemente este problema y he estado trabajando en una solución donde las capas de persistencia pueden elegir implementar interfaces que representen optimizaciones. Acabo de jugar con él, pero para usar tu ejemplo de ListAUsers, dice así ...
Primero escribe un método ListAllUsers que hace todo en el nivel de dominio. Por un tiempo, esto funcionará, entonces comenzará a volverse demasiado lento.
Cuando se usa lentamente el modelo de dominio enriquecido, cree una interfaz llamada "IListActiveUsers" (o probablemente algo mejor). Y haga que su código de persistencia implemente esta interfaz usando las técnicas que sean apropiadas (probablemente SQL optimizado).
Ahora puede escribir una capa que verifique estas interfaces y llame al método específico si existe.
Esto no es perfecto y no tengo mucha experiencia con este tipo de cosas. Pero me parece que la clave es garantizar que si está utilizando un método de persistencia totalmente ingenuo, entonces todo el código debería funcionar. Cualquier optimización debe hacerse como una adición a esto.
Hay al menos dos formas de ver este problema, una es una versión técnica de "¿qué puedo hacer para cargar mis datos más inteligente?". Lo único realmente inteligente que conozco son las colecciones dinámicas que están parcialmente cargadas con el resto cargado a pedido, con la posible precarga de partes. Hubo una charla interesante en JavaZone 2008 sobre esto
El segundo enfoque ha sido más de mi enfoque en el tiempo que he estado trabajando con DDD; ¿Cómo puedo hacer mi modelo para que sea más "cargable" sin sacrificar demasiado de la bondad DDD. Mi hipótesis a lo largo de los años siempre ha sido que muchos modelos de DDD modelan conceptos de dominio que en realidad son la suma de todos los estados de dominio permitidos, en todos los procesos comerciales y en los diferentes estados que ocurren en cada proceso de negocio a lo largo del tiempo. Creo que muchos de estos problemas de carga se reducen si los modelos de dominio se normalizan un poco más en términos de los procesos / estados. Esto generalmente significa que no hay un objeto "Orden" porque un ordrer típicamente existe en múltiples estados distintos que tienen una semántica bastante diferente (ShoppingCartOrder, ShippedOrder, InvoicedOrder, HistoricalOrder). Si intentas encapsular esto es un único objeto Order, invariablemente terminas con muchos problemas de carga / construcción.
Pero no hay una bala de plata aquí ..
No en realidad no. No es que yo sepa de todos modos (aunque estoy interesado en escuchar las respuestas de los defensores de DDD en sentido contrario).
Según mi propia experiencia, y la del equipo con mucha experiencia con el que trabajo, si desea un rendimiento óptimo de una aplicación respaldada por una base de datos, la transformación de su arquitectura para estar orientada a los servicios es inevitable. Escribí más sobre esto aquí (el artículo habla de propiedades cargadas perezosas, pero podría considerar el punto para aplicarlo a cualquier método de la clase que necesite recuperar más datos para hacer su trabajo).
Tanto como lo está haciendo ahora, podría comenzar con un modelo de dominio enriquecido y transformarlo para que esté orientado al servicio cuando sea necesario por motivos de rendimiento. Mientras haya definido los objetivos de rendimiento y los esté cumpliendo, no hay necesidad de transformarlo todo. Creo que es un enfoque pragmático bastante decente.
Creo que debería considerar la capa de consulta como parte de la lógica de su dominio. Debe permitirse escribir consultas optimizadas que solo se pueden realizar con conocimiento "íntimo" de su solución de persistencia. No trates de abstraer todo. Además, el procesamiento por lotes es otra parte de su aplicación que también debe tener conocimiento de su dominio. Me parece innecesario tratar de evitar el procesamiento por lotes simplemente porque no puedo adaptarlo a mi modelo de dominio. Sin embargo, puede combinar los enfoques: use consultas para descubrir qué objetos deben cambiar, luego ponga en cola sus identificadores y maneje cada uno por su cuenta, usando su lógica de dominio.