Feeds:
Entradas
Comentarios

Archive for the ‘Modas’ Category


Aquí estoy de nuevo, esta vez con un post tecnológico. Voy a tratar de explicar en pocas palabras (a ver si no me sale un nuevo truño-post) lo que son los DVCS, que ventajas tienen y si nuestra alma corre algún peligro al usarlos.

Un Distributed Version Control System (DVCS) o en castellano, sistema de control de versiones distribuido, es la última moda en control de versiones de código, y al contrario de lo que yo pensaba en un principio, de distribuido tienen poco, de hecho es un nombre engañoso. Un DVCS no es más que un repositorio de código que puede sincronizarse con otros repositorios, pudiendo actuar tanto como cliente, tanto como servidor de otros repositorios. Lo más sencillo es comparar los DVCS con los tradicionales repositorios de código centralizados. Mantendré la discusión en este post a un nivel sencillo, de forma que cualquier usuario de repositorios tradicionales pueda entenderlo, para ello haré algunas simplificaciones, por favor, gurus del Git, no me crucifiqueis.

Tradicionalmente hemos usado repositorios centralizados. Un servidor central al que conectábamos nuestros entornos de trabajo. Cuando queríamos traer código desde el repositorio central a nuestro entorno de trabajo local, hacíamos un check out o update. Cuando queríamos mandar nuestros cambios al repositorio remoto hacíamos un check in o commit. Si queremos añadir nuevos ficheros o proyectos al repositorio central hacemos un share o add ¿Cómo hacemos todo ésto en nuestro nuevo y flamante DVCS? Pues exactamente igual: add, commit y update. Sí señor, en esto los DVCS son como cualquier otro repositorio.

En cuanto a los repositorios centralizados, los había principalmente de dos tipos:

  • Bloqueantes. Para poder trabajar con un fichero necesitas abrirlo en edición. Esto bloquea el fichero e impide que otro compañero, uy, quise decir usuario, pueda abrirlo en edición y trabajar con él. Cuando has terminado haces commit y el fichero se desbloquea. También puedes descartar cambios y desbloquear. La lógica detrás de este tipo de sistemas se basa en intentar disminuir la cantidad de conflictos y en la idea de que ciertos artefactos de código pertenecen a ciertos desarrolladores y nadie debería poder tocarlos (private code ownership). Con estos sistemas tienes menos conflictos, aunque también puede aparecer alguno de vez en cuando. También te lo pasas pipa cuando alguien se deja el fichero bloqueado y cae enfermo, se da de baja, está de vacaciones o en una reunión. Como veis este tipo de sistemas no promueven el agilismo.
  • No bloqueantes. Los más famosos son el CVS y el SVN. Simplemente te actualizas el fichero, trabajas sobre él, y haces commit de los cambios. Si tienes suerte no hay conflictos. Si no, tienes un conflicto, alguien modificó el fichero y lo subió antes que tu. Normalmente los conflictos se resuelven con un merge automático, pero a veces necesitas hacer un merge manual. Si usas integración continua no tendrás mucho problema, ya que al integrar cambios más frecuentemente, la cantidad de código sobre los que pueden haber conflictos es pequeña, con lo cual la probabilidad de éstos disminuye y la facilidad de resolverlos aumenta. De hecho la probabilidad de un conflicto y la facilidad para hacer el merge aumentan exponencialmente con la cantidad de código a integrar, lo que hace que si no usas integración continua seas hombre muerto. Si no usas integración continua, es mejor que uses un sistema bloqueante, y por lo tanto no podrás ser ágil.

Los DVCS son no bloqueantes (al menos todos los que yo conozco). Como veis los DVCS ese parecen mucho a un SVN o CVS, ¿dónde está la diferencia? Muy sencillo, los DVCS introducen dos nuevas operaciones:

  • La operación push. De la misma manera que podemos enviar cambios con un commit desde nuestra área de trabajo al repositorio, podemos enviar los cambios que hay en un repositorio hacia otro repositorio, si tenemos permisos claro. Esto puede generar que en el repositorio destino se tengan conflictos, es decir, distintos cambios sobre el mismo artefacto provenientes de distintos repositorios.
  • La operación pull. De la misma manera que actualizamos nuestro entorno de trabajo local con el contenido del repositorio central, podemos traer cambios desde otro repositorio hacia el repositorio origen del pull. Es la operación simétrica al push, y puede generar conflictos en el repositorio origen del pull.

