Modelo de dominio matriarcal (II)

Antes que nada pedir disculpas, especialmente a Julio César Pérez porque hace ya varias semanas que, en la primera parte de este artículo, le prometí que iba a escribir un ejemplo para explicar cómo se implementaba la ignorancia de la persistencia (“persistence ignorance” en inglés) en un modelo de dominio al estilo DDD. Lo cierto es que he visto que me estoy metiendo en tantos fregados (incluídas mis labores familiares cotidianas) que cada día tengo menos tiempo de ponerme a programar con la mínima continuidad necesaria.

Cargo freighter passing under the Golden Gate bridge in San Fransisco. Image courtesy of FreeFoto.com
Bueno, como la respuesta a Julio la tengo más o menos elaborada, voy a utilizar código ajeno para explicarme. A ver si queda suficientemente claro. El código completo está disponible en el ejemplo “oficial” de DDD: DDDSample. Lo podéis bajar desde el repositorio de subversion (yo he utilizado la última versión etiquetada como 1.1.0) o podéis “navegar” por el código fuente coloreado e hipervinculado generado con el plugin maven jxr. Seguramente explicar este ejemplo daría para varios blogs, pero me voy a centrar en la explicación de la persistencia. Si queréis ver los ficheros de configuración de Spring e Hibernate no os quedará más remedio que acceder al SVN. Perdonad que no os hipervincule todas las clases pero ya os he explicado que ando justito de tiempo libre… 🙁

Este DDDSample es la implementación del ejemplo empleado por Eric Evans en el libro azul y que se trata de una compañía de transporte de contenedores. Yo voy a seguir el camino de una petición de reserva del envío de un contenedor, que es algo sencillo pero que implica guardar datos en la base de datos. No voy a entrar en cómo se llama desde la UI al servicio para no extenderme demasiado, pues como dije más arriba tenéis todo el código disponible para descargarlo, pero sí que os explicaré (al final) el “truqui” que emplean aquí por si os suena un poco raro.

