Modelo de dominio matriarcal

En los últimos días he estado cruzando algunos mensajes en el blog de Julio César Pérez Arques y dado que hemos llegado a un punto donde no lo puedo “despachar” con un par de frases ocurrentes, creo que lo oportuno es explicarlo en un formato más “extenso”.

En un artículo de su blog, Julio César resumía bastante bien una presentación de Neal Ford en InfoQ, pero dijo algo que nos llevó a iniciar esa “eterna” discusión sobre modelo anémico y modelo rico:

No me gustan nada los constructores con más de 2 parametros. Prefiero uno vacio y luego hacer sets.


Julio César en realidad luego explica la postura de Neal Ford sobre el uso de constructores, más alineada con los modelos ricos que con los modelos anémicos que defiende Julio.

Y ante mi crítica, Julio se excusa (en vano, je, je) diciendo lo siguiente:

No se si es que estoy demasiado Springizado pero me gusta más tener mi capa service y mi capa dao separaditas. Supongo que también tiene que ver con el tipo de aplicaciones en las que trabajo, basicamente de gestión sobre una bbdd ama y señora.
¿Por qué acaso en una aplicación de gestión no son todas las clases del modelo Value Objects? Para mi la unica diferencia es en si mueven los datos del cliente a la app o si de la app a la bbdd. Siempre desde mi pto d vista…

No voy a entrar a discutir si usar Spring y tener las capas de servicios y de acceso a datos desacopladas tienen algo que ver. Para mi no, pero no me interesa discutir ahora sobre frameworks, porque me interesa “atacar” esa idea de que una “aplicación de gestión” es poco más que una aplicación de “data entry”. No estoy de acuerdo. Para nada, una aplicación de gestión es justamente donde más y mejor podemos aplicar el Domain-Driven Design.

Pero voy a usar un viejo artículo de Udi Dahan (al que, sí, es cierto, últimamente cito mucho) en el que explica muy, muy bien cómo y por qué debemos orientar nuestros diseños hacia el modelo de dominio en vez de al CRUD.

Udi pone el ejemplo de un sistema para hacer entrevistas a candidatos en una selección de Recursos Humanos. Y así, si modelamos esta aplicación como un conjunto de inserciones, actualizaciones y eliminaciones de objetos Cita, nuestro código para el servicio de citar a un entrevistado sería algo así como:


Cita cita = new Cita();

cita.setEntrevistador(entrevistador);
entrevistador.getCitas().add(cita);

cita.setCandidato(candidato);
candidato.getCitas().add(cita);

cita.setFechaHora(fechaHora);

dao.guardar(cita);

Pero, en cambio, si nos orientamos al dominio y modelamos el sistema como objetos con estado que se encargan ellos mismos de persistirse cuando cambian de estado (si es necesario), conseguimos que el código de nuestros servicios no sólo no esté acoplado al modelo de datos sino que es más fácil de entender al carecer de artificios tecnológicos:


entrevistador.planificaEntrevistaCon(candidato).enFechaHora(fechaHora);

Si entramos a discutir cómo se implementan estos métodos, a partir de este punto tendremos una gran discusión porque no todos se ponen (nos ponemos) de acuerdo sobre cómo hay que hacerlo. Muchos hablamos de la ignorancia de la persistencia, es decir, de que los objetos de dominio no deben llamar nunca a los DAOs directamente sino que para ello hablan con los Repositorios usando una interfaz tipo Collections. Otros dicen que por qué esa indirección si el 99% de las aplicaciones tienen que persistir los datos. Yo tengo claro que se trata de aplicar los principios SOLID y que, por tanto, lo correcto es desacoplar la lógica de negocio de la lógica de persistencia. Pero para gustos hay colores…

Esta semana lo voy a tener un poco difícil, pero puede que para la semana que viene pueda implementar completo este ejemplo usando Spring y JPA. Espero que la salud mía y de mis niños me lo permita. Je, je…

Para terminar, si a alguien le interesa todo esto del Diseño Orientado al Dominio, le invito a pasarse por la lista de DDD-es que hemos creado recientemente.