Las operaciones push y pull pueden tener un commit en el destino implícito o no, dependiendo del DVCS que usemos y del comando exacto que lancemos. En caso de que no tengan commit implícito, debemos hacer commit manualmente si queremos aceptar los cambios.

Como hemos visto tanto el push como el pull pueden generar conflictos, que deben ser resueltos con merge. En los DVCS los algoritmos de merge están optimizados y suelen resolver automáticamente más conflictos que en un SVN o CVS, y tardan menos en ejecutarse.

Armados con estos conceptos podemos entender como se trabaja con un DVCS. Lo que se suele hacer es que cada desarrollador tenga uno o más repositorios instalados en su máquina local. Esto permite a cada miembro del equipo trabajar en local, con las consiguientes ventajas:

  • Se puede trabajar sin necesidad de conectividad, ya que el repositorio está en local.
  • Mayor rendimiento, ya que no hay necesidad de tráfico de red.
  • Mayor escalabilidad. No hay un servidor central que deba soportar la carga concurrente de distintos usuarios.
  • Sencillez en la gestión de los permisos. Cada desarrollador local sólo necesita dar permisos a los compañeros en los que confía.
  • No hay miedo a hacer commit, ya que el repositorio es local al desarrollador. Este miedo es sustituido por miedo a hacer pull o push, ya lo veremos en un momento.

¿Y lo de distribuido? Pues simplemente cuando dos desarrolladores quieren compartir código o integrar trabajo, hacen pull o push entre ellos.

¿No hay un repositorio central? ¿Quién controla el código del producto? Bueno, puedes tener un repositorio central si quieres, al que todos los repositorios locales entreguen código con pull o push. Pero puedes tener la topología o arquitectura de repositorios que quieras. De hecho es muy importante elegir la arquitectura de repositorios adecuada a implantar en tu proyecto. Yo recomiendo seguir la misma estructura que tienes en tu equipo de trabajo:

  • Si tienes un equipo plano, típico de Scrum, puedes usar una topología peer to peer. Todos los repositorios intercambian código entre si. Adicionalmente puedes añadir un repositorio principal de equipo y otro para las releases estables.
  • Si tienes un equipo jerárquico usa una topología jerárquica. Existe un repositorio central bendito (blessed) controlado por un jefe de proyecto. Este jefe de proyecto tiene lugartenientes con un repositorio cada uno. El jefe sólo incluye código en el repositorio central desde los repositorios de sus lugartenientes, y éstos desde los repositorios de sus subordinados.
  • Si tienes un equipo grande de Scrum, dividido en varios feature teams, cada feature team tendrá su topología de repositorios peer to peer, con un repositorio central por equipo. Los distintos repositorios centrales de los distintos equipos pueden conectarse a su vez en topología peer to peer o en modo jerárquico contra un repositorio principal de proyecto.

En general doy el mismo consejo que con la elección de estructura de equipo, si tienes varios repositorios, en una red rápida, y que deban intercambiar información frecuentemente, conéctalos en peer to peer para facilitar el intercambio de información y no tener intermediarios. Si el número de repositorios ya es grande, puedes añadir un nivel de jerarquía. A mi, en cualquier caso, me gusta tener siempre un repositorio central para cada equipo, y otro por proyecto si hubieran varios equipos.

¿Y como hacemos backup si no hay repositorio central? En esta forma de trabajar, el código importante e interesante, suele estar distribuido entre distintos repositorios. Esto hace que el sistema sea naturalmente tolerante a fallos. Esto no impide que hagas backup de repositorios que consideres especiales o que cada desarrollador haga backup del suyo. La política de backup depende de la topología escogida para los repositorios. Si usáis un repositorio por equipo y/o un repositorio por proyecto podéis hacer backup formal de éstos.

