Como no puedo atender temporalmente este blog como es debido, os dejo una encuesta para que os vayáis entreteniendo.
Por supuesto dejad los comentarios que queráis, yo vuelvo pronto.
Posted in encuesta, noSQL on 31/05/2010| 1 Comment »
Posted in dvcs, Modas on 24/05/2010| 3 Comments »
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:
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:
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:
¿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:
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.
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:
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:
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…
Posted in noSQL on 17/05/2010| 12 Comments »
Continúo con el tema del noSQL. Técnicamente, no se si realmente el uso de transacciones no ACID, entra en el movimiento noSQL, pero la verdad es que normalmente van de la mano. Además hay una identificación muy estrecha entre transacciones ACID y las bases de datos tradicionales (aunque esto no es necesariamente cierto).
Todos hemos aprendido desde chiquititos lo que son las transacciones ACID y lo bondadosas que son. Para refrescar un poco:
Esto es algo básico que se enseña en todas las universidades. Sin embargo no suelen enseñar en la facultad el teorema CAP (al menos a mi no). El teorema CAP viene a decir, de forma resumida, sólo puedes tener dos de las siguientes tres propiedades, pero no las tres a la vez:
El teorema CAP nos dice que no hay nada bueno, bonito y barato en el mundo de la persistencia, y que debemos reflexionar sobre que propiedades queremos escoger. Hay que enfatizar que no tiene que ser una decisión de todo o nada. Podemos sacrificar algo de consistencia en vez de toda la consistencia. También podemos sacrificar un poco de disponibilidad, que en la práctica es permitir que se degrade el tiempo de respuesta e incluso que algunos clientes pierdan disponibilidad por un tiempo limitado. O bien se puede decidir perder algo de tolerancia a fallos, que en la práctica significa tolerar una cantidad limitada determinada de fallos pero nada por encima de un límite.
La forma en que podemos conseguir tolerancia a fallos es tener muchos nodos de forma que si cae uno haya otro capaz de tomar el relevo. Si queremos un sistema tolerante a fallos y que además tenga consistencia absoluta, debemos replicar los datos entre los nodos cada vez que hagamos una escritura. Al escribir, todos los nodos deben replicarse y debemos esperar hasta que este hecho suceda para poder dar por terminada la operación. Esto como es normal hace que la operación sea tanto más lenta cuanto más nodos tengamos, lo que degrada el tiempo de respuesta y por lo tanto la disponibilidad. Otro efecto negativo es que aumenta la probabilidad de que la operación no se produzca con éxito, ya que debemos escribir en todos los nodos, y alguno puede estar caído, y para que la operación sea exitosa debe escribir en todos los nodos. La probabilidad de que al menos un nodo esté caído aumenta con el número de nodos totales del sistema, con lo que cuanto más nivel de replicación tenemos, más probabilidad de fallar en la operación y por lo tanto de falta de disponibilidad. Esto va en contra de la disponibilidad del sistema. Como contrapartida sólo necesitamos leer de un nodo, con lo que en lectura tenemos gran rendimiento, disponibilidad y tolerancia a fallos. Las bases de datos tradicionales suelen usar el enfoque anteriormente expuesto como mecanismo de tolerancia a fallos.
Si queremos aumentar la disponibilidad, la operación debe terminar antes, lo que implica que no podemos esperar a que se repliquen todos los nodos, sino sólo uno o quizás unos cuantos pero no todos. El hecho de que la operación termine antes de que todos los nodos se repliquen aumenta la disponibilidad pero a su vez degrada la consistencia al no tener todos los nodos la misma información. Así si un cliente lee de un nodo que pudo ser replicado no tendrá problema, pero si lee de un nodo que aún no ha sido replicado se producirá una lectura inconsistente con la escritura anterior. Normalmente los sistemas que permiten sacrificar consistencia por disponibilidad, tiempo de respuesta y tolerancia a fallos implementan un modelo de consistencia llamado eventually consistency (consistencia eventual). En sistemas con consistencia eventual, si bien al terminar una operación el sistema puede estar inconsistente, se garantiza que al cabo de un tiempo el sistema quedará en un estado consistente, es decir, que eventualmente se conseguirá un estado consistente. Cuanto más tiempo pase entre la escritura y la lectura más probabilidad habrá de que el sistema sea consistente. No se puede especificar un tiempo mínimo para que se produzca esta consistencia ya que el sistema puede sufrir algún fallo (congestión de red, caída de un nodo, etc).
El hecho de que podamos tener inconsistencia complica la lógica de los clientes ¿Cómo podemos detectar y resolver problemas de consistencia? Para explicarlo os pongo un escenario:
Aunque en el escenario me centro en la lectura, la inconsistencia también puede presentarse en la escritura, en el caso en el que intentásemos escribir en varios nodos, cada uno con una versión diferente. Como vemos, en sistemas con grados de consistencia no estricto, el cliente tiene la posibilidad de detectar inconsistencias, pero por contra le queda la papeleta de resolver el conflicto entre varias versiones. A algunos les parecerá esto una torpeza, ¡ pero a mi me parece genial ! Esto quiere decir que la resolución de conflictos queda en mano de reglas de negocio en vez de en algoritmos genéricos (que es lo que en realidad hacen las bases de datos tradicionales de forma encubierta). Tal vez nos dé más trabajo programar estas reglas de negocio, pero como son específicas para cada situación de nuestro sistema, seguro que son más eficientes. Algunos algoritmos típicos:
Bueno, espero que el cuerpo se os haya quedado bien.
Otro hecho interesante de este tipo de sistemas es que podemos especificar un grado de consistencia diferente a las operaciones de lectura y escritura. Esto nos permite optimizar el rendimiento de las escrituras o de las lecturas en función de lo que nos interese.
Obviamente podemos tener una vía alternativa: sacrificar la tolerancia a fallos y tener sólo un nodo. De esta forma sólo se necesita escribir y leer del único nodo del sistema, con lo que conseguiremos consistencia y disponibilidad mientras el nodo no se caiga. Ni que decir tiene que estos sistemas tienen tolerancia a fallos cero. Realmente este es el enfoque clásico de una base de datos tradicional sin tolerancia a fallos. Es interesante que este enfoque no está totalmente libre de la degradación del tiempo de respuesta o de la disponibilidad aunque no se haya producido ningún fallo. Pensad en lo que ocurre si dos clientes quieren ejecutar dos transacciones concurrentes que afectan al mismo dato. En este momento se produce un conflicto. Si forzamos ACID estricto, podemos invocar la propiedad de aislamiento transaccional y decidir serializar las transacciones ya que intentan acceder al mismo dato (en BBDD relacionales, la misma fila). Para ello se suele implementar un mecanismo de bloqueo a nivel de fila, aunque también es típico bloqueo a nivel de tabla. Bien este mecanismo se suele llamar concurrencia pesimista, y puede llevar a la degradación del tiempo de respuesta o incluso a la indisponibilidad del sistema con alta carga de transacciones concurrentes. Recordad que esto está pasando en un sistema con un único nodo, es decir con tolerancia a fallos cero. Los fabricantes de base de datos lo saben y por ello te permiten relajar la consistencia mediante una disminución en el grado de aislamiento de las transacciones. Sin entrar en detalles, diré que si relajamos el grado de aislamiento, podemos usar concurrencia optimista, que consiste en no bloquear filas, y al escribir comprobar si la versión que está en la BBDD es la misma que la versión que leímos al principio de la transacción. En caso de que sea así no hay conflicto. En caso de que sean distintas versiones se produce un conflicto y hay que solucionarlo. Los algoritmos para solucionarlos son exactamente los mismos que los expuestos anteriormente: fallar, sobrescribir, fusionar automáticamente o fusión manual por parte del usuario. Como veis si tenéis un sistema de persistencia tradicional, no tolerante a fallos, y queréis escalar, tenéis que bajar el aislamiento transaccional y usar concurrencia optimista, lo que os lleva también a la necesidad de gestionar conflictos e inconsistencias en la aplicación.
El objetivo de diseño de un sistema tradicional siempre ha sido favorecer la consistencia sobre las otras propiedades del sistema. Esto nos lleva al dilema de o bien sacrificar la disponibilidad o la tolerancia a fallos. Es decir si queremos un sistema disponible y consistente, que responda a todos los clientes en un tiempo razonable, no puede ser tolerante a fallos y por lo tanto no se puede caer ningún servidor o línea de comunicación. Si tenemos un sistema tolerantes a fallos y consistente, y se produjera un fallo de red o la caída de una máquina, el sistema seguiría funcionando, pero o bien el tiempo de respuesta se degradaría o algunos clientes perderían el servicio temporalmente. Estos compromisos entre disponibilidad y tolerancia a fallos pueden ser aceptables en aquellas aplicaciones donde la consistencia es crítica, y el coste de un fallo en la consistencia es mayor que el coste de estar un tiempo sin servicio o que el sistema responda muy lentamente.
Sin embargo en algunos escenarios esto no es aceptable. Por ejemplo en una tienda online, la perdida de servicio es desastrosa, ya que equivale a cerrar completamente el negocio y es mucho más costoso que una inconsistencia puntual de vez en cuando. También es costoso que el usuario de una tienda online perciba un tiempo de respuesta muy lento o que no pueda entrar de vez en cuando. En estos sistemas se requiere una tolerancia a fallos y disponibilidad a toda costa, con lo que se relajan los requisitos de consistencia. Es una pura decisión práctica guiada por las fuerzas de mercado.
En un sistema que deba ofrecer información en tiempo real, la degradación del tiempo de respuesta es catastrófica ya que hace que el sistema deje de ser útil. Por lo tanto o se sacrifica la consistencia o la tolerancia a fallos. En el caso de que el sistema sea crítico se sacrifica la consistencia.
Normalmente en el negocio de los sistemas web a gran escala lo que realmente se necesita es disponibilidad, bajo tiempo de respuesta y tolerancia a fallos. Google, Amazon o eBay no pueden permitirse estar caídos. Twitter, Facebook o LinkedIn pueden tolerar grandes grados de inconsistencia pero no una caída o una degradación del rendimiento. No es de extrañar que éstas empresas hayan abandonado total o parcialmente el modelo de bases de datos tradicionales con transacciones ACID, y hayan investigado en sistemas de persistencia alternativos.
En este apartado merecen una mención especial Dynamo de Amazon y Cassandra (de Apache y facebook). Este último está basado en el propio Dynamo mezclado con BigTable (Google). En Cassandra puedes especificar al leer o escribir el nivel de consistencia que quieres tener en la operación. El nivel de consistencia más bajo es 1, que indica que al leer o escribir te basta con completar la operación en uno de los nodos. También puedes realizar operaciones con máxima consistencia, donde todos los nodos deben replicarse al escribir o bien responder en la lectura. Por último tiene un modo intermedio de consistencia llamado de quorum, donde se busca mayoría simple (la mitad más uno) para considerar una operación completada. Además tanto Dynamo como Cassandra implementan consistencia eventual y tolerancia a fallos mediante una arquitectura distribuida que no necesita de máquinas especiales. Basta con poner una buena cantidad de PCs para tener un sistema muy robusto. Ellos claman que no se necesitan procedimientos de instalación y explotación complejos pero dada mi falta de experiencia con estos productos en este sentido no os lo puedo asegurar.
Así que ya sabéis, si necesitáis un sistema a prueba de bombas, que esté siempre disponible con tiempos de respuesta decentes, y que escale a volúmenes de carga grande (decenas o cientos de miles de usuarios), id pensando en sacrificar consistencia y pasaros a la consistencia eventual, pero, ¡ apretaos los cinturones de seguridad !
Posted in Agile, Lean, Metodologías, team on 10/05/2010| 3 Comments »
¡ Oh no ! Otro post infumable sobre equipos de desarrollo… Si señores y señoras vuelvo a la carga con este tema que considero de importancia vital, no os preocupéis creo que es el último de la serie. Considero importante este tema porque si no tenemos un equipo bien estructurado lo tendremos muy difícil para tener éxito en nuestros proyectos.
En mi anterior post sobre equipos comenté que una estructuración plana y democrática era la más ágil y óptima para el desarrollo de software. Sin embargo al crecer en tamaño dicha estructura se colapsa debido a la sobrecarga de información de cada miembro del equipo. Además para explotar las ventajas de tal estructura de equipo lo mejor es que estén todos colocados en la misma sala. Esto nos lleva a un problema, ¿qué hacemos si necesitamos un equipo muy grande (más de 9)?¿Qué hacemos si no tenemos a todos lo miembros del equipo colocado? En estos casos necesitamos estructurar el equipo de forma diferente. Veamos alternativas.
La primera tentación es montar una jerarquía. Ciertamente las jerarquías disminuyen la cantidad de lineas de información que llegan a una persona, lo que permite que esta se sature menos si el equipo es grande. Desde este punto de vista los equipos organizados en jerarquías escalan bien con el tamaño. De hecho el número de lineas de comunicación crece mucho más despacio (logarítmicamente) que con equipos planos (crecimiento lineal). Otra ventaja es que la toma de decisiones la toma el jefe, lo que permite mayor velocidad de decisión que la basada en consenso. Es la forma tradicional de organizar los equipos. Todo parece bonito pero… esta estructura ¡no es ágil! y además ¡ es frágil ! Veamos:
Evidentemente este modelo radical de jerarquía no suele aplicarse. Es más común el tener pequeños grupos planos que están coordinados por un jefe. Esto disminuye un poco el problema pero no lo elimina por completo, sobre todo si tenemos en cuenta que los equipos planos están en la base, y que es más raro encontrar equipos planos de jefes.
Otra forma típica de estructurar un equipo es alrededor de especialistas. Se suelen tener grupos de especialistas, donde cada grupo es experto en un componente del sistema o en una tarea o actividad. La idea detrás de esto es que los especialistas son muy eficientes en realizar unas actividades en concreto, por lo tanto la forma más eficiente de organizar un equipo es que cada individuo o grupo haga lo que mejor sabe hacer, su especialidad. De esta forma el rendimiento del equipo de desarrollo será óptimo, ¿verdad? Pues no, ¡ FAIL ! Veamos el siguiente dibujo ilustrando un equipo basado en especialistas:
Como se ve desde que una historia de usuario se extrae del product backlog hasta que se completa y reporta un valor al cliente (y dinero para nosotros), ésta pasa por… ¡un montón de intermediarios! Cada intermediario (especialista) de nuevo añade un posible punto de encolamiento («el otro equipo está tardando mucho en darme trabajo, yo no tengo la culpa….») y de malentendidos («pero suponíamos que la interfaz entre el componente A y el componente B era así, no nos la explicásteis bien»). El hecho de que cada grupo sólo entienda de su especialidad agrava los problemas, ya que provoca entre otras cosas:
Como hemos visto la jerarquía y la organización en grupos de especialistas genera intermediarios y colas que son fuente de múltiples problemas. Por cada intermediario se produce un retraso, con lo que la información llegará más anticuada al receptor. Por cada intermediario hay una cierta de corrupción de la información y malentendidos, con lo que la información se perderá por el camino y lo que llegue al receptor no puede no ser lo suficientemente veraz o preciso. Si encima cada intermediario tarda una cantidad diferente y no predecible de tiempo en procesar la información se producirán encolamientos. Si quieres un equipo ágil, evita intermediarios. Las colas son también fuentes de ineficiencias. Por cada cola tenemos trabajo a medio hacer, parado que no genera valor. Por cada cola se retrasa el feedback, haciendo que un montón de posibles defectos y problemas estén escondidos, a la espera de aparecer en el momento más inoportuno y cuando todo el mundo está ya «en otra cosa». Si quieres ser lean debes evitar las colas.
Lo más eficiente es organizar los equipos grandes de forma que se minimicen el número de intermediarios y colas. Para esto tenemos dos armas: la autogestión y los equipos multidisciplinares. Veamos algunos consejos generales en este sentido:
Si implementamos estos consejos podemos montar un equipo estructurado en base a «feature teams». En este tipo de estructura cada grupo o «feature team» se encarga de realizar una historia de usuario. Veamos el siguiente dibujo:
Como vemos eliminamos las colas. Cada equipo es multidisciplinar y tiene su propio Product Owner. De esta forma es capaz de coger una historia de usuario e implementarla sin necesidad de pasar por otros grupos. Como cada equipo es plano, éste se comporta de forma ágil y rápida. En la figura hay cuatro equipos, cada uno de ellos en su propia sala. Los equipo que pertenecen a «Color Features» están en el mismo edificio porque cogen historias de usuario que tienen bastante relación entre si, son funcionalmente próximas, por lo tanto los dos equipos necesitan comunicación ágil y por lo tanto proximidad. De la misma manera «Alpha Features» está en otro centro de trabajo y se estructura de forma similar. El uso de autogestión y representantes de equipos disminuye mucho la necesidad de coordinadores. Sin embargo siempre es bueno es tener varios scrum masters itinerantes o coachers que se dediquen a resolver dudas, aconsejen a los equipos, desatasquen potenciales problemas globales o arbitren en disputas «irresolubles». Estos «coachers» no serían coordinadores sino facilitadores y maestros. Tampoco es mala práctica que además del representante de equipo se reúnan periódicamente los product owners de cada grupo para mantener una linea de coherencia estratégica del producto.
Un hecho interesante es que como cada historia de usuario requiere de varias tareas o componentes para ser completadas, cada grupo debe trabajar sobre distintas áreas de código. Esto hace que todos los grupos tengan una responsabilidad compartida sobre todos los componentes de código de la aplicación. De esta forma a todos los grupos le convienen que todos los componentes de código estén hechos con una calidad y mantenibilidad decentes, porque te puede tocar usarlos o modificarlos para la siguiente historia de usuario. Contrasten este efecto con lo que ocurría con los especialistas, cada especialista sólo se preocupa de su componente y no de los demás. El hecho de que varios grupos modifiquen simultáneamente las mismas zonas de código nos genera un problema de gestión. Este problema se soluciona con varias prácticas del agilismo:
Finalmente, todo esto funciona porque los miembros de tu proyecto que necesitan una comunicación más rápida y ágil son los que están implementando una misma historia de usuario. Dos personas implementando dos historias de usuario distintas no tienen tanto acoplamiento y necesidad de intercomunicación como dos personas que están trabajando sobre la misma historia de usuario. De hecho el acoplamiento entre dos «feature teams» se produce sobre todo a través de la base de código compartido. En este caso dicho acoplamiento se puede optimizar mediante TDD e integración continua con lo cual no es necesario una intercomunicación tan frecuente. Dicho esto aprovecho para introducir una práctica muy necesaria en este escenario, pero tenida por no ágil: la documentación. Como vemos la comunicación se realizará de forma más intensa por código entre dos «feature teams» distintos, por lo que además del TDD y la integración continua es necesario reforzar el grado de legibilidad del código, y eso se hace precisamente con una buena documentación. Recordad que el agilismo no prohíbe la documentación, sólo dice que documentes lo justo y necesario. En proyectos grandes, la cantidad de documentación justa y necesaria es mucho mayor que en un proyecto pequeño. Si no vas a implementar TDD, integración continua y no vas a mantener una buena documentación, tus «feature teams» no podrán coordinarse con efectividad y tu proyecto fracasará.
Como conclusión: es posible gestionar proyectos grandes usando agilismo aunque nuestro equipo se muy grande o esté distribuido. Ciertamente no es el caso óptimo pero la realidad manda. Para afrontar esto hay que estructurar tu proyecto en múltiples equipos pequeños, colocados en la misma sala, multidisciplinares y autogestionados, donde cada equipo implementa una historia completa de usuario detrás de otra. Como sacrificio podemos hacer que cada equipo pueda estar en su propio centro de trabajo, pero la menor interacción entre dos «feature teams» hacen que el TDD, la integración continua y la documentación sean vitales. Como prácticas a evitar están la de estructurar en grupos de especialistas y minimizar el grado de jerarquía.
Posted in Filosofada, Modas, noSQL on 03/05/2010| 14 Comments »
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:
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:
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):
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:
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.