Todo empieza por una fachada llamada BookingServiceFacadeImpl (que implementa la interfaz BookingServiceFacade y que no sirve para otra cosa que para envolver al “verdadero servicio” (BookingServiceImpl, que implementa BookingService). El constructor de BookingServiceImpl tiene como parámetros un CargoRepository, un LocationRepository y un RoutingService, esto se podría cambiar por un constructor vacío y varios setters, pero siempre y cuando nos aseguremos de no usar BookingService sin antes haber “seteado” estos colaboradores. Por eso es más seguro inyectar los colaboradores imprescindibles en el constructor. En DDDSample se hace con Spring en context-application.xml:







El método que nos interesa es bookNewCargo:


@Override
@Transactional
public TrackingId bookNewCargo(final UnLocode originUnLocode,
final UnLocode destinationUnLocode,
final Date arrivalDeadline) {
// TODO modeling this as a cargo factory might be suitable
final TrackingId trackingId = cargoRepository.nextTrackingId();
final Location origin = locationRepository.find(originUnLocode);
final Location destination = locationRepository.find(destinationUnLocode);
final RouteSpecification routeSpecification = new RouteSpecification(origin, destination, arrivalDeadline);

final Cargo cargo = new Cargo(trackingId, routeSpecification);

cargoRepository.store(cargo);
logger.info("Booked new cargo with tracking id " + cargo.trackingId().idString());

return cargo.trackingId();
}

Fijaos que no hablamos con la BD para guardar el objeto Cargo que recién creamos, sino que hablamos con un CargoRepository: el que hemos inyectado en el constructor y que puede perfectamente guardar el objeto en la BD o en cualquier otro sitio, p.ej. una Collection. ¿Por qué no? Si no hubiera un requisito que nos pidiera poder recuperar la información de los envíos (Cargos) si la aplicación se reiniciara, no habría nada que nos impediría mantener en memoria esta información. ¿Verdad? En esto justamente consiste la ignorancia de la persistencia. Si cambiamos la política para guardar los datos, el código de los objetos Cargo no necesita ser modificado ni una línea. Es más, nuestro servicio (de dominio) BookingService tampoco. Sólo necesitamos cambiar el repositorio que inyectamos. Esto es fácil con Spring. Veamos cómo está hecho en context-infrastructure-persistence.xml:







No pongo los detalles de cómo se configuran transactionManager ni sessionFactory porque no son significativos: son idénticos a los de cualquier otra aplicación. Lo que sí es relevante aquí es CargoRepositoryHibernate. Fijaos en que está anotada con y extiende HibernateRepository (que simplemente sirve para tener el setSessionFactory y el getSession). Finalmente, echemos un vistazo al método store que es el que nos interesa ahora, pero echad un vistazo “de refilón” a los demás porque pueden ayudaros a situaros sobre cuál es la responsabilidad de este objeto (que es MUY parecido a un DAO).


public void store(Cargo cargo) {
getSession().saveOrUpdate(cargo);
// Delete-orphan does not seem to work correctly when the parent is a component
getSession().createSQLQuery("delete from Leg where cargo_id = null").executeUpdate();
}

Observad que alguien ha dejado un comentario explicando el borrado de ciertos objetos relacionados. Esto tiene un cierto “tufillo”, yo hubiera extraído el comentario y la linea a la que se refiere a un método private llamado deleteOrphansWhenParentIsAComponent o algo así, y hubiera llevado el comentario al javadoc del método y me aseguraría de tener un buen test para esto que huele tan raro… pero es lo que hay…

Llegados a este punto algunos volveréis la vista atrás y diréis: “¡Demonios! ¿Dónde hace commit?”. Aquí es donde tenemos que mostrar todas las cartas y enseñar dónde se configura aquella fachada de la que hablábamos al principio BookingServiceFacadeImpl:









hibernateInterceptor









Claro, ahora se entiende, usamos el HibernateInterceptor con la misma sessionFactory de nuestros repositorios. Je, je… Vale, es un poco “truqui”, pero es la manera más limpia que hay de trabajar “puertas adentro” y olvidarte de historias.

¡Uy! Se me olvidaba explicar cómo es el objeto Cargo, que es lo que en DDD se denomina AggregateRoot y que es algo así como la raíz de un árbol de objetos dependientes, cuya existencia depende de la raiz. En este caso son Cargo, Itinerary, Leg, Delivery y RouteSpecification. No voy a profundizar aquí, pero diré que cuando guardamos un Cargo podemos estar guardando todos los que dependen de él. Cargo también es una Entity (en el sentido de DDD y también en el sentido de JPA, si lo usáramos).

¿Cómo se mapea este AggregateRoot con las tablas de la BD? Si usáramos JPA sería muy fácil viendo las anotaciones; en el caso de DDDSample han usado los ficheros de mapeo de Hibernate. No voy a entrar en detalles sobre esto porque sería explicar Hibernate, y no es mi intención ahora mismo.

Por último, el “truqui” del que os hablaba al principio que usan para exponer la fachada. Usan RmiServiceExporter, una utilidad (como otras muchas) de Spring, y que permite acceder a este objeto BookingServiceFacadeImpl como si de un EJB se tratara. Fácil, ¿verdad? Bueno, también podríais haberlo anotado con y todas esas cosas que explican en Java EE. Ya se sabe, para gustos, colores. 🙂

Espero que haya quedado claro el “meollo” de la cuestión, que tiene mucho que ver con aquello que contaba hace ya bastante sobre la Arquitectura en Capas de Cebolla. La idea que me gustaría que os quedara después de este artículo (y sobre todo el anterior) es que la ventaja de hacer un modelo de dominio rico y carente de dependencias tecnológicas consiste en que tenemos un código más limpio y realmente desacoplado, lo que nos trae un montón de ventajas como que podemos comprobar unitariamente nuestro código con mucho menos coste y riesgo de errores.

Quedo a vuestra disposición para discutir lo que creáis conveniente.

Nota: Perdonad el formato de los xml, pero Blogger me reformatea los elementos vacíos y me monta un cristo, hasta que me he dado cuenta y los he cambiado a mano.

Tagged:
  • jcesarperez

    Hola José Manuel. Nada de disculpas eh, es un placer “discutir” contigo.

    Aunque esta vez lo has puesto muy fácil. Lo cierto es que tu método bookNewCargo no es muy diferente a todos los que pueblan mis clases *Service en mis modelos anémicos.

    Quitando la diferente de nomenclatura Repositorio-Dao, la única diferencia son esos constructores con 2 y 3 parametros por mis constructores vacios y los métodos set correspondientes.

    No sé, esperaba un poquito más de azucar DDD 😛
    Además esta vez el uso de constructores hasta me parece bien, no supera el limite de 3 parametros y además ninguno es un String.

    Yo uso los constructores vacios para evitarme esos constructores de 3-6 parametros String, folloneros de usar (nunca te acuerdas del orden) y de mantener (sobretodo si te da por no querer añadir los nuevos parametros al final).

    Muchas gracias por tu tiempo!

  • Jose Manuel Beas

    Vale, vale… tienes razón. Faltaba azúcar. 🙂

    Lo que no he explicado bien es cómo queda desacoplado el dominio (el objeto Cargo y el resto de objetos que forman parte de ese Aggregate) del resto de la lógica que no tiene que ver directamente con la lógica del negocio (e.d. la persistencia o la posible coordinación con otros servicios).

    En realidad la diferencia se sustancia en que tus objetos anémicos no pueden hacer nada por ellos mismos (sólo son contenedores de datos y como mucho realizan algún cálculo con esos datos, vamos, que son Value Objects) y el estado del negocio sólo se puede conocer cuando miramos el estado de la BD.

    En cambio, si te fijas bien, el objeto Cargo no es un mero contenedor de datos sino que representa parte del estado del negocio y delega en otros objetos para representar otras partes del estado del negocio. Es decir, son objetos “vivos” que se relacionan con otros objetos “vivos”, con comportamiento propio.

    Esa es la diferencia fundamental. El modelo rico es más OO (Orientación a Objetos) que el modelo anémico (que es realmente más procedural).

    Si recuerdas el ejemplo que ponía en la primera parte de este artículo, el Entrevistador es el encargado de planificar la entrevista, no el servicio de entrevistas. En el ejemplo del modelo anémico se veía muy claro que era el servicio el encargado de crear el objeto Cita y guardarlo en la BD. En el modelo rico, es el objeto Entrevistador el que crea el objeto Cita, que es más cercano a lo que ocurre en la realidad, ¿verdad?

    En fin, espero que ahora haya quedado algo más claro. Sé que es difícil de pillar porque esto del modelo anémico lo tenemos (me incluyo) demasiado metido en las venas. Pero estoy completamente convencido de que muchos de los problemas que tenemos en nuestros desarrollos vienen del hecho de que usamos lenguajes orientados a objetos para programar procedural y, claro está, arrastramos los mismos problemas de cuando programábamos en COBOL. ¡Ay, qué tiempos aquellos! (Vaya, me hago mayor) 🙁

  • jcesarperez

    Hola de nuevo!

    Si claro está. Pero no termino de verle la ventaja al DDD.

    Vale, tengo un diseño donde las clases entidades del modelo tienen más protagonismo y las clases service menos. Pero esto es realmente una ventaja en terminos de desarrollo y mantenimiento?

    En mi opinión para la típica aplicación CRUD de bbbdd no lo es.
    Cuando trabajamos en una aplicación de este tipo estás deseando tener una versión del modelo lista para que todo el equipo pueda empezar a trabajar en el resto de capas que usan el modelo.

    Además todos sabemos que esa 1a versión del modelo va a cambiar, por eso cuánto más flexible sea mejor. Y el modelo anémico junto con el refactoring de un ide moderno es muy flexible.

    Con DDD las implicaciones de cambiar un método de negocio del modelo son más imprevisibles que en los modelos anémicos.
    También encuentro el código DDD muy tentador a romper esa “ignorancia de la persistencia” llamando a daos desde las clases del modelo.

    Quiero aclarar que esta es mi opinión desde el pto. de vista de responsable de un equipo con programadores de nivel medio. Como artesano el DDD me mola! Y si fuera programador de juegos ni te cuento…

  • Jose Manuel Beas

    ¿“La típica aplicación CRUD”? ¿Es que acaso tus clientes quieren seguir haciendo “data entry” o realmente quieren que le automatices sus procesos de negocio?

    Por otro lado, cuando dices “flexibilidad” en realidad quieres decir “rapidez para repartir trabajo”. Pero eso no necesariamente es bueno. Si te fijas, no estarás repartiendo las responsabilidades correctamente. En mi caso, podrás probar los objetos del negocio independientemente, ídem con los repositorios, etc.

    Tu modelo anémico es fácil de entender porque representa la “forma” de tu negocio, pero no el “comportamiento” y por eso tienes que poner el comportamiento en tus “servicios”. Pero eso te obliga a pensar siempre en “scripts”, en “transacciones”, y eso no es Orientación a Objetos, eso es Procedural, eso es COBOL. Todo lo modular que quieras, pero tu modelo anémico es tu DATA DIVISION.

    ¿Por qué es mejor orientarse a objetos? Porque es más fácil hablar el lenguaje de negocios. Si te fijas, tus “servicios” están mezclando negocio y tecnología. El lenguaje que se usa dentro de un modelo rico es sólo el lenguaje de negocio. No hay nada de tecnología. (De ahí la ignorancia de la persistencia).

    Dicho todo esto: DDD no es “una bala de plata” (de hecho, nunca he visto una). Todo el mundo coincide que DDD no sirve para hacer prototipado rápido, p.ej.

    De todos modos, ojo, que yo no soy un experto ni hablo desde la práctica exitosa. Así que toma esto como “la voz de un creyente que aún no se le ha aparecido la Virgen”. 🙂

  • jcesarperez

    Hola otra vez! Hoy ha sido un día largo… Por cierto, ten cuidado que te crecen los trolls!

    No entro en lo de los clientes para no desviar el tema.

    Me reafirmo en que es flexibilidad, y como consecuencia tiene que puedo repartir antes el trabajo. Y el desarrollar rápido es una necesidad real e importante para mi y para el cliente.

    También quiero aclarar que eso de que los modelos anémicos no son OO no es cierto. La única diferencia es que DDD se quiere llevar la logica de negocio a las clases entidad. Para mi, las clases entidad ya tienen btes responsabilidades: gestionan la info de forma flexible(get/set) + formateo de info + mapeo bbdd (anotaciones) + validacion de datos (más anotaciones).
    ¿Cómo se llevan tus entidades DDD con tener anotaciones de persistencia? ¿Ves la contradicción?

    Prefiero llevarme la lógica de negocio a las clases service (llámalas gestores o managers si encuentras el termino service demasiado orientado a procedimientos). Así obtengo clases más sencillas, más mantenibles y divido trabajo.

    Además la gran mayoría de entidades siguen necesitando métodos set/get porque siempre hay que leer y hay que modificar datos sueltos. Entonces dejas la puerta abierta a que puedan usarse directamente con get/set y pasar de tus metodos de negocio.

    Según lo veo yo, tu quieres creer y yo soy un curioso desconfiado.
    Creo que tu pto de vista es más académico o utópico (pensar en un modelo perfecto y tratar de hacerlo real), mientras que yo siempre he sido más de Maquiavelo (afectar desde y sobre la realidad)…
    Aún así ha vuelto a ser muy instructivo y de verdad me gustaría probarlo y dejar de imaginar.

    ¿Crees que podriamos sacar un poco de tiempo y programar algo ahora en verano? Aunque fuera sólo el modelo y los services sin las capas de presentación y data. Podriamos iniciar un proyecto en google code, programar un modelo y services DDD para lo de las Citas y evaluar cómo afectarian algunos cambios en los requisitos.

  • Jose Manuel Beas

    A ver, empiezo por lo de que los modelos anémicos son OO. Es evidente que estás usando un lenguaje OO, pero no estás usando objetos con estado y comportamiento simultaneamente, sino que estás separando estas dos cualidades, lo que te lleva a hacer programación procedural apoyada en una estructura de objetos relacionados entre ellos pero sin comportamiento. Eso, siendo purista, no es OO.

    Ahora bien, no voy a contradecirte ni a ti ni a Martin Fowler cuando decís que necesitáis más argumentos para desechar el enfoque anémico. A riesgo de saturar los 4096 chars de esta “cajita” voy a añadir sólo un poco del artículo seminal de Fowler:

    In essence the problem with anemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits. The primary cost is the awkwardness of mapping to a database, which typically results in a whole layer of O/R mapping. This is worthwhile iff you use the powerful OO techniques to organize complex logic. By pulling all the behavior out into services, however, you essentially end up with Transaction Scripts, and thus lose the advantages that the domain model can bring. As I discussed in P of EAA, Domain Models aren’t always the best tool.DDD no es una “bala de plata”, de hecho, creo que ya lo he dicho, DDD no es recomendable para según qué problemas. Lo que sí es cierto es que si primas el cortoplacismo de repartir tareas rápidamente, probablemente no estarás repartiendo conocimiento, cosa a la que te empuja el usar un modelo de dominio donde los objetos incluyen su comportamiento y no son simples “transportadores de datos”. Insisto, no estás haciendo Java sino COBOL o PL/SQL. Tus servicios no hacen mucho más que mover datos de un lado a otro, pero en un lenguaje lejano al del negocio. Y ahí es donde DDD pone énfasis: en el lenguaje ubícuo y en incorporar todas las reglas de negocio en el modelo de dominio.

    Me pregunto una cosa: “¿cómo repartes el trabajo a tu equipo? ¿por funcionalidades del tipo “el usuario paga una factura” o “el usuario se quiere dar de baja de una suscripción a una revista”? ¿o por el contrario repartes trabajo al estilo “hacer el CRUD de clientes”? El primer enfoque es más orientado al negocio, el segundo más orientado a la tecnología.

    No es cierto que necesitemos get/set para nuestras clases. Eso lo necesitas para los DTOs que llevan lo que el usuario ha escrito en un formulario de la UI hasta el servicio, pero cuando no estás haciendo puramente un “data-entry” lo que haces es rellenar un par de campos, seleccionar un combo y pulsar un botón, p.ej. Eso es una operación de negocio que puede llevar esos dos campos y un combo en un DTO, pero donde ese DTO obviamente no forma parte del modelo de dominio puesto que no es más que un artificio relacionado con cómo hemos construido la UI.

    Hablabas en un comentario anterior sobre el coste de mantenimiento de un modelo de dominio rico y, tengo que discrepar. Si lo ves todo como un conjunto, el hecho de que tienes un lenguaje único empleado por técnicos y especialistas del negocio, lo que contribuye a eliminar errores (entre otras cosas porque es mucho más fácil discutir sobre el propio código con la gente de negocio puesto que no hay artificios), hace que el coste de desarrollo sea muchísimo menor.

    Tengo un proyecto creado desde hace varios meses, pero como puedes ver, apenas tengo nada más que pruebas. Si te apetece, te añado y colaboramos. Lo discutimos en privado, ¿te parece?

    Finalmente, siento que te parezca un enfoque utópico u académico (no creo que una cosa tenga que ver con la otra). Lo cierto es que no he podido poner en práctica el uso de modelos ricos porque yo también vengo de trabajar siempre con esos modelos anémicos que vienen en todos los libros de J2EE. Pero que usar un modelo anémico es ser pragmático y usar un modelo rico es ser utópico me parece mucho decir.

  • jcesarperez

    La verdad es que la cajita se vuelve a quedar pequeña. Intentaré ir al grano y cerrar el debate porque creo que nuestras posturas han quedado más que claras.

    Sigo en mis 13. Los modelos anémicos son igualmente OO que los de DDD.
    La diferencia está en las responsabilidades que se asignan a cada clase. Para mi la clase entidad DDD tiene demasiadas responsabilidades: mapeo O/R, gestión de datos, validación, formateo y lógica de negocio. Y por eso es costosa de mantener y me atreveria a decir que escala mal.

    Corrigeme si me equivoco, pero tus entidades tienen get y set para poder realizar el mapeo O/R, ¿no?, ¿o es que tienes entidades y luego dto para la vista y dto para la bbdd? Me pareceria una pasada de clases…

    Creo que el problema puede venir en el nombre. En DDD, tienes Cargo, pero en anémico a lo mejor deberia llamarse CargoItem (pej). Asi queda más claro la responsabilidad y es más OO desde tu punto de vista.

    Hay que aceptar que un sistema informático nunca a va a representar la realidad al 100%, asi que hay que saber cortar por donde más convenga. No sólo en el sentido de modelar, sino también en el de llevarlo a cabo por un equipo de trabajo.
    Por ejemplo, si quisieramos ser realistas en tu otro ejemplo del Entrevistador, a lo mejor deberias tener una clase Secretaria 😛

    Por favor, no te tomes a mal lo de utópico. Le debemos mucho a los planteamientos utópìcos! La OO pej.
    Pero yo me enfrento diariamente a una realidad de sacar proyectos con un equipos de trabajo que no siempre llega al nivel de aptitud, actitud y experiencia que me gustaría.
    Si tuviera un equipo con todo artesanos (y bien pagados para estar seguro de que no se van a ir mñn) si podría parecerme realista un planteamiento DDD. Pero eso no es así y no tiene pinta de cambiar.

    De todas formas, no veo a un cliente con 3 desarrolladores discutiendo con código delante.

    Finalmente estoy contigo y Flowler en que DDD si es una opción para determinados tipos de proyecto y equipos.
    A mi me encantaria trabajar en ThoughtWorks o similares, aunque fuera el más tonto de allí!