Seguro que todo esto tiene alguna pega… Cierto, al trabajar cada desarrollador en un repositorio diferente, se produce de forma implícita un branching agresivo del código. Es como si usáramos un repositorio centralizado, y cada desarrollador tuviera una rama diferente. Pensad en los posibles conflictos que pueden producirse cuando múltiples desarrolladores hagan push de sus cambios al repositorio del equipo o proyecto. Psicológicamente, el desarrollador, al trabajar contra su repositorio local, puede tener una falsa sensación de seguridad al tener un control de versiones respaldando sus cambios, y olvidarse de hacer push de sus cambios a sus compañeros o al repositorio del equipo. Por otro lado va a tener miedo, y ser renuente, a hacer pull de los cambios de los demás, no vaya a ser que le provoquen conflictos y le rompan su repositorio. Bien, no nos desesperemos, la solución es sencilla, entregar los cambios al repositorio de equipo de forma frecuente, es decir usar integración continua. Si usamos DVCS y no usamos integración continua estamos perdidos.

¿Espera un momento, integración continua dices, pero si no tenemos un repositorio central? Exacto, lo que realmente hace falta es… integración continua distribuida (o incremental) Este invento consiste en poner un demonio de integración continua en cada repositorio. Cada vez que se produce un commit, pull o push sobre el repositorio, el demonio se despierta y mira si hay conflictos. En caso de que los hubiera, intenta hacer un merge automático. Si el merge automático falla, el build se considera roto. Si funciona o no hubiera conflictos, se ejecuta un proceso de integración continua normal (compilación, tests, etc). Si el build es exitoso, entonces se envía una petición a un repositorio de más alto nivel, para que acepte los cambios. Este repositorio de más alto nivel suele ser el de equipo o el del proyecto. Si se aceptan los cambios (de forma manual o mediante una regla de decisión automática), se hace un pull para recibir los cambios, lo que vuelve a disparar el proceso de integración continua sobre éste repositorio. El proceso se repite recursivamente hasta que el build falla o se hace un build con éxito en el repositorio central de proyecto. Una idea interesante es mantener los builds de los repositorios de nivel de desarrollador sencillos y veloces, por ejemplo, sólo compilación y tests unitarios. El nivel de calidad exigido en los builds va creciendo conforme se va subiendo a repositorios de mayor nivel. Por ejemplo el repositorio de equipo puede exigir tests de integración para todas las historias de usuario. El repositorio de proyecto puede pasar una suite exhaustiva incluyendo despliegue en un servidor y tests de aceptación contra una infraestructura más real. De esta manera el build en local se mantiene ágil, y a su vez los cambios que van recibiendo los repositorios de mayor nivel tienen una mayor calidad, ya que han pasado los procesos de build continuo de los niveles anteriores.

Una etapa de Integración continua distribuida

Una etapa de Integración continua distribuida

Algunos estaréis pensando que este proceso de integración continua distribuida es demasiado complejo para ser implementado. Otros quizás penséis que es imposible poner un demonio para cada repositorio. Yo simplemente os digo que hace tiempo, cuando explicaba la integración continua simple, esto es, asociado a un repositorio centralizado, muchas veces me miraban con incredulidad y se solían escuchar las misma protestas: “demasiado complejo…”, “demasiado para nuestros servidores…”, “eso se va a caer…”, “se va a formar un pollo de narices…”, etc.

Otro punto a tener en cuenta, es que la integración continua simple, está siendo sustituida por integración continua multietapa, en organizaciones grandes, con repositorios centralizados. En la integración continua multietapa se realizan más de un proceso de integración continua de forma secuencial, donde cada etapa implica un nivel de calidad más estricto que el anterior. El objetivo es aumentar la escalabilidad del sistema de integración continua, ya que no se lanzan los procesos de integración continua más estrictos, y por lo tanto más pesados, sin tener un mínimo de seguridad de que vayan a tener éxito. Este mínimo de seguridad se consigue ejecutando antes procesos de integración menos estrictos y más ligeros. Si lo pensais es algo muy similar al proceso de integración continua distribuida que os comento anteriormente, pero adaptado a un entorno de desarrollo con repositorio centralizado.