Tagged:
  • jcesarperez

    Hola de nuevo.

    La verdad es que tengo poco más que decir. No me gusta el modelo adulcorado que propone Fowler y cia para aplicaciones de tipo acceso a bbdd (en otros sí me gusta más). Pero intentaré explicarme mejor esta vez:

    La idea es llevarte la lógica de los sets del metodo service. Yo pregunto: ¿merece la pena distribuirla por todas las clases del modelo y tener que programar todos esos metodos DDL?
    El codigo del método de la clase servicio debería ser algo más de este estilo:

    Cita cita = buildCita(entrevistador, candidato, fecha);
    dao.save(cita);
    Ya me he llevado la lógica set a un método aparte. Tengo el código bien limpito y he tardado mucho menos. Pero vamos, es sólo mi opinión. Y repito, porque es una app tipo acceso a bbdd donde el modelo suele ser enorme y propenso a cambios. Las odio, ¿se nota?

    Pero tengo una pregunta, ¿qué pasaria con el caso de uso anularCita? ¿A quien le das la responsabilidad de anular la cita? ¿Sólo al entrevistador? Pero un candidato tambien puede anularla. ¿Repites código?. ¿Le das la responsabilidad a la clase Cita? Entonces ¿por qué no darle tambien la de planificarse? Demasiadas preguntas para algo tan sencillo y que al final se traducen en unos inserts SQL. Y sobretodo, IMHO, creo que se construyen aplicaciones más difíciles de mantener.

  • Jose Manuel Beas

    Ese buildCita no es más que esconder la complejidad con la intención de reusar. Pero en el largo plazo verás que reutilizas bien poco…

    Y repito, el que las aplicaciones sean “tipo acceso a base de datos” es algo que hacemos nosotros. No conozco a ningún usuario que diga “quiero pensar en términos de tablas y registros” en vez de “quiero pensar en términos de mi negocio”.

    Respecto a tu pregunta de cómo implementar el anularCita, es una pregunta que (orientándote al dominio) tienes la oportunidad de preguntarle al cliente sobre cómo implementa su negocio. Si ambos pueden anular citas “unilateralmente”, implementarás ambas operaciones en el objeto Entrevistador y en el objeto Candidato. Pero ojo, no estarías repitiendo código, estarías escribiendo el código que corresponde a cada regla de negocio. Todo esto debería estar desacoplado de la persistencia, cosa que no ocurre cuando usas un modelo anémico. Una discusión similar surge cuando hablamos de usar el patrón ActiveRecord que asume una relación 1:1 entre modelo de dominio y modelo de datos.

    Yo no veo la clase Cita teniendo responsabilidades, para mi es un ValueObject, como una dirección. ¿Tú ves una cita cambiándose a sí misma? ¿O cambiando a un entrevistador? Bueno, si la cita cambia a cinco minutos de la hora establecida probablemente cambie el humor del entrevistador. 😉

    Y para terminar, el código es más fácil de mantener sólo cuando está escrito de manera que el usuario nos lo pueda validar. Cuanto más lejos estamos de ese escenario, mayor es el esfuerzo de mantenimiento y mayor es la oportunidad que damos a las malas interpretaciones de los requisitos. ¿Has jugado alguna vez al “teléfono estropeado“?

  • jcesarperez

    Ya estoy de vuelta. Sigo pensando que esta discusión es demasiado rica para el cuadradito de comentarios de blogger.

    Intentaré ordenar mi respuesta:

    1- El método build es principalmente para obtener un código más sencillo y mantenible mediante descomposicion en métodos y nivel de abstracción. La reutilización es un bien adicional.

    Y en este aspecto de simplicidad y mantenibilidad. Veo el modelo anémico más sencillo y rápido de implementar/modificar además de más resistente al cambio de programadores.

    2- El Usuario sabe poco-nada de la base de datos. El que sabe de ella es el Cliente, que ya tiene experiencia y sabe que las aplicaciones van y vienen pero su base de datos relacional está ahi. Es el centro de todo. En el mejor de los casos hasta tiene su DBA que cuida de que cumplas su “guía de estilo”. Y, por supuesto, no quiere saber nada de ORMs.

    No quiero salirme del debate en sí. Pero nunca ví un Usuario que me validara el código. Me doy con un canto si me hace pruebas decentes. Eso me lleva a pensar en el tipo de clientes y proyectos que pueden tener empresas como ThoughtWorks y me da mucha envidia, así que lo borro de mi mente…

    De todas formas, hablo siempre haciendo un gran ejercicio de imaginación. Me encantaría participar en un proyecto DDD y hablar desde la experiencia. Pero me tendré que conformar con ver tu código 😛

    Una duda, el método planificaEntrevistaCon devuelve un Entrevistador o una Cita?

  • Herme Garcia

    Un poco bruto el Neal Ford ese, el numero de parametros del constructor no lo elige el programador, son los que son.

    Y son aquellos objetos que necesita el objeto a construir para ser válido.

    porque la mision de un constructor es construir el objeto o saltar una excepcion.

    Imagina un objeto Televisor, si definimos un televisor como un objeto con pantalla, mando y dos altavoces iguales, esos son los tres parametros del constructor.

    Luego le puedes poner un tv.setHdmi() o tv.setBluetooth() pero eso es opcional ya que no hemos definido así al objeto.

    El minimo numero de sentencias que debemos escribir para tener un objeto valido es 1, la del costructor, factory o similar.

    .. o eso pienso yo

  • Jose Manuel Beas

    @Herme

    ¡Qué bueno saber de ti de nuevo! Estoy de acuerdo en tu estupendo ejemplo salvo un matiz en tu premisa de que el programador no puede elegir el número de parámetros. Yo diría que es una buena práctica el mirar si un ValueObject nos vale para reducir el número de parámetros.

    @Julio César

    Cuando tenga el código de ejemplo completo quedará más claro. De momento está “en la pizarra” y ya se sabe que ahí siempre funciona. 🙂

    En respuesta a tu pregunta, en uno de los comentarios del artículo de Udi dice que este método devuelve un IAppointmentThatHasNotBeenScheduled . Yo no suelo usar interfaces para los objetos del modelo de dominio (aunque la intención de Udi es buena: para resaltar cualidades del objeto). La idea sería la misma, un objeto con entidad propia al que tiene sentido pedirle que haga algo. En términos de DDD, las citas son ValueObjects que forman parte de los AggregateRoot de Entrevistador y de Candidato. Esto es, que nunca deberías tener acceso a la lista de Citas de un Candidato para manipularla salvo que seas el propio Candidato. Idem para las citas de un Entrevistador.

    Piensa que el servicio es el encargado de que el ORM y los repositorios preparen el terreno para lo que los objetos del dominio harán luego. Así, el código que es verdaderamente difícil de reutilizar porque suele ser único para cada regla de negocio, quedará mucho más “limpio de polvo y paja”, limitándose a describir la lógica de negocio y dejando las dificultades técnicas a otros. Esto se resume en que los objetos del modelo de dominio se prueban unitariamente muy fácilmente mientras que los servicios se comprueban con pruebas de integración (algo más complicadas, por lo tanto).

    Me da rabia no tener más tiempo libre para poder preparar el ejemplo más rápido, pero agradezco mucho tus comentarios porque así quedará mucho más claro el ejemplo, que es de lo que se trata al fin y al cabo.