Yo veo varias ventajas en la aplicación de un proceso de integración continua distribuida en un entorno de DVCS:

  • El desarrollador pierde el miedo a hacer pull y/o push de otros repositorios al suyo. La integración continua distribuida baja aun más la probabilidad de conflicto por las mismas razones que la IC convencional.
  • Mayor escalabilidad. La integración continua deja de ser un proceso centralizado, ya no hay un servidor centralizado de integración continua, sino una red de procesos distribuida. Podemos tener equipos más grandes trabajando sin necesitar un super servidor.
  • Mayor tolerancia a fallos. Por las mismas razones que antes, no hay dependencia de un servidor central, lo que hace que podamos seguir integrando código.
  • Si el build se rompe, esto es, que no pasa los test, no compila o simplemente no cumple una nivel de calidad mínimo, el resto del equipo puede seguir integrando en otro repositorio mientras se arregla el build. En la integración continua tradicional si el build no pasa, el repositorio central se bloquea para evitar que alguien suba y fusione cambios con una base de código que no funciona. El equipo que había cometido el fallo debe arreglar el build cuanto antes, pero el resto de los desarrolladores ya no pueden seguir integrando. Esto puede generar miedo a subir cambios al repositorio central y también disminuir la frecuencia efectiva de integración. En un sistema distribuido esto no pasa, ya que se puede seguir integrando contra otro repositorio.

Para alcanzar el máximo de estas ventajas hay que montar una topología de repositorios donde no haya repositorios especiales o blessed.

Por último comentaros que hay dos DVCS distintos que pegan fuerte, que son:

  • Git. Escrito en C, es el que tiene más rendimiento y el más flexible de todos. Sin embargo para la gente que viene de SVN puede ser un poco complejo. Destaca que mediante una hash criptográfica, Git, permite detectar si el contenido del repositorio se ha corrompido o ha sido víctima de un ataque malicioso. Se ha usado con éxito para guardar el código del kernel de Linux.
  • Mercurial (Hg). Más sencillo que Git y escrito en Python, es el que tiene de momento, mejor integración con Windows y con herramientas JAVA. No es tan eficiente como Git y no tiene el concepto de ramas. En vez de ello, puedes clonar repositorios, que actúan como ramas. El clonado no implica una copia física del repositorio original, sino que es por referencia. Las operaciones de push y pull actúan por defecto contra el repositorio original del clon.

Conclusión, si queremos ser ágiles la integración continua no nos la quita nadie. De hecho es más importante decidir usar integración continua, que el hecho de elegir entre DVCS o repositorios centralizados no bloqueantes (SVN, CVS…). En cualquier caso a mi esto de los DVCS me parece un paso adelante y algo muy interesante (más flexibilidad, escalabilidad, trabajo offline y adecuar arquitectura de repositorios a la estructura de equipo).

Bueno, espero que os haya sido interesante. Yo me despido por tres o cuatro semanas. Por motivos personales no podré seguir actualizando este blog durante este tiempo, pero no os preocupéis, volveré. Por cierto, os espero por la conferencia de agilismo que se va a producir en Madrid, la CAS2010, donde tengo el honor de dar una charla junto con mi compañero Antonio David, ¡nos vemos y no falteis!

Ups, lo he vuelto ha hacer, más de dos mil palabras…

Read Full Post »


Hago un alto con la serie de posts sobre la estructuración de los equipos para hacer una introducción a un tema candente, las bases de datos no relacionales y el movimiento noSQL.
Algunos pensarán que esto del noSQL es una moda más, pero lo curioso es que se enfrenta a otra moda mucho más poderosa e influyente, el SQL y las base de datos relacionales. En muchos sitios me enerva una práctica muy común que consiste en que al diseñar la aplicación y analizar los requisitos lo primero que se hace es… ¡el modelo de tablas relacional! Muchos consideran que la aplicación está casi hecha en cuanto tenemos un esquema de base de datos y unas sentencias SQL, y acoplan todo su código a esto. Esta forma de diseñar aplicaciones está muy bien cuando trabajas con COBOL pero desperdicia por completo las características de cualquier lenguaje de programación OO moderno. La cosa llega a tal extremo que muchas personas sólo piensan en aplicaciones CRUD y de hecho hay frameworks como Grails o Rails que te proporcionan una aplicación CRUD muy rápidamente si no te importa acoplarte con la BBDD.
Mi filosofía es un poco diferente, usar Acceptance TDD y DDD y DSL (a.k.a. API Sexy) para definir un core de negocio orientado a objetos que sea fácil de probar, fácil de usar y modele el dominio de la aplicación. Es decir, como uso OO, lo que hago es realmente modelar el negocio mediante un modelo de objetos, que refleja la realidad del problema que queremos resolver y contiene la lógica de negocio. Sobre este modelo monto un DSL para poder trabajar con el modelo escribiendo código que parezca lenguaje natural o al menos sea legible por el analista funcional. En esta filosofía de trabajo la interfaz de usuario y la capa de persistencia son periféricas a este core de negocio. De hecho se puede diseñar el sistema para que tenga distintas interfaces de usuario y puedas cambiar el mecanismo de persistencia de forma sencilla.

Desde este punto de vista, si yo diseño mi sistema con un core OO de negocio robusto, y necesito almacenar los objetos de forma persistente, entonces debo usar una capa de persistencia, y no me importa especialmente cual sea mientras cumpla mis requisitos de la forma más sencilla posible. Desde este punto de vista analicemos lo que nos ofrece una base de datos relacional:

  • Persistencia. Ok, es lo que busco.
  • Lógica de negocio, en forma de procedimientos almacenados y triggers. Pues no, esto me sobra, lo tengo en el core de negocio.
  • Transacciones ACID. Esto depende, si estoy haciendo operaciones críticas que deben realizarse de forma instantánea desde el punto de vista de negocio entonces sí. Si estoy escribiendo una aplicación de redes sociales y quiero escribir una actualización de estado, no me importa mucho si falla al grabarse, o tengo lecturas fantasma. Otro ejemplo de operación no ACID es el de una transferencia monetaria bancaria. Ciertamente das de alta la transferencia de dinero de forma atómica, pero la consolidación de los balances entre la cuenta destino y origen puede tardar días, teniendo mientras tanto un estado “inconsistente”.
  • SQL. Un lenguaje de query complejo, que me permite hacer casi cualquier consulta y que para optimizarlo tengo que ser un experto en BBDD. No gracias, las consultas complejas las hago en mi core de negocio, en mi lenguaje de programación favorito, y por que no, con mi DSL. Yo sólo quiero grabar, leer, y hacer unas cuantas consultas muy concretas, no cualquiera. Además, ¿cómo puede una BBDD ser más óptima en la ejecución de la consulta que mi código, hecho específicamente para mi problema? Es el típico caso de que una solución genérica a un problema no puede ser más eficiente que una solución específica para un problema específico. En este caso SQL es un lenguaje de query genérico, y no conoce los detalles específicos y posibles optimizaciones de mi aplicación, sólo puede usar técnicas generales.
  • Esquema. Estructura rígida y tipada de almacenamiento en forma de tablas con columnas ¿Para que quiero eso, si la estructura y el modelo y los tipos y validaciones del sistema está en el core OO? Realmente esto es un dolor, todos habréis sufrido con problemas de conversión de tipos entre columnas de tablas y campos de objetos, o entre objetos y tablas. Tal es el problema que nuestra aplicación termina haciendo uso de algún framework de mapeo objeto relacional, como Hibernate o iBatis o JPA, que añade otro grado de complejidad y configuración innecesario. Es innecesario porque la estructura de la información ya está en el modelo de objetos, no es necesaria replicarla en la capa de persistencia.

Si somos prácticos y aplicamos el principio KISS, vemos que una BBDD relacional puede ser, en muchos casos, algo que nos añade una complejidad innecesaria. Nos añade funcionalidades duplicadas con el core OO o simplemente innecesarias en algunos casos. Lo que ocurre es que el típico escenario de aplicación, el típico caso de uso, está cambiando respecto a lo que era antes. Cada vez tenemos más aplicaciones que tienen alguna o todas de las siguientes características:

  • Deben soportar grandes volúmenes de datos. Pensad en cualquier red social o en aplicaciones de Google, como el google maps.
  • Deben soportar altas cargas de transacciones online, debido a que el número de usuarios en la web puede ser enorme. De hecho cuantos más usuarios tengamos, los señores de negocio estarán más contentos.
  • El rendimiento no debe degradarse debido a los dos puntos anteriores.
  • ¡Que no se caiga! Hoy en día la caída e indisponibilidad de una aplicación tiene consecuencias monetarias muy fuertes.
  • En aplicaciones como las redes sociales, la consistencia no es crítica, con lo que las transacciones ACID no son necesarias.
  • ¡Fácil de usar! No quiero sufrir para simplemente grabar datos y hacer cuatro consultas básicas. No quiero saber nada de configuraciones de Hibernate o explains de Oracle.
  • ¡Fácil de integrar con un core OO de negocio!

Como vemos en muchos tipos de aplicaciones, las bases de datos relacionales te ofrecen capacidades que no te interesan a cambio de complicarte la vida. Por otro lado puedes tener muchos problemas con los requisitos de rendimiento, escalabilidad y disponibilidad. Para solucionar esos problemas vas a necesitar expertos, máquinas y productos normalmente caros. Lo peor que es que dichos expertos pueden coger una posición de poder y empezar a imponer sus reglas. Lo ideal es que las reglas las ponga el cliente, no un grupo de sacerdotes de una oscura tecnología.

Hasta ahora por tradición y por moda (o por imposición) cada vez que se nos planteaba la necesidad de persistir información elegíamos una BBDD relacional de forma automática y sin pensar. Como la integración entre un core OO y la BBDD relacionales es complicada, la gente llegó a varias soluciones (no necesariamente excluyentes):

  • No usar un core OO de negocio. Me hago una aplicación igual que la que hacía en COBOL pero traducida literalmente a JAVA.
  • Uso un Grails o un Rails, me acoplo al esquema de la BBDD y ya está.
  • Uso mi framework de mapeo objeto relacional (Hibernate, iBatis, etc),  e invierto esfuerzo en configurarlo y optimizarlo.

Esto es un razonamiento torticero, como tengo que usar BBDD relacionales entonces me complico la vida o paso de la OO. Es decir, me creo problemas “artificiales”, que no existían originalmente, porque elijo no solucionar el problema original (la persistencia) de forma óptima, sino hacer según manda la tradición y la moda. Lo lógico es analizar que tiene que hacer tu sistema de persistencia y después decidir qué usas. Puedes terminar usando una BBDD relacional si realmente la necesitas, pero en la mayoría de los casos os daréis cuenta que no os hace falta. Este es precisamente el punto de partida del movimiento noSQL.

Resumiendo, el movimiento noSQL, entre otras cosas, te propone:

  • No tienes porque usar una BBDD relacional, tienes alternativas para la persistencia de tu modelo de objetos.
  • No te hace falta un lenguaje de triggers ni de consultas complejas ya que la lógica de negocio está en la aplicación, no en el sistema de persistencia.
  • No necesitas un esquema rígido en tu sistema de persistencia, ya tienes tu modelo de objetos. Puede que incluso trabajes con un lenguaje de programación de tipado dinámico (js, ruby, etc). Simplemente quieres persistir objetos enteros o documentos. La estructura exacta de lo que guardes no debe ser de la incumbencia del sistema de persistencia, sino de tu modelo de negocio.
  • Te vale una API sencilla y un modelo de consulta sencillo. Uno muy popular es el clave valor, o tabla hash persistente. Guardas objetos o documentos y los asocias a una clave para poder recuperarlos.
  • Puedes prescindir de ACID para conseguir alta disponibilidad, escalabilidad y bajos tiempos de respuesta.
  • No odies SQL, simplemente debes saber cuando usarlo y cuando no. Hay casos en los que es la mejor solución, pero no en todos, y creo que cada vez en menos.

En siguientes posts hablaré sobre que sistemas de persistencia noSQL son más usados, su paradigma de desarrollo y como podemos eliminar, en muchos casos, la necesidad de transacciones ACID y el 2PC, con el objetivo de aumentar la disponibilidad y escalabilidad. Iré mezclando posts de noSQL con los de estructuración de equipo para que haya variedad.

Read Full Post »


Lo cierto es que una de las novedades que se introdujeron en la JDK5 que han causado mayor impacto son las anotaciones. Ahora la mayoría de los frameworks e incluso estándares JSR hacen un uso intensivo de las anotaciones, son algo que esta de moda. Están tan de moda que a veces las podemos llegar a usar de forma incorrecta sin darnos cuenta. Al fin y al cabo se usan por todos lados, y si algo se usa mucho entonces es que esta bien, ¿verdad? Debemos descubrir cuando usarlas y cuando no, y de eso precisamente, hablo en este post.

Ciertamente las anotaciones resultan muy útiles y nos proporcionan una herramienta muy potente a los diseñadores de frameworks. La finalidad de las anotaciones es sencilla: permitir al programador definir metainformación con la que decorar métodos, clases, paquetes, campos y argumentos. Sí, sí, pero, ¿que es esto de la metainformación? ¿Que tiene todo esto que ver con mi JPA? La idea es que el programador puede querer añadir información que pertenece al dominio de la aplicación a un método o clase. Si estamos escribiendo un framework de persistencia, será muy útil definir qué clases son persistentes y que propiedad de la clase usar como clave primaria. Fijaros que en este caso, el del framework, el dominio de negocio es la persistencia. Si usáramos un framework de negocio el dominio de negocio seria otro.

Veamos mas en detalle el ejemplo del framework de persistencia (al fin y a al cabo algunos piensan que con tener la persistencia solucionada la aplicación ya esta hecha al 50%, solo le faltarían las pantallas). En dicho framework necesitamos indicar cuales son las clases que representan conceptos persistentes y que propiedad usar como identificador, ese es nuestro dominio de negocio.

¿Como lo haríamos antes del advenimiento de las anotaciones? Simplemente tendríamos un maravilloso ficherito XML (esa moda moribunda) donde haríamos un mapeo indicando que clases son persistentes y que campo usar de identificador. Sin embargo pronto se vio que este enfoque es bastante frágil y propenso a errores. ¿Que ocurre si cambiamos el nombre de la clase? ¿O del campo? ¿Ese nombre era con mayúsculas o minúsculas? ¿Cometí un error tonto al escribir el nombre? Mantener la consistencia entre las clases y campos y el fichero XML puede ser una tarea compleja si el proyecto tiene un tamaño decente. El problema principal está en que información importante respecto a las clases se mantiene en un fichero distinto del código fuente. Esto genera el problema de sincronizar y mantener la consistencia entre ambos ficheros. Por otro lado una persona que lea la clase no sabrá si es persistente o no, con el consiguiente problema de mantenimiento. No contentos con esto, muchos IDEs no poseen capacidades de refactorización que tengan en cuenta el fichero XML de nuestro framework de persistencia favorito.

Debido a estos problemas se importó la idea de las anotaciones desde la plataforma .NET a JAVA. Ahora el diseñador del framework puede eliminar ese fichero XML tan molesto y definir anotaciones para su dominio de negocio. Supongamos que define las anotaciones @Persistente e @Identificador.  Solo tenemos que anotar nuestro código. Solucionamos el problema de raíz al eliminar el fichero XML. Ademas tenemos junto al código fuente nuestra metainformación de persistencia en forma de anotaciones lo que aumenta la mantenibilidad. El framework ahora en vez de leer el XML, lee dicha información directamente desde la clase. Esta lectura y procesamiento de anotaciones se puede hacer en tiempo de compilación, de carga de clases, o mediante reflexión, dependiendo de como hayamos definido las anotaciones y de la sofisticación de nuestro framework. Las anotaciones definidas para ser procesadas en tiempo de compilación son usadas por IDEs y scripts de compilacion. Las accesibles en runtime por reflexión y en tiempo de carga son útiles usadas sobre todo por frameworks. El código de una supuesta clase persistente quedaría así:

// Declara las instancias de esta clase como persistentes
@Persistente
public  class Person implements Serializable {
// Declara esta propiedad como identificador
 @Identificador
 private Integer id;

 private String name;

 public Integer getId() {
 return id;
 }

 public void setId(Integer id) {
 this.id = id;
 }

 public String getName() {
 return name;
 }

 public void setName(String name) {
 this.name = name;
 }
 }

Hasta aquí todo muy bien, ¿donde está la pega? La pega está en confundir metainformación con información de configuración. La información de configuración es aquella que cambia en función del entorno en el que se ejecuta la aplicación, o bien, no esta bajo nuestro control y puede cambiar en cualquier momento. En función de la maquina donde se ejecute, la base de datos, o el servidor de correo que usemos, la información de configuración puede cambiar, o simplemente el administrador puede decidir en cualquier momento cambiar ese nombre de usuario tan importante. Por eso dicha información se suele depositar en ficheros de configuración, para no tener que recompilar tu código cada vez que vayas a cambiar de entorno.  Hoy en día a nadie se le ocurre poner un usuario y contraseña a fuego en el código, ¿verdad? Tal vez estoy siendo demasiado optimista…

Volvamos a nuestro ejemplo de framework de persistencia para ver como esto nos puede llevar a la perdición. Supongamos que somos unos arquitectos muy listos y decidimos mejorar nuestro framework con una nueva anotación: @Tabla Como estamos a la última sabemos que las anotaciones pueden tener campos para ser parametrizadas. Decidimos pues que @Tabla recibirá el nombre de la tabla en la que se persiste el objeto y que en @Identificador podremos definir el nombre de la columna que contiene la clave primaria. Nuestro código quedaría así:

// Declara las instancias de esta clase como persistentes
// La persistencia sera mediante la tabla PERSONAS
@Persistente
@Tabla(tableId="PERSONAS")
public  class Person implements Serializable {
 // Declara esta propiedad como identificador, mapeado a columna PK
 @Identificador(columnId="PK")
 private Integer id;
 private String name;

 public Integer getId() {
 return id;
 }

 public void setId(Integer id) {
 this.id = id;
 }

 public String getName() {
 return name;
 }

 public void setName(String name) {
 this.name = name;
 }
 }

¿Que chulo verdad? FAIL !!!  Cuando instaleis esto en producción os encontráis con la desagradable sorpresa que por razones de nomenclatura los nombres de tabla son $entorno$_$tabla$ y la columna de la clave primaria siempre es $entorno$_$tabla$_PK ¡ Dios, que hacemos ! ¿Cambiamos el código y recompilamos, generando una versión para cada entorno? No señores, damos marcha atrás en nuestro cambio tan chulo y lo tiramos a la basura. Los nombres de las tablas y columnas de identificación hay que ponerlos en un fichero de configuración que cambia por entorno. Esto nos quita el problema de compilar pero no de reempaquetar para cambiar el fichero. Si queremos tener el mismo desplegable en los tres entornos, y ahorrarnos problemas con MAVEN y la gente de sistemas, lo que hacemos es pedir la URI donde se encuentre el fichero mediante JNDI, abrir la URI y procesar el fichero. Debemos además declarar una referencia JNDI a recurso URI. Cada entorno tiene un fichero distinto configurable por la gente de base de datos y en una URI distinta. Durante el despliegue la gente de sistemas mapea la referencia JNDI al fichero correspondiente al entorno y nosotros no nos damos ni cuenta. Existen otras formas de hacer esto, como por ejemplo jugar con el classpath y las librerías compartidas, pero eso en otro post.

Bien, si alguno leyó mi anterior post, puede invocar el poder de KISS para arrearme en “to la boca”. Cierto, si en tu proyecto hay ciertos parámetros externos a la aplicación que nunca van a cambiar puedes usar KISS y ahorrarte el fichero de configuración y poner estos parámetros a fuego en tu código mediante anotaciones. Normalmente esto de que hay parámetros que no cambian no me lo encuentro muy a menudo, ya que mis proyectos se mueven en un entorno con alta tasa de cambio e incertidumbre. Si es tu caso, enhorabuena, pero asegúrate bien antes, no tenga que decirte eso de “te lo dije…”. En el ejemplo anterior puede ser que realmente el nombre de las tablas lo decida el desarrollador (mapeo top-down) o simplemente no puedan cambiar por los siglos de los siglos.

El caso del mapeo top-down es interesante. Si el nombre de las tablas se puede decidir por parte del desarrollador JAVA, y no cambia con el entorno, podríamos invocar KISS y eliminar esa anotación @Tabla y el parámetro columnId de @Identificador ¿Como? Usando configuración por convención o nomenclatura. De esta forma el nombre de las tablas se deriva del nombre de las clases. Es un mapeo automático que nos evita la configuración, ya sea en forma de anotación o de fichero.

El confundir metainformación con configuración es tan común que muchos estándares JSR y frameworks lo han cometido. El clamor por eliminar ficheros XML y usar las anotaciones era tal que no se pararon a pensar en lo que hacían, ¿o tal vez sí? Si lo pensáis bien, los JSRs y los frameworks tienen que ser populares si quieren ser usados, y lo que te hace más popular es usar lo que esté más de moda, no lo mejor. El usar anotaciones de forma masiva, al ser una tecnología chula, hizo que esos JSR y frameworks se hicieran populares. Un truco muy hábil. O al menos eso es lo que mi mente me dice a veces cuando me pongo en modo paranoico.

Así que ya lo sabéis, usad las anotaciones, diseñad frameworks que las aprovechen, pero no caigais en la tentación de usarlas de cualquier manera y para cualquier cosa.

Read Full Post »

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 43 seguidores