Quantcast
Channel: Adictos al trabajo
Viewing all 990 articles
Browse latest View live

MVP, MMP, MMR, MMF: 4 «emes» para gobernarlas a todas

$
0
0

Tres Anillos para los Reyes Elfos bajo el cielo. […]

No, perdón, esto no iba aquí.

Una “eme” para el mínimo producto viable de nuestro producto.
Otra para el mínimo producto comercializable entre nuestros usuarios.
Otra “eme” para una mínima versión comercializable que amplíe la funcionalidad.
Una para la funcionalidad que aporta valor.
En la definición del backlog de producto donde se extienden las Sombras.

Las “emes” para gobernarlas a todas, para encontrarlas,
para agruparlas a todas y atarlas al producto
en el backlog de producto donde se extienden las épicas y las historias de usuario.

Índice

  • Introducción
  • Conclusiones
  • El método
  • Paso 1: Identificar las fuentes
  • Paso 2: Analizar cada concepto y hacerse una composición de lugar
  • Paso 3: Ampliar la visión más allá de los conceptos explícitos
  • Paso 4: Elementos adicionales y aplicación de la experiencia

Introducción

Hace poco entre los coaches de Autentia surgió una discusión en cuanto a las agrupaciones funcionales de un producto, concretamente el MVP (minimum viable product) y las MMF (minimum marketable feature) ya que había diferentes aproximaciones y era algo que se repetía en nuestros clientes.

Tengo que agradecer a mis compañeros Jeselys y Javi, que cada uno me aportara una visión diferente, ya que me hizo plantearme analizar con más detalle -del que originalmente consideraba- el solapamiento entre conceptos y que finalmente ha dado como fruto esta entrada. ¡Gracias chicos!

En nuestro día a día es fácil hablar de Épicas, Historias y Releases, pero cuando nuestros productos adquieren dimensiones épicas (y por eso la intro tipo El Señor de los Anillos), podemos apoyarnos en los siguientes elementos adicionales que nos ayuden a agrupar y gestionar la funcionalidad:

  • MVP – Minimum Viable Product
  • MMF  –  Minimum Marketable Feature
  • MMP  –  Minimum Marketable Product
  • MMR  –  Minimum Marketable Release

OJO!!! No siempre son necesarios y debemos tener cuidado de no introducirlos si no tienen sentido porque lo único que harán será añadir complejidad.

Con estos elementos lo que ocurre, como en tantas otras ocasiones, es que los términos se han usado de forma inconsistente a lo largo de tiempo, a lo que hay que sumar que su significado varía dependiendo del dominio de utilización. Es decir, no significa lo mismo hablar de MVP con una persona de negocio (que busca confirmar su idea con un público real para seguir invirtiendo), que con un ingeniero (que busca resolver una incertidumbre técnica que haga viable un desarrollo).

En las primeras búsquedas rápidas que hice sobre los términos a partir de nuestra discusión interna no conseguí llegar a una conclusión clara, buceé entre blogs, glosarios, infografías…, entradas puntuales que no me daban “garantías”. Llegaba al mismo punto que durante nuestra conversación en Autentia. Esto me llevó a plantearme dedicar algo más de esfuerzo y establecer un método para llegar a una solución y homogeneizar los criterios que usábamos al respecto como equipo.

 

Conclusiones

Como muchos querréis ir, como dirían los Chilenos «al tiro», lo primero que os dejo es el escenario final al que nosotros hemos llegado; vamos el resumen ejecutivo con las definiciones que utilizamos. ;).

Si tienes un interés por profundizar y comprender mejor los resultados, te invito a leer más allá de las conclusiones del post y ver cómo hemos llegado hasta aquí con algún ejemplo visual.

  • MVP – Minimum Viable Product, debe estar enfocado a realizar una «prueba de concepto» o experimento, y mediante ésta validar una hipótesis o adquirir un conocimiento (usuario, negocio o técnico).
  • MMF  –  Minimum Marketable Feature, son las características mínimas con las que debe contar una funcionalidad para poder ser entregada a los usuarios finales.
  • MMP (o MMR1)  –  Minimum Marketable Product, es la agrupación de varias funcionalidades o MMFs que entregamos en la primera release. Es lo que comúnmente se confunde con MVP.
  • MMRx  –  Minimum Marketable Release, releases posteriores al MMP o MMR1 que contienen funcionalidad. Pueden contener o no MMFs.

Como podemos ver, se trata por tanto de conceptos muy próximos y que pueden llevar a confusión, por ello creo que es una buena práctica el intentar aproximarse a través de otros elementos como son las Épicas, Historias, Spikes y Chores.

Imagen que representa las agrupaciones funcionales completas con MVP, MMP, MMR y MMF
Agrupaciones funcionales completas con MVP, MMP, MMR y MMF

 

Como resumen y siguiendo la imagen anterior, podríamos decir que una MMF es la parte de una épica que aporta mayor valor y que puede ser considerada con funcionalidad mínima.

Una MMF debe entregarse de forma íntegra en una misma versión, mientras que una épica puede desarrollarse a lo largo de varias versiones hasta completar toda su funcionalidad.

Una MMF puede estar constituida por una o más historias de usuario (u otro tipo de item).

Una Historia de usuario podría ser una MMF, pero considerando siempre que MMF tienen que ser entregable al usuario, si hablamos de una única historia y esta no aporta  por sí misma suficiente al usuario como para entregarla de forma independiente, no podremos decir que es una MMF.

NOTA: Recordad el punto más importante, no introduzcamos elementos complejos que no sean  necesarios a nuestros equipos o en nuestras organizaciones. Recordad el principio de simplicidad.

 

El método

Ahora sí, si estás aquí es que te interesa profundizar más en cómo hemos llegado a esta definición que nos ayuda en nuestro día a día. Sin volvernos locos, el método de trabajo que se ha seguido ha sido:

  1. Realizar búsquedas rápidas para intentar identificar las acepciones más utilizadas.
  2. Identificar las fuentes reconocidas de definiciones explícitas de los términos y el contexto en el que se producían (hablamos de MVP y MMF).
  3. Analizar y comparar mis conclusiones con las que había identificado en las búsquedas rápidas.
  4. Contrastar con nuestra experiencia y nuestro modelo de trabajo habitual.
  5. Poner en común con el equipo y llegar a un acuerdo.
  6. Compartir las conclusiones.

 

Paso 1: Identificar las fuentes

A partir de las búsquedas rápidas que mencionaba antes, establecí una red de referencias que llevaba a las mismas fuentes (esto lo cito para que cualquiera pueda reproducirlo y no sea una mero comentario como aquellos por los que he tenido que navegar yo). Podemos decir que se reducían a tres, dos sobre el MVP y una sobre la MMF:

  1. MVP ha sido acuñado en el libro Lean Startup por Eric Ries, y por otro lado Jeff Gothelf y Josh Seiden también hacen uso de él en el libro Lean UX.
  2. MMF ha sido establecida por Mark Denne y Jane Cleland-Huang en el libro Software by Numbers.

Hay bastantes referencias también al método IFM, The incremental funding method: data-driven software development (Jane Cleland-Huang), donde se asocia el «value» con los aspectos económicos de un producto como el ROI o el NPV. Para no complicarlo más, por el momento vamos a dejar expresamente este aspecto un poco al margen.

 

Paso 2: Analizar cada concepto y hacerse una composición de lugar

Una vez obtenidas estas referencias de las fuentes, me he ido a ellas para consultar las definiciones:

Lean UX – PMV y experimentos (Capítulo 5) o Minimum Viable Products and Prototypes (hay modificaciones en la segunda edición del libro). Literalmente (os recomiendo hacer la lectura original para extender un poco la idea de MVP y los tipos que existen) me quedo con:

» […] When it comes to creating an MVP, the first question is always what is the most important thing we need to learn next? In most cases, the answer to that will either be a question of value or a question of implementation. […] «

Sintetizando, el MVP puede tomar dos vertientes:

  • – PoC: Prueba de concepto, es un esfuerzo del equipo en probar unas hipótesis o adquirir unos conocimientos necesarios antes de avanzar en el producto (rápido y “barato”). Puede ser algo tan sencillo como crear una página web «tonta» que presenta información del producto para ver métricas de leads que tengan potencialmente interés. No necesariamente tiene que haber funcionalidad construida.
  • – PMV: Producto mínimo viable, que es cuando se libera un mínimo de funcionalidad construida (una unidad “comercializable” sea de pago o no). Puede ser una versión muy reducida que busca explorar sobre qué características tenemos que trabajar porque son las que más aceptación tienen entre nuestros clientes.

Software by Numbers – Software Development after dot.com (Capítulo 1)

Definición literal de MMF:

» […] We’ve spoken earlier about the idea of software development as a value creation activity. In this section we give components of value the ability to be referenced and posit the existence of minimum marketable features (MMFs). MMFs are units of software value creation. They represent components of intrinsic marketable value.

[…] What is particularly unique about software products is that the features are often, or even usually, separately deliverable. In other words, a complex software application can have value to a user even if it is incomplete. Indeed, it is often claimed that software is by its very nature always incomplete!

By carefully choosing the way in which software components are assembled, we can create identified units of marketable value well before the application is anywhere near completion. […] «

Paso 3: Ampliar la visión más allá de los conceptos explícitos

Además de los párrafos anteriores, he revisado el resto de menciones y los capítulos completos de los libros, en ningún sitio limita el uso del MVP, pudiéndose usar un MVP asociado a una funcionalidad o a un grupo de ellas, si pensamos sólo en una es donde comienza el solape con el MMF, ya que podríamos considerar estar hablando de lo mismo.

Es importante aclarar en este punto que también podría solaparse con el concepto de historia de usuario.  Una MMF debe tener un volumen de funcionalidad necesario como para ser un entregable al usuario, mientras que, una historia de usuario no necesariamente.

Volviendo a revisar la definiciones que comentaba al inicio (MMP y MMR), podemos llegar a la conclusión que puede existir solape entre la primera MMR (que puede recibir la denominación de MMP) y un MVP; una primera MMR puede ser únicamente el MVP.

Es decir, virtualmente podríamos no encontrar diferencia entre MVP, MMP, MMR y MMF si sólo afectase a una feature que fuese auto contenida, es decir, que podemos entregar como una primera funcionalidad (desarrollada o fake) al usuario final para conseguir un aprendizaje.

¡Vaya jaleo!, parece que todo sirve para todo…

 

Paso 4: Elementos adicionales y aplicación de la experiencia

Pero… ¿entonces cómo salimos de este lío de nombres y agrupaciones? ¿Cómo podemos enfocarlo de una forma simplificada que se pueda contar fácilmente?.

Para ayudar a deshacer este entuerto, hemos metido más elementos en la ecuación, las historias Épicas y las historias de usuario. Al contrario de lo que podría pensarse, introducir estos elementos nos ayuda a establecer un gobierno.

Para ayudar a digerir el texto, vamos a secuenciar los conceptos con un gráfico que nos ayude “cronológicamente” (entre comillas por que no es del todo exacto, es una referencia dado que la casuística puede ser infinita). Vamos a considerar la cajas verticales, versiones o entregables y los MMF como bloques de funcionalidad que pueden ir asociados a ellas.

Imagen que representa una aproximación "cronológica" a las versiones para ayudar a comprender mejor los conceptos MVP, MMR, MMP y MMF.
Aproximación «cronológica» a las versiones para ayudar a comprender mejor los conceptos MVP, MMR, MMP y MMF.

 

Vale, si hemos dicho que una MMF es un bloque de funcionalidad, ¿cómo las encajamos como Épicas e Historias de usuario?

Vale, en primer lugar volvemos a nuestra jerarquía básica: Épicas, Historias y Subtareas. Vamos a establecer que una épica es aquella historia demasiado grande como para realizarla en una iteración de trabajo, es decir, es necesario descomponerla en Historias de usuario (HdU) que sí podamos acometer.

Imagen que muestra una jerarquía de Épicas, Historias y subtareas.
Jerarquía de Épicas, Historias y subtareas.

Cuando hacemos esto es muy normal que pasen dos cosas al descomponer la historia épica (puedes ver técnicas de descomposición aquí):

  1. Que algunos elementos sean “poyaques” es decir no sean estrictamente necesarios como funcionalidad mínima indispensable.
  2. Que algunas historias sean funcionalidades auto contenidas que no requieran de otras para aportar valor real al usuario final.

Vamos con un ejemplo, tenemos la Épica1 que se descompone en 7 Historias de usuario:

Imagen que representa un post-it grande con una épica y otros 7 post-it más pequeños por debajo que se corresponden con historias de usuario
Épica con siete historias de usuario por debajo

 

Pero de estas 7 historias:

  • 3 suponen la funcionalidad mínima indispensable que podemos entregar al usuario final (o comercializar). Por lo tanto podemos agruparlas como la MMF de la Épica 1.
  • 2 son historias que habíamos introducido con el concepto “pues ya que estamos” («poyaque») y que al analizarlas realmente no forman parte de la MMF.
  • 2 son historias auto contenidas y que no pertenecen realmente a la Épica 1.

Si representamos visualmente nuestra funcionalidad quedaría organizada más o menos así:

Imagen que muestra una Épica con MMF e historias representadas.
Épica con MMF e historias representadas.

Ahora volviendo a nuestro modelo cronológico, vamos a suponer que una de nuestras historias de usuario necesita aclarar una incertidumbre tecnológica porque no tenemos información suficiente para abordarla o ni siquiera para estimarla y puede poner en riesgo nuestro producto.

Imagen que representa una historia que genera un Spike.
Historia que genera un Spike.

Deberíamos generar un Spike que nos ayude a resolver esta incertidumbre, a realizar esta prueba de concepto (PoC).

Imagen que muestra un Spike derivado de una historia de una épica.
Spike derivado de una historia de una épica.

Ahora que tenemos clara cuál es la funcionalidad mínima que necesitamos en una primera instancia, podríamos considerar entonces que nuestro MVP se compone de:

Imagen que muestra la agrupación de elementos bajo un MVP
Agrupación de elementos bajo un MVP
  • Una MMF, a través de la cual es necesario presentar 3 aspectos críticos a validar con los usuarios finales (3 historias). La tecnología existe y está probada, pero no sabemos cómo va a aceptar el usuario nuestra solución.
  • Un Spike, o prueba de concepto, que requiere construir cierta funcionalidad. La tecnología es novedosa y desconocemos si su aplicación a nuestra solución será válida.

 

Estos cuatro items son las incertidumbres que necesitamos resolver. Sin hacerlo el riesgo de fracaso en nuestro proyecto sería alto, tanto por no saber cómo van a reaccionar los usuarios a nuestras funcionalidades, como por no saber si técnicamente es viable nuestra propuesta.

A la izquierda estaría representado este MVP.

 

 

 

Ahora que hemos visto como queda nuestro MVP, ¿qué pasa con nuestras siguientes historias de usuario? Recordemos que aún nos quedan 4 historias, dos que forman parte de nuestra épica y otras dos que no. Vamos a considerar que nuestro Spike ha ido correctamente y nuestro MVP también, es hora de iterar.

Imagen de la agrupación adaptada de un MMR
Agrupación adaptada de un MMR

Vamos a considerar incluir la historia que generó el Spike y la otra que también formaba parte de la épica. Incluyendo también una de las historias que no formaban parte de la épica, podemos tener una agrupación de varias funcionalidades que sería entregable a nuestros usuarios, un MMR.

 

A la izquierda estaría representado este MMR.

 

 

 

 

 

 

 

Si recuperamos y añadimos la entrega anterior a nuestro diagrama «cronológico» para representarlo todo, tendríamos algo así:

Imagen que muestra las agrupaciones "cronológicas" de un MVP y un MMR
Agrupaciones de un MVP y un MMR

Una nueva versión con varias funcionalidades que entregar a los usuarios (por lo tanto un MMR)

 

 

 

 

 

 

 

Nos quedaría por último una historia auto contenida que reflejar. Apoyándonos en que era auto contenida y era suficientemente relevante para nuestros usuarios, vamos a considerar una última versión o MMR, nuestros bloques de versiones quedarían así:

Imagen que refleja las agrupaciones de un MVP y dos MMRs
Agrupaciones de un MVP y dos MMRs

Solo nos quedaría una duda por despejar, ¿qué ha pasado con nuestra épica?

Imagen final de los agrupadores de funcionalidad MVp, MMR, MMF.
Agrupadores MVP, MMR, MMF con Épicas e Historias.

Sencillo, ha quedado distribuida en el tiempo, aportando lo más importante en iteraciones previas (MVP) y relegando características menos importantes a versiones posteriores (MMR1).

 

Con este marco, se cumple que una MMF tenga que ir completa en una release y que una Épica pueda estar distribuida a lo largo de varias releases.

 

Si extendemos este ejemplo un poco más, podríamos empezar a tener agrupaciones como las siguientes:

Imagen que representa las agrupaciones funcionales completas con MVP, MMP, MMR y MMF
Agrupaciones funcionales completas con MVP, MMP, MMR y MMF

 

Solapamientos y “casos raros”

Teniendo en cuenta las definiciones originales y nuestro modelo de aplicación, podemos decir que:

  • MVP – Minimum Viable Product, debe estar enfocado a realizar una «prueba de concepto», y mediante ésta validar una hipótesis o adquirir un conocimiento (sea de negocio o técnico).

Solapamientos del MVP

  • MMF  –  Minimum Marketable Feature, son las características mínimas con las que debe contar una funcionalidad para poder ser entregada a los usuarios finales.

Solapamiento del MMF

  • MMP (o MMR1)  –  Minimum Marketable Product, es la agrupación de MMFs que entregamos en la primera release. Es lo que comúnmente se confunde con MVP.

Solapamiento MMP

 

Extra bullet: ¿Minimum o minimal?

Me gustaría lanzar un último punto que afecta a los diferentes elementos que hemos estado viendo. En algunos casos aparece la definición minimal en lugar de minimum, pero es una diferenciación a nivel de matices, podemos considerarlo un «término de diccionario»en este caso. El uso más extendido y aceptado en minimum, por lo tanto, nos mantendremos apegados a éste.

  • Minimum. Hace referencia a la cantidad estrictamente necesaria.
  • Minimal.  Es algo más flexible y hace referencia a la cantidad que es suficiente.

 

Referencias

 

La entrada MVP, MMP, MMR, MMF: 4 «emes» para gobernarlas a todas se publicó primero en Adictos al trabajo.


Yo me pongo la camiseta…

$
0
0

¿Y qué tiene que ver el título del post con un blog técnico? Pues más de lo que te imaginas. Al igual que el término Scrum es el término inglés que hace referencia a una posición en rugby (también conocida como melé), la frase “ponerse la camiseta” procede del fútbol.

¿Y qué representa el ponerse la camiseta? Pues piensa por un momento cuando te pones la camiseta de tu equipo favorito ¿por qué lo haces? Seguro que tu respuesta ha sido: me la pongo cuando estoy orgulloso por sus triunfos, para mostrarle mi apoyo, para animar. Me la pongo porque me siento identificado por sus colores, su himno, etc.

En el entorno laboral «ponerse la camiseta» viene a ser: mostrar el compromiso del empleado con su empresa. Como persona con el rol de Coach hay situaciones en las que debo explicar el término del orgullo de pertenencia a un equipo, y pongo ejemplos, como el famoso anuncio de los electrodomésticos Balay, donde un trabajador que se jubila muestra su sensibilidad ante las cámaras llorando porque va a echar mucho de menos su empleo. Pero, ¿tú podrías decir lo mismo? ¿Te pondrías la camiseta de tu empresa?

Hace poco más de un año entré a formar parte de Autentia, empresa de consultoría informática donde la mayor parte de mis compañeros son desarrolladores y yo me desempeño como Agile Coach. Mi background viene de otras consultoras, por las que no he generado el mismo sentimiento que en Autentia y a experiencias de conocidos y amigos en el mismo sector que no son muy halagüeñas. Es frecuente escuchar que tal empresa es una cárnica, dando a entender que para la empresa no eres más que un cacho de carne con un número de empleado en el que te exprimen y cuando te quemas tienen a otro para suplirte en la recámara.

Probablemente te preguntarás: ¿por qué ponerme la camiseta?, ¿cómo debe ser la empresa para la que me la ponga? o ¿qué pasa allí para que alguien se ponga la camiseta tan orgullosamente?

Llegados a este punto habrá quien valore más el salario por encima de otros factores, aunque ya te adelanto que según diferentes estudios y artículos una vez alcanzado cierto rango salarial  lo que se busca es el salario emocional (debido a que el alto rango salarial no es motivo de felicidad) y eso es lo que nos hace aún estar más orgullosos de nuestra empresa cuando obtenemos cierto salario emocional.

Y eso es lo que te vengo a reflejar aquí, todo el mundo busca un puesto remunerado con un alto salario pero ¿te has parado a pensar en el salario emocional? ¿De qué te sirve tener un salario estratosférico si luego en el puesto de trabajo, donde pasamos un tercio del día, no estás a gusto?

A continuación, te voy a listar una serie de características y cualidades que puedes aplicar en tu equipo para que tus empleados se pongan la camiseta:

  • Modifica el proceso de reclutamiento. Nunca olvidaré la pasión con la que Lucía, la responsable de RRHH de mi empresa me explicaba la visión a detalle de la empresa. La pasión es contagiosa y ella desde el primer momento me la transmitió. En el proceso de selección se deben buscar personas afines en visión y valores, la entrevista debe estar centrada más en la conversación que en superar un exámen.
  • Asigna un salario digno. No para hacer rico a tu empleado, pero sí para ofrecer al empleado una seguridad y estabilidad económica.
  • Planifica un horario flexible. Permite que el empleado se marque su horario a su gusto (respetando el del cliente) y limita hacer horas extras. Habrá días en los que le toque quedarse, de forma excepcional, más horas de las que le correspondan, pero permite que pueda recuperarlas el día que mejor se ajuste, mitigando esas horas en exceso de su jornada laboral.
  • Brinda en todo momento apoyo a todos los compañeros. Incluso fomenta que ese apoyo proceda de los socios.
  • Integra y haz que cada empleado forme parte de un equipo. De esta forma se fomentan diferentes habilidades sociales y emocionales en equipo, compañerismo, humildad, escucha activa, asertividad, etc.
  • Muestra al empleado que está capacitado para llevar a buen puerto los hitos marcados. ¿Cómo? Otorga puestos en posiciones de influencia, esto hará sentir al empleado una parte importante de la organización.
  • Aprecia cuando alguien sea productivo, ¿cuántas veces has felicitado a un compañero por un trabajo bien hecho?
  • En ocasiones reta a tus empleados, conseguirás que mejoren y evolucionen como personas y como compañeros de trabajo.
  • Abre canales de comunicación con los socios, conviértete en alguien cercano y accesible, de tal manera que se pueda  hablar con ellos casi en cualquier momento y de cualquier tema con toda comodidad. Presta tu tiempo y sobretodo proporciona una retroalimentación de gran valor. ¿Qué dueño de empresa  se para a hablar con un trabajador y escucha sus problemas? ¿Con cuál sales a almorzar o a practicar deporte? Prueba a hacerlo y nos cuentas.
  • Promociona el desarrollo profesional como una prioridad. Para ello puedes asignar una cuota a invertir en cursos de formación y eventos, libros o material de formación que se brinden con sólo solicitarlos, etc.
  • Invierten en formación, dispón de unas horas y presupuesto para formación además de subvencionar los estudios y el aprendizaje de idiomas. No estoy hablando de cursos de formación enlatados u online. Te hablo de una carrera universitaria, certificaciones oficiales, másters, etc. A esto se le suman todas las formaciones internas que pueden ser impartidas por los compañeros y que deberían ser accesibles para todos.
  • Dispón de material y dispositivos de vanguardia accesibles. Un almacén o habitación con el material suficiente y necesario permite desarrollar la creatividad e imaginación que marque la diferencia en el desarrollo del producto. Utilizar lo último en equipos informáticos y hacer al empleado propietario de los mismos les va a permitir estar motivados, en vanguardia y no caer en obsolescencia.

 

Como conclusión quiero indicar que podría listar una cantidad de cualidades que puedes encontrar en las empresas, que podrían hacer que te involucrases más con ellas y por las que sentirse orgulloso. No tienes más que ver la lista anterior y dar pesos a unas cualidades y a otras según tus preferencias. De esta forma conseguirás, sin llegar al fanatismo, alcanzar un nivel de felicidad óptimo, que seamos francos no es fácil en el mercado laboral actual. Considero que las mencionadas anteriormente abarcan gran cantidad de la demanda de necesidades de los empleados para incrementar su salario emocional y compromiso con la empresa.

Para finalizar me gustaría preguntarte, ¿podrías decir que te sientes orgulloso de tu equipo de trabajo? ¿Consideras que hay alguna cualidad que no se haya mencionado aquí? ¿Consideras que no deberías ponerte la camiseta?

Estaré encantado de leer tus respuestas en los comentarios.

La entrada Yo me pongo la camiseta… se publicó primero en Adictos al trabajo.

Clases selladas y enumerados en Kotlin

$
0
0

Índice de contenidos

1. Introducción

Las clases selladas (sealed class) en Kotlin son aquellas empleadas para construir una herencia cerrada en la que el compilador conoce cuáles son las únicas clases hijas, ya que no puede haber otras.

Otra definición —rápida e informal— de las clases selladas podría ser la de «enumerados híper vitaminados». Esto es porque su uso se da en los mismos casos que los de los enumerados —cuando tenemos un valor que puede ser de un solo tipo de un conjunto específico de tipos— pero nos permiten que cada elemento de ese «híper enumerado» sea una clase, con las ventajas que ello conlleva frente a las limitaciones de los clásicos enumerados.

2. Enumerados

Antes de ver las clases selladas, echemos un vistazo a los enumerados en Kotlin. Estos se escriben de la siguiente manera:

enum class Planet {
    MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE
}

Pueden tener valor y comportamiento:

2.1. Valor

enum class Planet(val radius: Double) {
    MERCURY(2_439.7),
    VENUS(6_051.8),
    EARTH(6_371.0),
    MARS(3_389.5),
    JUPITER(69_911.0),
    SATURN(58_232.0),
    URANUS(25_362.0),
    NEPTUNE(24_622.0)
}

Por cierto, los guiones bajos empleados como separador decimal sirven únicamente para mejorar la legibilidad del código y su uso es opcional.

2.2. Comportamiento

Pueden tener comportamiento de dos maneras:

a) Definiendo sus propios métodos, abstractos o no:

enum class Planet(val radius: Double) {
    MERCURY(2_439.7) {
        override fun spanishName() = "Mercurio"
    },

    ...

    NEPTUNE(24_622.0) {
        override fun spanishName() = "Neptuno"
    };

    abstract fun spanishName(): String

    fun isBiggerThan(astronomicalBody: AstronomicalBody): Boolean = radius > astronomicalBody.radius
}

b) Implementando interfaces, aunque no extendiendo otras clases:

interface AstronomicalBody {

    fun getDensity(): Double

    fun isPlanet(): Boolean

}

enum class Planet(val radius: Double) : AstronomicalBody {
    MERCURY(2_439.7) {
        override fun getDensity() = 5.42
    },

    ...

    NEPTUNE(24_622.0) {
        override fun getDensity() = 1.64
    };

    override fun isPlanet() = true
}

Como vemos, podemos implementar los métodos de las interfaces tipo a tipo (getDensity()) o de forma única para todos los tipos (isPlanet()).

Sí, están fuertes los enumerados de Kotlin, pero espera a ver las clases selladas, que son más versátiles.

3. Clases selladas

La principal limitación de los enumerados es que todos los subtipos siguen una misma estructura. Por ejemplo, en el caso de los planetas todos tenían un radio, un método para obtener su densidad, otro para obtener su nombre en español, etc. Sin embargo, cada subtipo (subclase) de una clase sellada es una clase que puede ser de la manera que quiera, con sus propios valores y métodos.

3.1. Ejemplo

Imaginemos que hacemos peticiones a un repositorio y queremos controlar posibles errores. Sabemos que tendremos un error si el usuario no está autorizado, otro si no tiene permisos para acceder al recurso y otro desconocido por lo que pueda pasar. En el caso de no estar autorizado, el repo nos dice el nombre del usuario, mientras que en el caso de falta de permisos el repo nos responde con una lista de roles de nuestro usuario; en caso de error desconocido, no tenemos ninguna información adicional.

Para modelar este conjunto de errores ya no nos sirven los enumerados pero… ¡sí las clases selladas!

3.2. Definición

El ejemplo anterior lo modelaríamos de la siguiente manera:

sealed class RepositoryFailure {

    class Unauthorized(val username: String) : RepositoryFailure()

    data class Forbidden(val userRoles: List) : RepositoryFailure()

    object Unknown : RepositoryFailure()

}

Las llaves son opcionales, por lo que es válido también lo siguiente:

sealed class RepositoryFailure

class Unauthorized(val username: String) : RepositoryFailure()

data class Forbidden(val userRoles: List) : RepositoryFailure()

object Unknown : RepositoryFailure()

RepositoryFailure es una clase abstracta que tiene únicamente tres hijas: Unauthorized, Forbidden y Unknown. Por tanto, cuando manejemos un RepositoryFailure este solamente podrá ser de estos tipos. Por cierto, estamos obligados a definir las subclases en el mismo fichero que la clase sellada madre.

3.3. Ventajas de las clases selladas

Las ventajas frente a los enumerados vienen dadas por lo que ya hemos comentado, que es el hecho de que los elementos sean clases (class, data class, object e incluso sealed class):

  • La más importante es que cada subclase puede tener sus propios valores y sus propios métodos, a diferencia de los enumerados, cuyos elementos siguen todos la misma estructura.
  • Además, los enumerados solamente pueden tener una instancia, mientras que las subclases de clases selladas pueden tener varias instancias, cada una con su estado, o una si la definimos como object.

sealed class Animal {

    sealed class Mammal : Animal() {

        class Dog : Mammal() {
            fun bark() { ... }
        }

        data class Cat(val remainingLives: Int) : Mammal() {
            fun meow() { ... }
        }

    }

    ...

}

4. Manejo de enumerados y clases selladas

Dado un enumerado o una clase sellada, su uso en un condicional puede ser como el siguiente:

when (planet) {
    MERCURY -> "El más cercano al Sol."
    EARTH -> "El único con vida."
    JUPITER -> "El más grande."
}

Como vemos, simplemente estamos definiendo comportamiento para un subconjunto de los planetas pero, normalmente, queremos definirlo para todos los casos. Podríamos poner todos los subtipos en el when, pero el código tendría un problema: si en un futuro Plutón da el estirón y pasa a ser considerado planeta, aunque nosotros lo metamos en el enumerado, en el código anterior no le estaremos dando comportamiento. Podríamos pensar en crear los condicionales con un else, pero lo que en realidad estaría chulo es que el compilador nos avisase de aquellos fragmentos de código en los que no hemos tenido en cuenta al pobre Plutón. Y esto en Kotlin se puede hacer si utilizamos los condicionales como expresiones.

4.1. Condicionales como expresiones

Una (otra) de las maravillas de Kotlin es que los condicionales se pueden usar no solamente como declaraciones, sino también como expresiones, es decir, como sentencias que devuelven valor. Por ejemplo:

val min: Int = if (a < b) a else b

o:

val text: String = when {
    a < 0 -> "Menor que cero"
    a == 0 -> "Cero"
    else -> "Mayor que cero"
}

Cuando los condicionales son utilizados como expresiones, el compilador nos obliga a contemplar todos los casos, lo cual hacemos con un else en el par de ejemplos anteriores.

4.2. Enumerados y clases selladas en condicionales como expresiones

En el caso de los enumerados y clases selladas no es necesario añadir ningún else para que el compilador no se queje, sino que nos basta con definir todos los casos:

val description: String = when (planet) {
    MERCURY -> "El más cercano al Sol."
    VENUS -> "..."
    EARTH -> "El único con vida."
    MARS -> "..."
    JUPITER -> "El más grande."
    SATURN -> "..."
    URANUS -> "..."
    NEPTUNE -> "..."
}

Además, si ahora añadimos Plutón al enumerado de planetas, el código anterior no compilaría. Y esto es lo deseable, ya que los errores los queremos en tiempo de compilación, no de ejecución.

Y para terminar, si empleamos clases selladas, entra en juego el smart cast y ya nuestra experiencia de programación es colosal:

fun getError(failure: RepositoryFailure): String = when (failure) {

    is Unauthorized -> "El usuario ${failure.username} no está autorizado."

    is Forbidden -> "No se puede acceder al recurso con ninguno de los siguientes roles: ${failure.userRoles.joinToString()}"

    Unknown -> "Error desconocido."
}

El smart cast de Kotlin hace que, si entramos en la rama de Unauthorized, por ejemplo, nuestra variable failure sea ya un Unauthorized en ese ámbito y nos evitar tener que hacer un cast. Como vemos, en el ejemplo estamos accediendo al atributo username que solamente el tipo Unauthorized tiene.

Por cierto, el condicional de las ramas de un when para comprobar el tipo de una instancia se construye con is en el caso de las clases y sin is en el caso de los objects.

4.3. Extra: exhaustive

Personalmente, intento utilizar siempre condicionales como expresiones para manejar enumerados y clases selladas, así estoy obligado a declarar el comportamiento para cada subtipo. Sin embargo, no siempre quiero estructurar el código así, por la razón que sea, pero tampoco quiero perder la detección del compilador de que me estoy dejando algo.

Pues bien, esto lo podemos solucionar añadiendo al final del when una llamada a exhaustive:

// No compila.

when (planet) {
    MERCURY -> "El más cercano al Sol."
    EARTH -> "El único con vida."
    JUPITER -> "El más grande."
}.exhaustive

Al hacer esto, nuestro código deja de compilar porque no se están contemplando todos los casos.

No lo entiendo.

Vayamos por partes. exhaustive es una propiedad de extensión que nos creamos así:

val Any?.exhaustive
    get() = Unit

Como extensión que es, se puede llamar desde cualquier tipo (Any?). Definimos el get() para que no devuelva nada, pues si quisiésemos el valor devuelto por el when, entonces usaríamos este directamente como expresión, sin tener que recurrir al exhaustive.

Al añadirlo a un when de tipo declaración lo convertimos a expresión porque el exhaustive es una extensión de un tipo, que será el que el when devuelva. Y, al ser una expresión, ya estamos obligados a dar comportamiento a todos los tipos.

En fin, un pequeño hack aprovechando las posibilidades de Kotlin.

5. Conclusión

Las clases selladas son la alternativa a utilizar cuando los enumerados se nos quedan cortos, cuando queremos una herencia cerrada. Son muy útiles, por ejemplo, para modelar errores en el caso en el que no todos tengan la misma estructura. Y su uso con when como expresión y smart cast hace nuestro código robusto y legible, respectivamente.

6. Referencias

Documentación oficial de Kotlin:

La entrada Clases selladas y enumerados en Kotlin se publicó primero en Adictos al trabajo.

Capas, cebollas y colmenas: arquitecturas en el backend

$
0
0

En esta entrada analizaremos algunas de las arquitecturas que se utilizan hoy en día a la hora de montar un sistema backend relativamente complejo, las diferencias y similitudes que tienen y si pueden llegar a complementarse o son excluyentes.

 

Índice de contenidos.

¿Y tú qué arquitectura utilizas?

Lo primero que tenemos que dejar claro es qué entendemos por arquitectura. La arquitectura de un sistema software es la definición de qué componentes constituyen ese sistema, sus responsabilidades y las relaciones de uso y dependencia entre ellos. Es, por tanto, completamente independiente de la tecnología que se utilice y no debería representar en ningún momento el framework, la base de datos o la forma de interactuar con el usuario.

Arquitecturas multicapas: tiers y layers

La primera de las arquitecturas que vamos a ver es la multicapa. En inglés tenemos los conceptos de multitier y mutilayer architectures. En español la traducción para ambos es arquitectura multicapa, pero es importante conocer la diferencia entre ambos.

Una arquitectura multitier (o n-tier) hace referencia a una arquitectura en la que se expone la separación de sistema en varias capas físicas. Es decir, los distintos componentes están en máquinas separadas. En este tipo de arquitectura tenemos por ejemplo la de [cliente – servidor] o la de [presentación – negocio – datos]. Sin embargo, al referirse más a la distribución física del código ejecutable que a la relación lógica de sus componentes se aleja de lo que estamos tratando en aquí.

Esquema de una arquitectura n-tier

Por su parte una arquitectura multilayer (o n-layer) refleja la separación lógica en capas de un sistema software. En este contexto una capa es simplemente un conjunto de clases, paquetes o subsistemas que tienen unas responsabilidades relacionadas dentro del funcionamiento del sistema. Estas capas están organizadas de forma jerárquica unas encima de otras y las dependencias siempre van hacia abajo. Es decir, que una capa concreta dependerá solamente de las capas inferiores, pero nunca de las superiores. En backend lo más común suele ser tener [servicio – negocio – acceso a datos], aunque a veces podríamos también encontrarnos una capa superior de presentación si esta se está manejando también a nivel de backend o una capa con controladores REST.

Esquema de una arquitectura n-layer

Al hablar de arquitectura multilayer podemos diferenciar además entre sistemas estrictos (strict layered systems) y relajados (relaxed layered systems) en función de las relaciones de dependencia con las capas inferiores. Un sistema estricto es aquel en el que una capa solo depende directamente de la capa inmediatamente inferior, mientras que en un sistema relajado puede hacerlo de todas las que hay por debajo aunque no sean contiguas. Más adelante veremos para qué es esto útil.

Arquitectura multicapa relajada

Ya simplemente como apunte, hemos visto que las dependencias siempre van de arriba a abajo pero hay ocasiones en las que, dependiendo del funcionamiento del sistema y cómo se hayan ideado las capas, es necesario que haya una comunicación de abajo a arriba. Un ejemplo es cuando la interfaz gráfica tiene que actualizarse con cambios que se producen en los datos. Mecanismos como el patrón Observer permiten a capas inferiores transmitir información por su cuenta y riesgo a otra superior pero abstrayéndose por completo de quien es el observador concreto y, de esta forma, no rompiendo las reglas de dirección de dependencia.

La principal desventaja de este planteamiento es que la última capa es la de acceso a datos. Esto implica que todas las demás capas, incluyendo una posible de interfaz de usuario, acaba dependiendo de ella bien de forma directa o de forma transitiva.

Arquitectura multicapa con visualización por círculos concéntricos

Al final lo que estamos logrando es algo como lo que se muestra en la figura de arriba, que el centro de toda nuestra aplicación sea la persistencia de datos. Es cierto que para algunos sistemas puede llegar a cuadrar o a no ser una desventaja demasiado importante como para tenerla en cuenta, pero aún así es algo a tener en cuenta.

Para sistemas más grandes puede llegar a ser un problema y por eso han surgido otras arquitecturas que intentan solucionarlo, aunque no dejan de ser una versión ligeramente distinta y con un nombre llamativo de las arquitecturas multicapas ligeramente modificadas para implementar adecuadamente el principio de inversión de dependencia (la D de SOLID). Según este principio debemos depender de las abstracciones en lugar de hacerlo de las clases concretas. En breve vemos cómo hacen esto y como al final todo sigue siendo una arquitectura multicapa bien diseñada.

Onion architecture

Teniendo lo que hemos visto arriba en mente, se empezaron a crear arquitecturas multicapa que sí seguían el principio de inversión de dependencias (DIP por sus siglas en inglés) y que, por tanto, solucionaban el problema que comentábamos. Con el tiempo Jeffrey Palermo decidió darle un nombre a este tipo de arquitecturas e intentar estandarizarla para que la gente tuviera una forma común de referirse a ella. Pero no es algo que crease él mismo desde cero. Está es la forma en la que se suele representar la arquitectura onion.

Esquema de una arquitectura onion

A grandes rasgos se trata de una arquitectura multicapa construida en torno a un modelo de dominio independiente de todo lo demás. Las dependencias van hacia el centro, por lo que todo depende de ese modelo de dominio. A su alrededor se organizan varias capas, estando en las más cercanas las interfaces de repositorio, es decir, las que definen el comportamiento del almacenamiento de los datos pero no lo implementan. En las capas siguientes está la lógica de negocio que usa estas interfaces y que en tiempo de ejecución tendrá las implementaciones apropiadas. Alrededor del núcleo de modelo puede haber un número variable de capas, pero siempre debe cumplirse que las interfaces estén más cerca que las clases que las utilizan. Con esto ya tenemos creado el core lógico de nuestra aplicación, que no tiene absolutamente ningún detalle de infraestructura.

Por último, en la capa más exterior es donde estarán todos los detalles de comunicación con el exterior (tanto de interfaz con el usuario como el almacenamiento) y los tests de integración. Las clases que se presentan aquí implementarán las interfaces que se definen en las capas inferiores, pudiendo cambiar por tanto las implementaciones dependientes de la tecnología sin que las capas inferiores se enteren. Lo que conseguimos de esta manera es una arquitectura que habla de cómo está montado el sistema y no de los terceros que se comunican con él.

Disclaimer: aunque la arquitectura como tal esté desligada de la forma en la que se va a comunicar con el exterior no quiere decir que la implementación lo esté también al 100%. Obviamente hay que tener en cuenta el tipo de uso que se va a dar a nuestro sistema, pues si se va exponer en una web y ser accesible por varios usuarios a la vez tendremos que poner mecanismos para evitar problemas de paralelismo mientras que no sería necesario si va a ser una aplicación de escritorio que solo usará una persona a la vez. El uso que se le va a dar a nuestro sistema (que se acceda en paralelo) sí afectará al core de la aplicación, pues es parte de la identidad del sistema. Mientras el cómo se ejerce este uso (mediante una API REST) no lo es, y es de esto de lo que nos desligamos mediante estas abstracciones.

Además también tiene otra ventaja importante a nivel de pruebas. Igual que se pueden sustituir partes de la capa externa sin problemas también pueden omitirse según necesitemos. Así la lógica de nuestra aplicación es mucho más fácil de testear si podemos quitarnos de enmedio toda esta complejidad o sustituirla por otra que sea más conveniente a la hora de probar el sistema.

Lo único que queda por ver entonces es cómo se logra esto. Para ello vamos a representar esta misma arquitectura como una multicapa relajada (aunque no pondremos todas las flechas de dependencia de ahora en adelante):

Esquema aplanado de una arquitectura onion

De esta forma creo que se ve claramente cómo el sistema que hemos construido ahora se ha hecho sobre el modelo de dominio, que sí es crucial para nosotros, en lugar de sobre detalles de persistencia que podríamos llegar a querer cambiar. Esto parece tener mucho más sentido y según crezca la aplicación seguirá siendo mantenible.

Sin embargo, si miramos la arquitectura que acabamos de plantear podemos observar algo que es posible que necesitemos aclarar. Según lo estamos viendo, en la capa superior tenemos los detalles de la implementación de los repositorios, cerca del dominio las interfaces y en medio de ambas las clases de lógica de la aplicación. En principio esto parecía lógico, ¿pero cómo puede usar la capa intermedia las implementaciones de la capa superior sin depender de ella? Pues con la inyección de dependencias. Este mecanismo permite seleccionar por configuración qué implementación concreta se usará en cada caso, pero se hará en tiempo de ejecución. De esta forma  todo el core está abstraído de tal manera que solo está programado contra las interfaces y en ningún momento es dependiente de cómo esté funcionando por debajo la implementación elegida. Esto nos permite, además, tocar la configuración de qué se está inyectando y así elegir la implementación que queremos sin que nada por debajo cambie en lo más mínimo.

Esto es exactamente lo que comentamos en la sección anterior sobre seguir el principio de inversión de dependencias. Como vemos, en el diagrama estamos poniendo en el nivel superior siempre las implementaciones y en el inferior las interfaces y siempre estamos accediendo a las implementaciones concretas mediante las interfaces. Al final no es otra cosa que diseñar una arquitectura multicapa que sigue correctamente el DIP.

Puertos y adaptadores: una arquitectura hexagonal

Unos años antes de que Jeffrey Palermo acuñase la arquitectura onion, Alistair Cockburn (uno de los firmantes del manifiesto ágil) definió la arquitectura de Ports and Adapters o arquitectura hexagonal. Aunque se crease antes la voy a explicar después porque casi todos los conceptos importantes aplican también a onion y si lo ponía después no quedaba demasiado que decir.

El objetivo de la arquitectura hexagonal es poner, una vez más, en el centro del sistema toda la lógica propia del dominio y definir unas fronteras muy claras y unos mecanismos de transformación con el exterior. Así se consigue que el core lógico no se contamine ni dependa en ningún momento de los detalles concretos del acceso a los datos, la comunicación con terceros o la interacción del usuario. Más o menos es lo mismo que busca onion, como habréis observado.

Esquema de una arquitectura hexagonal

Partiendo de esta imagen está claro que nuestro sistema se compondrá de 3 partes bien diferenciadas: la lógica core de la aplicación, los puertos y los adaptadores. Cualquier comunicación con el exterior se hará única y exclusivamente a través de los puertos y adaptadores, que se encargarán de la conversión de datos para que dentro de las fronteras todo esté en nuestro idioma. Pero, ¿qué función hacen exactamente estos componentes?

Los puertos son las interfaces que definen la interacción con el exterior y exponen únicamente datos de nuestro dominio, dejando que toda la lógica de transformación esté de puertas afuera y no se contamine el interior. Y los adaptadores son precisamente la forma de conectar el exterior con los puertos, implementando la comunicación y la conversión de datos entre el dominio y lo que se necesite fuera.  Los adaptadores no pertenecen al core como tal y podrían implementarse cada uno completamente por separado si quisiésemos mientras dependan del puerto que usan/implementan.

Una cosa importante a tener en cuenta (tal y como se ve en la imagen de arriba) es que un único puerto puede tener más de un adaptador asociado. Pongamos el ejemplo de un puerto de entrada que expone una funcionalidad que hace nuestro sistema. El puerto sería la interfaz de dicha funcionalidad: la entrada y salida del método expresada en objetos del dominio. Y ese servicio podría exponerse, por decir dos, como un servicio REST o con una interfaz por terminal; en cuyo caso tendríamos un puerto y dos adaptadores. Y esto también es aplicable al acceso a datos, que podría ser desde una caché, una base de datos, un servicio de un tercero o cualquier otro sistema.

Como vemos, gracias a la definición de estas fronteras nuestro core queda completamente desvinculado del exterior. Es más, se abstrae incluso de si estamos estamos haciendo un backend para una aplicación web, de escritorio o un web service que se expondrá en un bus; pues al final no dejan de ser formas de comunicarnos con el usuario y no deberían afectar a la lógica. Simplemente se añadirá un nuevo adaptador para la funcionalidad que se quiera exponer por una vía concreta y ya está. Eso sí, no olvidéis el disclaimer que puse en la arquitectura onion, pues es igualmente aplicable aquí.

Además, en la imagen habréis visto que tanto puertos como adaptadores están divididos en primarios y secundarios (también llamados driving y driven respectivamente). Los primarios definen el comportamiento que expone nuestro sistema al exterior, la comunicación con el usuario (sea este una persona, otra aplicación…). Por su parte los secundarios definen la interacción que necesita nuestro sistema con terceros (base de datos, chachés, otros sistemas) para ejecutar correctamente sus funcionalidades de principio a fin. A grandes rasgos podríamos decir que los primarios son de entrada y los secundarios son de salida en el sentido de la dirección en la que viajan las peticiones.

Y muchas veces surge una pregunta, especialmente en este caso por la terminología que se usa. ¿Cuántos puertos debemos definir? ¿Hay que limitarse a 6? La respuesta es que no. Como todo en esta vida depende del caso concreto. Los extremos están en tener un único puerto para todo el sistema o tener uno por cada funcionalidad concreta, y por lo general el término medio suele ser lo óptimo. En mi experiencia solo he usado este tipo de arquitectura una vez y teníamos prácticamente un puerto ya no solo por funcionalidad sino por fuente de datos que se usaba en cada una. Sin embargo, era un caso muy concreto en los que cada dato se obtenía de un servicio web distinto, que puede llegar a cambiar independientemente en el futuro, y de esta forma podemos modificar cualquiera de ellos rápidamente sin afectar al resto. Pero, una vez más, depende del caso. El hecho de que se llame arquitectura hexagonal se debe simplemente a que gráficamente suele representarse con un hexágono para dejar claro que puede hacer varios puertos y que son independientes los unos de los otros. Pero no es más que una ayuda visual para entenderlo mejor y no implica que 6 sea el número idóneo para nada.

Pero una vez entendida la teoría siempre queda la duda de cómo se implementa esto. Lo primero que tenemos que decir es que en la arquitectura hexagonal (al menos originalmente) no se habla de cómo debe implementarse la lógica de negocio como tal, sino que se entiende todo como solo dos capas: los adaptadores que están fuera del hexágono por una parte y los puertos y la lógica de negocio como otra. La capa superior será lo que está fuera y la capa inferior lo que está dentro del hexágono, yendo la dependencia de arriba a abajo.

Forma simple de una arquitectura hexagonal aplanada

Lo que sí se define es qué relación tienen los puertos y los adaptadores y quién implementa cada una de las interfaces.

  • Los puertos primarios son interfaces de funcionalidad del sistema, por lo que estarán implementadas por la capa de servicios dentro del hexágono. Los adaptadores primarios usan, y no implementan, dichos puertos para comunicarse como necesiten con el interior.
  • Los puertos secundarios, por su parte, sirven de interfaz de salida para que el interior se comunique con sistemas externos. En este sentido el core utilizará los puertos secundarios, que esta vez serán implementados por los adaptadores secundarios.

Si detallamos esto un poco más y lo desarrollamos para entender bien dónde debería estar nuestro modelo y dónde las interfaces para hacer posible el planteamiento de esta arquitectura nos quedaría algo como esto:

Diagrama de componentes de una arquitectura hexagonal

De aquí destacar que los adaptadores solo dependen de la interfaz y del dominio, pero no del resto de la aplicación. Así pues no hay nada que dependa de los adaptadores ni estos dependen de nada que no sean las interfaces. Ya hemos visto que con esto seguimos el DIP y se logra normalmente usando la inyección de dependencias. Sin embargo, lo que nos interesa es que al ponerlo en forma de una arquitectura multicapa relajada obtenemos esto:

Forma desarrollada de una arquitectura hexagonal aplanada

Antes hemos dicho que la arquitectura de Ports and Adapters no especifica en ningún momento la estructura que debe tener la parte core dentro del hexágono (mientras que por ejemplo onion sí lo hace), pero lo mostrado arriba parece la manera más natural de implementar lo que se expone manteniendo las menores dependencias posibles. Así pues se ve de forma clara que lo que logramos es construir todo nuestro sistema alrededor del modelo de dominio y minimizando las dependencias con el exterior. Además en esencia es lo mismo que teníamos en la arquitectura onion pero cambiando el nombre a algunos componentes para dar más importancia a las fronteras con el exterior.

Clean architecture

Ya hemos visto que ha habido más de un tipo de arquitectura que busca exactamente lo mismo: abstraer la lógica esencial y la propia arquitectura de los detalles de comunicación con el usuario u otros sistemas. En algún momento Uncle Bob decidió crear una que las unificara todas (incluida la screaming architecture que él mismo propuso) y la llamó, en un alarde de originalidad, clean architecture.

El objetivo es el mismo que hemos estado persiguiendo hasta ahora, y la implementación es a grandes rasgos una hexagonal aunque cambiando algunos nombres y definiendo mínimamente la estructura interna. Como ya hemos visto los conceptos no me pararé a explicarla demasiado. En este caso utilizaré la imagen que él mismo tiene en su entrada de Clean Coder, que es bastante explicativa:

Esquema de clean architecture

A partir de este esquema se ve que hay 4 capas, aunque internamente cada una podría dividirse en todas las que puede ser necesario. En la capa interna tenemos las entidades, que vendría a ser el modelo de negocio, las funciones básicas o lo que sea que represente la lógica del negocio (el dominio, vaya). En la capa inmediatamente superior están los casos de uso, que no es otra cosa que la lógica propia de la aplicación. Aquí también están lo que serían los puertos en una arquitectura hexagonal, que en este caso se llaman Use Case Input Port (si son primarios) y Use Case Output Port (si son secundarios), y la implementación de los de entrada está en lo que se denomina Use Case Interactor. Por encima de esto tenemos la capa de lo que serían los adaptadores: controladores, presentadores, acceso a terceros… Y por encima está una última capa que ya no forma parte del sistema backend como tal y que son los dispositivos con los que nos comunicamos, la base de datos, la interfaz de usuario que nos llama, etc.

Como vemos, nada nuevo bajo el sol. Simplemente es una arquitectura hexagonal con otros nombres y en la que se ha definido un poco más la separación interna en, al menos, dos capas. Sin embargo, aunque no se haga de manera explícita este mismo planteamiento de separar el dominio y construir sobre él emana de forma natural de Ports and Adapters si queremos implementarlo como hemos comentado arriba.

¿Y qué pinta tiene todo esto?

Por último quería mostraros una serie de esqueletos muy muy básicos de las arquitecturas que hemos comentado. Son proyectos Java creados con Maven, pero no deberíais tener ningún tipo de dificultad para entenderlos puestos que lo que nos interesa es la estructura.

Por supuesto en un sistema real más grande es posible que adopten otra forma y tengan más paqueterías o niveles. O que se esté siguiendo DDD (Domain Driven Development) para construirlo y sea dentro de cada dominio donde se pueda observar el tipo de arquitectura que sigue. Esto último es, al menos en mi opinión, muy recomendable y lo más difícil será aplicar DDD, pero lo que es la arquitectura será muy sencillo de aplicar.

En cualquier caso lo que quiero es afianzar lo que hemos visto arriba y que veáis que en esencia todas son iguales. El código, por si queréis echarle un vistazo y ver la relación entre cada clase/interfaz, está subido a mi repositorio.

Arquitectura multicapa sin seguir el DIP

Esqueleto de multicapa sin DPI

Onion (arquitectura multicapa que sigue el DIP)

Esqueleto de arquitectura onion

Arquitectura hexagonal

Esqueleto de arquitectura hexagonal

Clean architecture

Esqueleto de clean architecture

El primer ejemplo es un caso aparte porque no está siguiendo el mismo principio que las demás. Pero dejando ese aparte podéis ver que realmente no son tan distintas unas arquitecturas de otras. En las tres la conversión entre MyModel y MyModelPersistence se hace en el propio repositorio para que no afecte al resto de la aplicación. Y lo mismo ocurre en el controlador al transformar a MyModelWebResponse. Además en los tres también estamos programando contra interfaces y luego inyectando la implementación (excepto en el caso de la llamada al servicio en onion, que como no lo indica expresamente la arquitectura he preferido ceñirme a ella). Lo único que cambia realmente son los nombres de la paquetería y, ligeramente, dónde está cada uno.

Conclusiones

La entrada que hemos hecho ha sido un poco densa, pero al menos espero que se acaben entendiendo los conceptos, de qué se está hablando (o de qué se debería estar haciendo) al usar estos términos y cómo están relacionados entre ellos. Al menos mi objetivo era ese, que no pongáis caras raras cuando escuchéis estas palabrejas.

Como habréis visto, al final solo hemos hablado sobre cómo separar nuestro software por capas que el desarrollo sea más organizado, sencillo y, principalmente, mantenible. Inicialmente sin preocuparnos excesivamente de las dependencias. Esto tiene perfecto sentido para aplicaciones pequeñas que implementan un CRUD y poco más. Si al final todo lo que haces es consumir un dato, mostrarlo por pantalla y encima el sistema no tiene un volumen inmenso, ¿para qué vamos a complicarnos la vida montando algo que no nos va a aportar demasiado pero que puede ser más difícil de entender? Cada herramienta tiene su uso, y estas arquitecturas no son una excepción.

Posteriormente hemos dado varias alternativas para además construir toda la aplicación sobre nuestro dominio, lo cual tiene bastante más sentido y es conveniente si estamos en un desarrollo medianamente grande. Realmente no creo que os haya contado nada nuevo. Seguramente ya estéis usando estas arquitecturas en vuestro día a día, aunque a veces no conozcáis el nombre, porque desde hace tiempo se han empezado a convertir en un estándar. Lo que es importante, y a mi me interesa, es que entendáis el motivo de lo que estáis haciendo y las ventajas que aporta. Que no lo sigáis a pies juntillas simplemente porque alguien os lo ha dicho sin saber para qué sirve lo que hacéis. Y además que notéis que todas ellas esencialmente son lo mismo pero con distinto nombre. En cualquier caso, yo particularmente de todas ellas prefiero la arquitectura hexagonal. Creo que aporta un plus de semántica a toda esta nube de capas y ayuda a que en la arquitectura y la paquetería queden muy remarcadas las fronteras de nuestro sistema con el exterior. Y además los nombres me gustan sensiblemente más que en clean architecture, aunque para gustos colores.

Eso es todo por hoy. Y recordad que la arquitectura es cómo vuestro sistema se compone de partes más pequeñas que se relacionan entre sí y dependen unas de otras. Así que cuando alguien os pregunte cuál es la de vuestra aplicación no les mencionéis el framework, la base de datos y demás, al menos hasta la segunda frase.

Referencias

La entrada Capas, cebollas y colmenas: arquitecturas en el backend se publicó primero en Adictos al trabajo.

Either en Kotlin

$
0
0

Índice de contenidos

1. Introducción

Either es un tipo de datos comúnmente usado en programación funcional que se emplea para almacenar un valor de uno de dos posibles tipos. Por ejemplo, si quisiésemos almacenar en una variable una pera o una manzana, pero solamente una de estas dos, el tipo de esta podría ser un Either de peras o manzanas:

val either: Either<Pear, Apple>

Una variable de este tipo contendría o bien una pera o bien una manzana, pero nunca las dos.

2. Definición

Es un estándar que los tipos que pueden representar un Either se llamen Left y Right. Por ejemplo, con el Either<Pear, Apple> antes mencionado, el Left sería una pera y el Right una manzana.

¿Cómo construimos este tipo de datos? Vemos que el Either es una abstracción de dos tipos: Left y Right; además, únicamente de estos dos, pues no queremos que Either tenga ningún otro tipo hijo. Así que una clase sellada se ajusta perfectamente a nuestras necesidades:

sealed class Either<out L, out R> {

    data class Left<out L>(val l: L) : Either<L, Nothing>()

    data class Right<out R>(val r: R) : Either<Nothing, R>()

}

Either admite dos genéricos L y R, que serán los dos tipos posibles del valor que queremos almacenar (peras o manzanas, siguiendo el ejemplo). La clase hija Left admitirá uno de estos tipos, L, y Right el otro, R. Y como hemos dicho que el Either actúa como contenedor de datos, pues Left almacenará un valor de tipo L (val l: L, una pera) y Right uno de tipo R (val r: R, una manzana).

3. Uso básico

Un caso sencillo de cómo una función podría devolver un Either sería el siguiente:

fun getOnePieceOfFruit(user: User): Either<Pear, Apple> {
    if (user.lovesPears()) {
        return Either.Left(Pear())
    } else {
        return Either.Right(Apple())
    }
}

El Either se genera en esta función y puede ir viajando por diferentes capas sin tratarlo, pero en algún punto hay que controlar qué ocurre cuando es Left y qué ocurre cuando es Right:

val pieceOfFruit: Either<Pear, Apple> = getOnePieceOfFruit(user)

when (pieceOfFruit) {
    is Left -> {
        val pear: Pear = pieceOfFruit.l
        eat(pear)
    }
    is Right -> {
        val apple: Apple = pieceOfFruit.r
        store(apple)
    }
}

El mismo código haciendo inline de las variables sería así:

when (pieceOfFruit) {
    is Left -> eat(pieceOfFruit.l)
    is Right -> store(pieceOfFruit.r)
}

Por cierto, en estos ejemplos el Left y el Right están importados estáticamente.

4. Uso avanzado

4.1. Tratar Either con fold

En el ejemplo anterior, el código de las ramas del when era bastante sencillo pero, si fuese complicado, lo suyo sería sacarlo a métodos auxiliares:

when (pieceOfFruit) {
    is Left -> handlePear(pieceOfFruit.l)
    is Right -> handleApple(pieceOfFruit.r)
}

fun handlePear(pear: Pear) {
    // ...
}

fun handleApple(apple: Apple) {
    // ...
}

Para estos casos, podemos mejorar nuestro código si definimos el siguiente método en la clase Either:

sealed class Either<out L, out R> {

    // ...

    fun <T> fold(fnL: (L) -> T, fnR: (R) -> T): T {
        return when (this) {
            is Left -> fnL(l)
            is Right -> fnR(r)
        }
    }

}

y lo utilizamos de la siguiente manera:

pieceOfFruit.fold(::handlePear, ::handleApple)

Es decir, estamos encapsulando el when en un método llamado fold y acercándonos a una programación más funcional y declarativa, ya que expresamos qué queremos hacer (tratar el Either) sin importarnos el cómo (usando un when).

4.2. Convertir Either con map

Siguiendo con el ejemplo de las peras y las manzanas, podría llegar un punto en el que la reina Grimhilde recibiese un Either de estas frutas y, en el caso de tener una manzana, convirtiese ésta a una manzana envenenada (¡celooosa!). Pues bien, la bruja podría hacer uso del método map que podemos definir en la clase Either:

sealed class Either<out L, out R> {

    // ...

    fun <R2> map(fn: (R) -> R2): Either<L, R2> {
        return when (this) {
            is Left -> this
            is Right -> Right(fn(r))
        }
    }

}

y pasar de un Either<Pear, Apple> a un Either<Pear, PoisonedApple>:

val surprise: Either<Pear, PoisonedApple> = pieceOfFruit.map(::poison)

fun poison(apple: Apple) : PoisonedApple {
    // ...
}

5. Manejo de errores con Either

Hemos visto que el Either se emplea para almacenar o bien un valor de un tipo o bien un valor de otro. Normalmente esto no se da en la lógica de nuestros programas y podemos pensar que es raro que vayamos a necesitar este tipo de datos. Pero ahora piensa en llamadas a funciones que pueden devolvernos o bien el valor esperado o bien un error conocido, ¿a que ya no suena raro?

Tradicionalmente hemos manejado los errores con excepciones, pero deberíamos dejar las excepciones para casos ¡excepcionales! Que yo en mi backend vaya a obtener de una base de datos una entidad dado su ID y no la encuentre no es una excepción, es un error conocido y esperado por cualquier programador. Que yo desde un dispositivo móvil haga una petición al backend y no funcione porque no hay conexión a Internet tampoco es una excepción, sino un error esperado. Y deberíamos modelar esos errores con tipos en lugar de ir lanzando excepciones. Además, las excepciones son costosas debido a la creación de la traza de la pila de ejecución.

Por tanto, podemos hacer que las funciones que sabemos que pueden fallar devuelvan un Either con el valor esperado o con uno de esos errores. Y es estándar emplear el Left para los errores y el Right para el éxito, debido al doble significado de la palabra inglesa right: derecha y correcto.

5.1. Ejemplo

Imaginemos que lanzamos una petición a un microservicio que nos devuelve un libro dado un ISBN. Lo que esperamos recibir es el libro, pero también es posible que nos hayamos inventado el ISBN y no haya ningún libro asociado a él o que, al hacer la petición, el microservicio esté caído y obtengamos un timeout, o que recibamos cualquier otro error HTTP. Como vimos en el tutorial de clases selladas en Kotlin, podríamos modelar nuestro error utilizando este tipo de clases:

sealed class GetBookFailure {

    data class BookDoesntExist(val isbn: String) : GetBookFailure()

    object Timeout : GetBookFailure()

    object Unknown : GetBookFailure()

}

El método que llamaría al microservicio podría tener la siguiente firma:

fun getBook(isbn: String): Either<GetBookFailure, Book> {
    // ...
}

En su cuerpo devolveríamos un Right del libro conseguido o manejaríamos los errores para devolver el Left correspondiente.

De esta manera, cada vez que llamemos al método getBook() ya sabemos qué esperar y no tenemos la incertidumbre de qué va a ocurrir en caso de error.

6. Extra: OrNull

Me encanta el manejo de la nulabilidad de Kotlin y cómo se puede aprovechar para hacer nuestro código más legible en ciertos casos.

Por ejemplo, es un estándar en las bibliotecas de Kotlin encontrar métodos como String.toInt() que tienen una variante llamada igual pero terminada en OrNull: String.toIntOrNull(). Mientras que el primer método lanza una excepción en caso de que el texto no sea un número, el segundo devuelve null. Esto hace que podamos manejar el error utilizando, por ejemplo, el cómodo operador Elvis:

fun String.toPercentage(): String {
    val number: Int = this.toIntOrNull() ?: 0
    return "$number %"
}

Por cierto, la misma función en una línea:

fun String.toPercentage() = "${toIntOrNull() ?: 0} %"

Dicho esto, podemos definir en la clase Either un método para obtener su valor en caso de ser un Left u obtener null si es un Right, y otro método para lo contrario:

sealed class Either<out L, out R> {

    // ...

    fun toLeftValueOrNull(): L? = when (this) {
        is Left -> l
        is Right -> null
    }

    fun toRightValueOrNull(): R? = when (this) {
        is Left -> null
        is Right -> r
    }

}

Estos métodos se podrían utilizar cuando solo quisiésemos el valor del Left o el valor del Right y nos diese igual el valor del otro tipo:

fun getAssistanceNumber() : Either<Failure, String> {
    // ...
}

val assistanceNumber: String = getAssistanceNumber().toRightValueOrNull() ?: DEFAULT_NUMBER

7. Conclusión

En mi opinión, el Either es excelente para manejar errores, ya que hace que el código sea más declarativo, legible y robusto. Además, el uso de métodos como fold y map aumenta dicha legibilidad, ya que potenciamos el qué queremos hacer y escondemos el cómo lo conseguimos.

Con Kotlin y sus posibilidades, podemos partir de una programación orientada a objetos que pasito a pasito se vaya acercando a una programación más funcional, abrazando las ventajas de ésta y combinándolas con las de la POO.

La entrada Either en Kotlin se publicó primero en Adictos al trabajo.

Sketchnotes

$
0
0

Sketchnotes o notas visuales, es un término acuñado por Mike Rohde en 2007 y no es más que una forma mucho más amena y divertida de tomar notas añadiendo una combinación de elementos visuales tales como: figuras, dibujos, tipografía dibujada a mano, formas, flechas, cuadros y líneas. En esta review me gustaría animarte a iniciarte en esta magnífica práctica y sobre todo convencerte de que no se trata de una habilidad que sólo unos pocos poseen.


Desde la primera vez que lo vi me encantó pero debo confesar que lo veía como algo difícil de hacer por mí, pensaba: «no dibujo tan bien como para hacer eso…». Pero mi espíritu agile me seguía diciendo que era una excelente forma de tomar apuntes y recordar lo hablado, sin necesidad de largos correos y aburridos resúmenes,  así que decidí ponerme en acción y comencé a investigar al respecto: asistiendo a meetups de bikablo, a talleres como el taller de sketchnoting con Javier Alonso (@oyabun) y a leer libros tales como: The Sketchnote handbook, Bikablo 2.0. Me gustaría compartir aquí todo lo que he aprendido hasta ahora y para ello me gustaría comenzar por una frase de la que puedo dar fe:

«Aunque no seas un artista o creas que no dibujas bien,

¡puedes crear Sketchnotes!»


Aunque resulte increíble la verdadera habilidad que se requiere para hacer sketchnotes no es la de saber dibujar bien, sino la de saber escuchar activamente, es decir, saber:

Enfócarte, centrando toda tu atención única y exclusivamente en el orador.

 

Eliminar: las distracciones que puedan surgir a tu alrededor. Ej: llamadas, mensajes y alarmas del móvil.

 

Sumergirte: prestando toda tu atención en la presentación.

 

Cuando haces Sketchnote todo tu cuerpo se involucra:

Oído, para escuchar las ideas que estás recibiendo. Cerebro para entender, interpretar y representar gráficamente las ideas y conceptos que estás escuchando. Todo esto mientras activas tus ojos y manos para plasmar esas representaciones sobre el papel.

A pesar de ello, un Sketchnote no es arte, no se trata de cómo dibujas, si no de cómo aterrizar las ideas que estas escuchando sobre el papel.  Aunque no puedas trazar una línea recta, puedes aprender a hacer sketchnotes con un poco de práctica, y la razón es que incluso un mal dibujo puede transmitir efectivamente una idea, sino fíjate en la siguiente imagen:

Independientemente de la calidad de cada uno, el mensaje está claro: es un gato.

En resumen, la clave para hacer sketchnotes es escuchar, sintetizar y visualizar.

     Sketchnote nació de la frustración de Mike Rohde, quien harto de intentar capturar hasta el más mínimo detalle en sus notas, obtenía apuntes eternos con sólo texto, pesados de leer y a los que nunca acudía una vez finalizados.  

     En enero del 2007, en una conferencia en Chicago, Mike Rohde decidió intentar algo nuevo y en vez de preocuparse por cada mínimo detalle, escuchó más atentamente, enfocándose en capturar las grandes ideas principales y expresarlas con gráficos, dibujos y formas. A esta nueva forma de tomar notas la denominó Sketchnotes.

El proceso para hacer sketchnotes obliga al cerebro a usar los dos hemisferios, porque activa las dos formas en que éste procesa la información: la verbal (conceptos como palabras) y la visual (conceptos como imágenes). Por lo tanto, hacer sketchnotes representa un reto constante al cerebro para transformar palabras en imágenes; es gracias a ello que obtenemos los siguientes beneficios:

  • Serás capaz de recordar más fácilmente y muchos más detalles que cuando sólo tomas notas planas. Esto se debe a que hacer un sketchnote desafía a tu cerebro de muchas más formas, lo que ayuda enormemente a recordar lo que pensabas, veías e incluso sentías cuando lo hiciste, aunque hayan transcurrido días, meses o incluso años.
  • Ayuda a mejorar la concentración y, por tanto, a centrar tu mente en el momento presente ya que cuando tu mente y tu cuerpo trabajan juntos hay poco espacio para las distracciones.
  • Hacer sketchnotes es relajante. Ayuda a dejar de centrarse en los detalles para enfocarse sólo en las grandes ideas principales.
  • Mejora la capacidad de escucha y de síntesis.
  • Tus apuntes serán divertidos, agradables de retomar, compartir y visualizar.
  • Hacer un dibujo es generalmente más rápido de crear que una descripción escrita.
  • A menudo, una idea compleja se puede transmitir más efectivamente a través de una imagen que mediante una descripción escrita u oral. Sino observa este ejemplo: 
  • La forma de aprendizaje mas común es en un 65% visual, un 35% auditivo y un 5% kinestésico, por lo que las notas visuales son mucho más aclaratorias que las notas planas.
  • Son únicos. Nunca existirán dos iguales, eso es porque en el proceso de síntesis, cada persona embebe de alguna manera su personalidad y sus experiencias, así, aunque dos personas tomen notas visuales de una misma presentación nunca serán iguales.


Los sketchnotes pueden ser de dos tipos según el momento en el que se realizan:

→ Sketchnote en tiempo real es aquel que es creado mientras escuchas una charla, presentación o reunión:  

  • No es tan duro como suena; sólo requiere práctica, ya que la rapidez y la efectividad son críticos cuando estás haciendo sketchnotes en tiempo real.
  • Principalmente requiere estar completamente sumergido en la presentación para poder decidir qué idea vale la pena y cuál no.
  • Tienen el inmenso beneficio de que cuando el evento termina tus notas están terminadas.

 

→ Sketchnote en dos-fases: consiste en hacer un sketchnotes en tiempo real pero en lápiz, que luego se perfecciona y se pasa a tinta.

Un sketchnotes en dos fases también ocurre cuando inicialmente se toman notas planas como de costumbre y luego son convertidas en notas visuales.

Este tipo de sketchnotes tiene las siguientes ventajas y desventajas:

  • Trabajar dos veces sobre tus ideas permite repasar y reforzar las ideas en tu memoria.
  • El sketchnotes a lápiz permite corregir errores pero también toma el doble de tiempo realizarlos.

El sketchnote en dos fases está bien si te preocupa mucho cometer errores o quieres iniciarte progresivamente a un ritmo más lento, pero no te quedes atascado ahí. El sketchnote en tiempo real es más fácil de lo imaginas.


El proceso para hacer un sketchnote es muy personal. Mike Rohde recomienda su proceso  como punto de partida para luego personalizarlo:

  1. Investigar: investiga el evento, al orador y el tema sobre el cual va a hablar. Esto te dará la visión y la confianza necesaria antes de hacer el sketchnote (sobre todo si el orador y/o el tema son nuevos para ti).  
  2. Reúne el material: lleva contigo varios cuadernos, múltiples bolígrafos, tu smartphone y si el evento es a oscuras quizás una booklight (lámpara de libro). Nunca se sabe cuándo un bolígrafo se puede quedar sin tinta o perderse uno de tus sketchbook.
  3. Llega temprano: busca el mejor sitio, preferiblemente debajo de algún foco, en el que puedas escuchar y ver bien al orador. (Recomendación: sentarte en el medio de una fila podría reducir las molestias o interrupciones causadas por las personas que llegan tarde o se van temprano).
  4. Crea un título: que contenga el título de la charla, el nombre del orador, la ubicación y la fecha. Crear este título antes de que el orador comience te permitirá enfocarte en la charla.
  5. Sketchnote: Cuando la charla comience, escucha con atención, sintetiza y comienza a dibujar. Cuando haces sketchnotes el tiempo apremia y lo mejor es plasmar lo primero que tu mente imagina al escuchar una idea.
  6. Fotografía: Tomar fotos de tu sketchnote al finalizarlo es una excelente forma de compartirlo en redes sociales y de hacer un respaldo de los mismos.   

  1. Despídete de la timidez y de la vergüenza: no compares ni juzgues tus notas visuales. Recuerda: Incluso un mal dibujo puede transmitir una idea efectivamente.
  2. Los beneficios de practicar sketchnotes se perciben tanto si lo haces en físico sobre un papel o digitalmente sobre cualquier dispositivo electrónico.
  3. Casi todo lo que te puedas imaginar se puede dibujar usando estas cinco formas básicas.
  4. Limita tu espacio: Pasa del gran cuaderno de notas a un pequeño cuaderno. Limitar el espacio de escritura te obliga a seleccionar cuidadosamente las ideas principales y a omitir los pequeños detalles.
  5. Cambia el lápiz por el bolígrafo o rotulador: Al escribir con rotulador no tienes la oportunidad de corregir, lo que es ideal porque ayuda a enfocarnos en plasmar la idea tal y como nos viene a la cabeza y no en la perfección de cómo plasmarla.
  6. Sé literal: cuando escuches una idea o concepto, simplemente dibuja lo primero que te venga a la cabeza. Intenta no perder tiempo pensando «¿qué dibujo para expresar X cosa…?» ¡Simplemente dibuja! Cuando haces sketchnotes en tiempo real, el tiempo apremia y lo más ágil es plasmar lo primero que te viene a la mente al escuchar esa idea.
  7. Improvisa y comienza a amar tus errores:  Cuando haces sketchnotes en tiempo real y con boli no tienes la oportunidad de corregir, así que lo mejor es improvisar. Ver e imaginar formas donde no las hay, como cuando éramos niños, es una gran habilidad que te ayudará a solventar o a matizar esos fallos.
  8. Enfócate en las grandes ideas: centrarte en las grandes ideas principales que resuenen en tu mente para convertirlas en notas visuales. Truco: aquellas ideas que te hacen asentir, fruncir el ceño o sonreír son ideas que muy probablemente valga la pena representar.
  9. ¡Atrévete! Haz un sketchnote en la próxima oportunidad que tengas, no necesitas ninguna preparación previa; comienza al menos reservando una pequeña área de tus notas planas para hacer un sketchnote de lo que sea: el título de la conferencia, del orador, etc.
  10. Práctica, práctica y práctica: no pierdas ninguna oportunidad para hacer sketchnotes, como todo en la vida, la escucha activa se fortalece y mejora con la práctica.
  11. Una gran técnica que a mí me ha resultado super útil es la de «dibujar frases»: y es porque sin prisas me permite poner en práctica mi habilidad síntesis y visualización. Y en consecuencia al punto nº 1, aquí os muestro mis primeros intentos para dibujar frases que hice en el meetup de bikablo. 

  1. «Dibuja videos» de presentaciones online: Busca presentaciones cortas de entre 15 y 20 min que te permitan practicar tu capacidad de escucha activa sin estar bajo la presión de una verdadera conferencia o presentación, e intenta plasmar gráficamente las ideas principales. Nuevamente te muestro mis primeros pasos en este punto

Espero os haya resultado de utilidad esta entrega, en la próxima me gustaría entrar más en detalle sobre la estructura de un sketchnote y algunas técnicas concretas para dibujar los elementos gráficos que te ayudarán a hacer tus apuntes más dinámicos y divertidos. Sería un detalle que me dejaras tu feedback o mejor aún: ¿Te atreves a crear tu primer sketchnote y compartirlo con nosotros? 

 

La entrada Sketchnotes se publicó primero en Adictos al trabajo.

ApacheDS: tests de integración utilizando esquemas externos

$
0
0

Introducción

Hace tiempo nuestro compañero Jose Manuel publicó un tutorial sobre como realizar test de integración accediendo a un LDAP usando el servidor embebido ApacheDS. En ese tutorial se hacía uso de esquemas estándar que incluye cualquier LDAP, como son: core.schema, inetorgperson.schema, etc.

Aunque lo normal es utilizar esquemas estándar, puede ocurrir que nos encontremos empresas que personalizan el LDAP añadiendo esquemas propios.

En este tutorial vamos un paso más y os enseñaremos a cargar esos esquemas propios y probar la funcionalidad que hayamos desarrollado.

Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.6 GHz Intel Core i7, 32GB DDR4).
  • Sistema Operativo: Mac OS Mojave 10.4.5
  • Oracle Java: 11.0.3
  • Maven: 3.6.0
  • Spring Boot 2.1.3.RELEASE
  • Spring Data LDAP: 2.1.3.RELEASE

Esquema propio

En el tutorial de Jose Manuel se uso un LDAP tradicional creando un conjunto de usuarios con los tipos: organizationalPerson, person e inetOrgPerson. Apoyándonos en dicho ejemplo, imaginaros que después de un tiempo se necesita añadir un atributo nuevo (customAttribute) a los usuarios. Este atributo no existe en ninguno de los tipos estándar de LDAP y tenemos que crear uno propio. Entonces hacemos nuestro desarrollo y queremos comprobar con un test de integración que todo funcióna correctamente.

Lo primero que tenemos que hacer es definir el esquema con sus objectclass y attributetypes que lo van a componer. A continuación os enseñamos cada uno de los ficheros.

El primero es «cn=custom.ldif», donde definimos el nombre del nuestro esquema «custom» y las dependencias con otros esquemas.

version: 1
dn: cn=custom,ou=schema
cn: custom
m-disabled: FALSE
objectclass: metaSchema
objectclass: top
m-dependencies: system
m-dependencies: core
m-dependencies: cosine
m-dependencies: inetorgperson

En el segundo «cn=custom/ou=objectclasses.ldif» definimos la raíz donde estarán todos nuestros tipos objectClass:

version: 1
dn: ou=objectClasses,cn=custom,ou=schema
ou: objectclasses
objectclass: organizationalUnit
objectclass: top

De la misma forma para los tipos de atributos. Creamos el fichero «cn=custom/ou=attributetypes.ldif»:

version: 1
dn: ou=attributeTypes,cn=custom,ou=schema
ou: attributetypes
objectclass: organizationalUnit
objectclass: top

Ahora creamos el fichero «cn=custom/ou=objectclasses/m-oid=1.3.6.1.4.1.42.2.27.32.1.ldif» con el objectClass «customPerson» al que asociaremos el atributo «customAttribute» más tarde.
Destacar aquí, el tipo de objeto que estamos creando: de tipo estructural, hijo de inetOrgPerson y con el atributo «customAttribute».

version: 1
dn: m-oid=1.3.6.1.4.1.42.2.27.32.1,ou=objectClasses,cn=custom,ou=schema
m-oid: 1.3.6.1.4.1.42.2.27.32.1
m-obsolete: FALSE
m-supobjectclass: inetOrgPerson
m-description: -
objectclass: metaObjectClass
objectclass: metaTop
objectclass: top
m-name: customPerson
m-typeobjectclass: STRUCTURAL
m-may: customAttribute
m-equality: objectIdentifierMatch

Y, por último, creamos el fichero «cn=custom/ou=attributetypes/m-oid=1.3.6.1.4.1.42.2.27.32.1.1.ldif» donde definimos el atributo «customAttribute», este atributo se ha configurado para que no puede ser modificado por el usuario (m-nousermodification: FALE), tampoco permite colecciones (m-collective: FALSE) y es de tipo string (m-syntax: 1.3.6.1.4.1.1466.115.121.1.15).

version: 1
dn: m-oid=1.3.6.1.4.1.42.2.27.32.1.1,ou=attributeTypes,cn=custom,ou=schema
m-collective: FALSE
m-singlevalue: TRUE
m-oid: 1.3.6.1.4.1.42.2.27.32.1.1
m-obsolete: FALSE
m-description: Custom Attribute
m-nousermodification: FALSE
objectclass: metaAttributeType
objectclass: metaTop
objectclass: top
m-syntax: 1.3.6.1.4.1.1466.115.121.1.15
m-usage: USER_APPLICATIONS
m-name: customAttribute

Y ya sólo nos queda modificar el fichero «autentia-identity-repository.ldif» usado en nuestros tests e incluir el nuevo campo:

version: 1

dn: o=autentia
changetype: add
objectClass: extensibleObject
objectClass: organization
objectClass: top
description: autentia

dn: ou=users,o=autentia
changetype: add
objectClass: extensibleObject
objectClass: organizationalUnit
objectClass: top
ou: users

dn: ou=groups,o=autentia
changetype: add
objectClass: extensibleObject
objectClass: organizationalUnit
objectClass: top
ou: groups

dn: cn=administrativos,ou=groups,o=autentia
changetype: add
objectClass: groupOfUniqueNames
objectClass: top
cn: administrativos
uniqueMember: cn=jmsanchez,ou=users,o=autentia
uniqueMember: cn=psanchez,ou=users,o=autentia

dn: cn=tramitadores,ou=groups,o=autentia
changetype: add
objectClass: groupOfUniqueNames
objectClass: top
cn: tramitadores
uniqueMember: cn=ablanco,ou=users,o=autentia
uniqueMember: cn=msanchez,ou=users,o=autentia

dn: cn=admin,ou=groups,o=autentia
changetype: add
objectClass: groupOfUniqueNames
objectClass: top
cn: admin
uniqueMember: cn=administrador,ou=users,o=autentia

dn: cn=jmsanchez,ou=users,o=autentia
changetype: add
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: customPerson
objectClass: top
cn: Jose Manuel Sánchez
sn: jmsanchez
uid: jmsanchez
mail: jmsanchez@autentia.com
customAttribute: dummy
userPassword:: cGFzcw==

dn: cn=psanchez,ou=users,o=autentia
changetype: add
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Pablo Sánchez
sn: psanchez
uid: psanchez
mail: psanchez@autentia.com
customAttribute: another dummy
userPassword:: cGFzcw==

dn: cn=msanchez,ou=users,o=autentia
changetype: add
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Mario Sánchez
sn: msanchez
uid: msanchez
mail: msanchez@autentia.com
customAttribute: second dummy
userPassword:: cGFzcw==

dn: cn=ablanco,ou=users,o=autentia
changetype: add
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: Alfonso Blanco
sn: ablanco
uid: ablanco
mail: ablanco@autentia.com
customAttribute: thrid dummy
userPassword:: cGFzcw==

dn: cn=administrador,ou=users,o=autentia
changetype: add
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: admin
sn: admin
uid: administrador
userPassword:: cGFzcw==

Test de integración

Ahora creamos nuestro test de integración, no vamos a explicar cada una de las anotaciones que vienen con la librería de ApacheDS ya que fueron explicadas en el anterior tutorial.

package com.autentia.tutoriales.apacheds.accounts.repositories;

import com.autentia.tutoriales.apacheds.accounts.config.LDAPConfiguration;
import com.autentia.tutoriales.apacheds.accounts.domain.UserAccount;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.integ.CreateLdapServerRule;
import org.apache.directory.server.ldap.LdapServer;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.ldap.repository.config.EnableLdapRepositories;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = LDAPConfiguration.class)
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 18888)})
@CreateDS(allowAnonAccess = true, name = "Autentia", partitions = {
        @CreatePartition(name = "Autentia", suffix = "o=autentia")})
@ApplyLdifFiles(value = {"autentia-identity-repository.ldif"})
@EnableLdapRepositories
public class UserAccountRepositoryIntegrationTest {
    @ClassRule
    public static CreateLdapServerRule serverRule = new CreateLdapServerRule();

    @Autowired
    private UserAccountRepository userAccountRepository;

    @Test
    public void shouldFindUserAccounyByLogin(){
        final String login = "jmsanchez";
        final UserAccount userAccount = userAccountRepository.findByLogin(login);

        assertThat(userAccount).isNotNull();
        assertThat(userAccount.getName()).isEqualTo("Jose Manuel Sánchez");
    }

    @Test
    public void shouldReturnCustomAttribute() {
        final String login = "jmsanchez";
        final UserAccount userAccount = userAccountRepository.findByLogin(login);

        assertThat(userAccount).isNotNull();
        assertThat(userAccount.getCustomAttribute()).isEqualTo("dummy");
    }
}

Si ejecutásemos nuestro test de integración veremos que nos dá un error de este tipo::

Caused by: org.apache.directory.api.ldap.model.exception.LdapException: ERR_04269 ATTRIBUTE_TYPE for OID customattribute does not exist!
    at org.apache.directory.api.ldap.model.schema.registries.DefaultSchemaObjectRegistry.lookup(DefaultSchemaObjectRegistry.java:235)
    at org.apache.directory.api.ldap.model.schema.registries.DefaultAttributeTypeRegistry.lookup(DefaultAttributeTypeRegistry.java:300)
    ... 15 more

Es debido a que nuestro LDAP no tiene cargado todavía nuestro esquema. Para que ApacheDS cargue esquemas externos, debemos añadir al classpath el fichero «apacheds-schema.index» dentro del directorio «META-INF».

Cuando ApacheDS localiza este recurso carga cada uno de los ficheros que vengan definidos en él. Si tomamos como referencia los ficheros generados en el apartado anterior, el fichero «apacheds-schema.index» quedaría así:

schema/ou=schema/cn=custom.ldif
schema/ou=schema/cn=custom/ou=attributetypes.ldif
schema/ou=schema/cn=custom/ou=objectclasses.ldif
schema/ou=schema/cn=custom/ou=objectclasses/m-oid=1.3.6.1.4.1.42.2.27.32.1.ldif
schema/ou=schema/cn=custom/ou=attributetypes/m-oid=1.3.6.1.4.1.42.2.27.32.1.1.ldif

Ahora si volvemos a ejecutar nuestro test de integración estará en verde.

Referencias

Conclusiones

Como habéis podido comprobar es muy sencillo con ApacheDS realizar test de integración de funcionalidades que acceden a un LDAP incluso si éste tiene esquema propios.

La entrada ApacheDS: tests de integración utilizando esquemas externos se publicó primero en Adictos al trabajo.

¿Puede una VPN ayudar a que las páginas web se carguen más rápido?

$
0
0

Una vez que cualquier usuario ha navegado en Internet a alta velocidad, no hay vuelta atrás. Nadie quiere una conexión lenta y todos aspiramos a alcanzar el desempeño más óptimo en la web. Por eso, algunos suelen preocuparse —o motivarse— al momento de usar una VPN.

Entre las consultas más comunes, destaca la duda de si puede una VPN ayudar a que las páginas web carguen más rápido o si, por el contrario, este servicio hace que la navegación se vuelva más lenta. La respuesta no es tan evidente. En realidad, depende del servicio, el uso y algunos factores adicionales que entran al juego.

Aquí veremos cuáles son las situaciones en las que una VPN puede aumentar la velocidad de carga y cuándo y por qué puede hacer más lenta nuestra conexión, así como también algunas soluciones eficientes y sencillas.

¿Cuándo una VPN puede aumentar la velocidad de carga?

Existen varias ventajas en cuanto a la velocidad de conexión al momento de usar una VPN. La más común es que nos pueden ayudar a combatir las limitaciones arbitrarias de algunos proveedores de servicio de Internet (ISP, por sus siglas en inglés).

Estas regulaciones suelen realizarse con respecto al ancho de banda, porque algunas compañías quieren cobrar más por la mejora de este servicio. Esta práctica se conoce en inglés como ISP throttling, y es bastante común en América Latina. Sin embargo, una VPN puede burlar esta restricción y, por ende, ayudarte a navegar más rápido.

Por otro lado, los expertos de Make Use Of explican un caso valioso en el que una VPN puede hacer que una página cargue más rápido: cuando el servidor VPN está ubicado en Europa y el usuario visita sitios web que cumplen con la regulación GDPR.

Algunas plataformas estadounidenses, por ejemplo, ejecutan diferentes versiones de su web: una que se adapta a las regulaciones europeas y suele ser mucho más liviana, y otra para el resto de los países, que contiene más publicidad y códigos de seguimientos que pueden afectar hasta por varios segundos la latencia.

También en este caso una VPN puede aumentar considerablemente la experiencia de navegación. ¿Visitas sitios que cumplan con las regulaciones GDPR?

Lo que puedes hacer si tu VPN afecta la velocidad de conexión

En algunos casos una VPN puede disminuir la velocidad de conexión a Internet. Los factores que afectan la latencia son muchos: la calidad del servidor, su ubicación, el tipo de cifrado, el protocolo, la cantidad de usuarios utilizándolo, etc. Aquí veremos algunas de las soluciones más eficientes para que mejores la velocidad de conexión con tu VPN:

Cambiar la ubicación del servidor VPN

Al igual que ocurre con algunos servicios de hosting, la distancia física entre tu ubicación y la del servidor VPN puede afectar la latencia. Pongamos por caso que estás en México, conectado a un servidor en Australia, y luego decides cambiarte a uno en Estados Unidos o Canadá, la velocidad de carga mejorará de forma evidente. Mayor cercanía, mayor rapidez.

Elegir otro nivel de cifrado

Es posible que si el nivel de cifrado de tu VPN es muy alto —y tal vez demasiado seguro— perjudique tu experiencia de navegación. El algoritmo de cifrado puede tardar mucho más en proteger tus datos según el tipo de seguridad.

Si no necesitas una protección demasiado alta, opta por una encriptación más ligera, de acuerdo a los softwares de reputación disponibles. Si, por ejemplo, solo quieres usar tu VPN para reproducir un programa de streaming, entonces solo necesitas ocultar la dirección IP.

Migrar a otro protocolo

La mayoría de las VPNs confiables proporcionan protocolos de conexión seguros y conocidos. Los que mejor protegen tus datos tienen mayores niveles de cifrado, mientras que otros —que también pueden ser confiables pero en menor medida— apuestan por la velocidad.

Si tu VPN lo permite, puedes cambiar a un protocolo conocido por su alta velocidad y buenos niveles de seguridad como OpenVPN, o a uno de los menos seguros pero recomendados para streaming como PPTP.

Optar por un servidor con menos usuarios

Además de tomar en cuenta la ubicación geográfica de tu servidor VPN, también es importante considerar la popularidad del servidor. Es posible que muchas personas estén usando el mismo de forma simultánea y que esto afecte la velocidad de conexión.

Por ejemplo, puedes cambiar tu servidor a uno con similar cercanía, pero que esté menos congestionado de usuarios, que te permita jugar online con tranquilidad y así tener menos riesgos de perder la partida.

Conclusión

Como puedes ver, son muchos los factores que pueden afectar la latencia. Por lo general, si contratas un servicio de VPN seguro y confiable, no deberías tener muchos problemas en la experiencia de navegación. Además, estos consejos y sugerencias te ayudarán a alcanzar la velocidad óptima que necesitas.

 

La entrada ¿Puede una VPN ayudar a que las páginas web se carguen más rápido? se publicó primero en Adictos al trabajo.


Comentando el Libro la Vaca Púrpura de Seth Godin

$
0
0
Hace unos días recibí en la oficina tres libros de Seth Godin regalo de Carlos Blé y es buena época para leerlos en la piscina.
Voy a comentar aquellas cosas que me han llamado la atención porque aunque en principio, cuando lo empiezas a leer, da la sensación de ser un libro anticuado (2002), siempre hay ideas atemporales.

Este libro se lee bien porque expone una idea en un par de hojas y da poca pereza leerlo a trozos.

Para introducir la vaca púrpura o productos/servicios que destacan pone el ejemplo de pasear por la campiña y ver vacas. Al principio sorprende a alguien de ciudad. Posteriormente se vuelve aburrido y monótono. Pero si de repente ves una vaca púrpura de nuevo se vuelve a captar la atención. El lema del libro es diferenciarte para transformar tu negocio.
Copio literalmente algunos párrafos interesantes (obviamente tienen su contexto en el libro) en los que tengo que pensar un poco más.
Habla de la diferencia entre la época de la televisión vs post-televisión:
  • Productos estándar vs productos extraordinarios.
  • Anuncios dirigidos a todos a anuncios vs dirigidos al «primer adoptante».
  • Miedo a fracasar vs miedo al miedo.
  • Ciclos largos vs ciclos cortos.
  • Cambios pequeños vs cambios grandes.

La forma de llegar al público es dirigirse a un nicho en lugar de a un mercado amplio. Los primeros adoptantes en este nicho comercial tienen una mayor disposición para escuchar lo que usted tiene que decir, es más probable que los transmisores en este nicho hablen de su producto, lo que es mejor, el mercado es bastante pequeño como para que unos pocos transmisores lo difundan hasta la masa concreta que necesita para crear un ideavirus.

Es inútil hacer publicidad dirigida a nadie (que no sean transmisores interesados e influyentes).

Elabore una lista de competidores que no traten de serlo todo para todos ¿Le están superando? Si pudiera elegir un nicho de mercado como objetivo (para dominarlo), ¿cuál sería?
Normalmente respondemos a nuestra aversión por las críticas escondiéndonos, evitando los comentarios negativos y con ello (irónicamente) garantizamos nuestro fracaso. Si la única forma de abrirse paso es ser extraordinario y la única manera de evitar las críticas es ser aburrido y estar seguro, la lección es obvia. O no.

La gente que evita hacer una carrera extraordinaria jamás llegará a ser líder. Decide trabajar para una empresa y funcionar de forma intencionada como un zángano anónimo, quedándose atrás para evitar el riesgo y las críticas.

Si graba un álbum super ventas o escribe un best seller rompedor, el dinero, el prestigio, el poder y la satisfacción consecuentes son inigualables. A cambio de correr el riesgo -del fracaso, del ridículo o de los sueños no cumplidos -, el creador de la Vaca obtiene una gran recompensa cuando acierta. Lo mejor de todo es que los beneficios tienen una vida media. No hay que ser extraordinario todo el tiempo para disfrutar de la ventaja. Starbucks era extraordinario hace años. Ahora es aburrido.

Si uno puede aparecer en una parodia, significa que tiene algo único, algo que se presta a hacer broma. La mayoría de las empresas tienen tanto miedo a ofender a alguien o de parecer ridículas que evitan cualquier experimento en este sentido.
¿Qué pasaría si durante una o dos temporadas en lugar de lanzar nuevos productos reintrodujera algún clásico? ¿Qué sorprendente novedad podría ofrecer la primera temporada que regresarse (con los ingenieros descansados?)

La torre inclinada de Pisa recibe millones de visitas cada año. Es, tal y como la anuncian, una torre inclinada.¿Tiene un eslogan, una afirmación de posición o un alarde extraordinario que sea cierto? ¿Es consistente? ¿Vale la pena transmitirlo?

En casi todos los mercados, el puesto aburrido ya está ocupado. El producto diseñado para gustar a la mayor cantidad de público posible ya existe y desplazarlo es muy difícil. El verdadero crecimiento llega con productos que molestan, ofenden, no gustan, son demasiado caros o demasiado baratos, demasiado complicados, demasiado simples… demasiado algo (para algunas personas, para otros son perfectos).

Una vez cruzada la linea entre lo extraordinario y lo provechoso, que otro equipo le saque partido. Convierta sus productos en servicios y sus servicios en productos, que florezcan mil variantes.

Cuando Comedy Central puso a prueba South Park ante un focus group, batió un récord: recibió 1,5 puntos sobre 10 entre las mujeres. Tres mujeres del grupo lloraron, de lo poco que les había gustado. ¿Aterrador? Cierto. ¿Extraño? Para algunos. Pero el grupo que importaba (adolescentes y aquellos que actuaban como si lo fueran) difundieron el mensaje y la serie fue un éxito.
¿Por qué los ejecutivos de la industria del cine cruzan medio mundo en avión para asistir al festival de Cine de Cannes? Aunque las fiestas siempre son divertidas, no justifican la molestia y los gastos de viaje. La razón es simple: los ejecutivos saben que allí va a ocurrir algo extraordinario.

Vaya un paso más allá, o dos, identifique a un competidor que se considere siempre al límite y supérelo. Cualquier cosa que ellos hagan, hágalo usted más. Mejor y más seguro, haga lo contrario que ellos. Encuentre cosas que no se hacen en su industria y hágalo.

Bueno, aquí lo dejo. La última idea a destacar es la palabra otaku, que describe algo que es más que un hobby pero menos que una obsesión. Algo provoca un deseo irrefrenable de cruzar toda la ciudad para probar algo nuevo.

La entrada Comentando el Libro la Vaca Púrpura de Seth Godin se publicó primero en Adictos al trabajo.

El Polimorfismo es la Clave

$
0
0

 

En este artículo analizamos, a través de un ejemplo, el Polimorfismo como característica clave de los lenguajes orientados a objetos.

Índice de contenido

1. Introducción

Clean Code, Principios de diseño (SOLID, DRY, IoC…), inyección de dependencias, patrones de diseño… son todos conceptos, técnicas, principios, que nos aportan conocimientos indispensables para desarrollar software de calidad, pero hay una característica de los lenguajes orientados a objetos que es clave para implementar y comprender la mayoría de estos conceptos más avanzados: El polimorfismo.

La palabra Polimorfismo significa «múltiples formas». En el caso de la programación orientada a objetos (POO) el polimorfismo está más ligado al comportamiento, por lo que pudiéramos ver más su significado como «múltiples comportamientos». Más formalmente podemos definir el polimorfismo como la característica que le permite a un cliente enviar un mensaje a objetos de diferentes tipos sin conocer exactamente el tipo que se está usando. El único requisito que deben de cumplir estos objetos es saber responder a dicho mensaje. De esta manera, bajo un mismo símbolo (el método o mensaje usado) se van a ejecutar comportamientos diferentes dependiendo de a qué objeto apunte la variable de referencia que usamos para enviar el mensaje.

Aunque en este artículo nos vamos a centrar en el polimorfismo de tipo inclusión o controlado por la herencia (subtype polymorphism), existen otros tipos de polimorfismos que es conveniente conocer.

2. Tipos de polimorfismo

Luca Cardelli en el artículo On Understanding Types, Data Abstraction, and Polymorphism agrupa los tipos de polimorfismo en las siguientes categorías:

Universal (True polymorphism)

  • Parametric: Se refiere a la parametrización de tipos (Genericidad).
  • Inclusion o subtyping: Es al que nos referimos casi siempre cuando hablamos de polimorfismo en los lenguajes de POO. Está basado en la herencia/implementación de clases/interfaces.

 

Ad-hoc (Apparent polymorphism)

  • Overloading: Sobrecarga de métodos.
  • Coersion: Casting implicito y explicito.

 

Solo la primera categoría agrupa los tipos que se conocen como polimorfismo verdadero porque es en tiempo de ejecución cuando se determina que comportamiento ejecutar (Late binding). El resto se resuelve en tiempo de compilación, por lo que podemos decir que es un polimorfismo aparente.

Por ejemplo, en la sobrecarga de métodos (Overloading) puede parecer que el método sobrecargado se comporta de varias formas, pero en realidad se invoca a distintos métodos dependiendo del número, orden y tipo de los parámetros. Es decir, el compilador reemplaza la llamada al método a partir de la signatura de éste (Static binding).

3. Un ejemplo

Veamos el uso del polimorfismo a través de un ejemplo sencillo. Los requisitos son los siguientes: dado un texto plano y el nombre de un algoritmo de encriptación, el programa tiene que imprimir por pantalla el texto encriptado utilizando dicho algoritmo. Para implementarlo vamos a utilizar el patrón de diseño Strategy combinado con una factoría que nos dará el algoritmo de encriptación a utilizar a partir del nombre de éste.

import java.util.HashMap;
import java.util.Map;

public class PolymorphismDemo {

    public static final void main(String[] args) {
        String plainText = args[0];
        Algorithm algorithm = Algorithm.valueOf(args[1]);

        EncryptionStrategy encryptionStrategy = EncryptionStrategyFactory.getInstance(algorithm);

        String encryptedText = encryptionStrategy.encrypt(plainText);

        System.out.println(
            String.format("Text encrypted using '%s' algorithm: %s", 
                algorithm, encryptedText)
        );
    }

    enum Algorithm {
        AES, TWOFISH
    }

    interface EncryptionStrategy {
        String encrypt(String input);
    }

    static class EncryptionStrategyFactory {

        public static Map<Algorithm, EncryptionStrategy> encryptionStrategies = new 
            HashMap<Algorithm, EncryptionStrategy>();

        static {
            registerStrategy(Algorithm.AES, (input) -> "aes:" + input);
            registerStrategy(Algorithm.TWOFISH, (input) -> "twofish:" + input);
        }

        public static EncryptionStrategy getInstance(Algorithm name) {
            return encryptionStrategies.get(name);
        }

        public static void registerStrategy(Algorithm algorithm, EncryptionStrategy encryptionStrategy) {
            encryptionStrategies.put(algorithm, encryptionStrategy);
        }

    }

}

 

En la línea 12 es donde ocurre la magia. El cliente, a través del mensaje ‘encrypt’, va a ejecutar comportamientos diferentes dependiendo de a qué estrategia concreta apunta la variable encryptionStrategy, sin conocer realmente el tipo de algoritmo que se está utilizando.

Indiscutiblemente estamos aplicando otros conceptos de la POO. Nos estamos abstrayendo porque el código cliente que usa el algoritmo de encriptación está expresando en términos generales, además representamos el comportamiento común en la abstracción EncryptionStrategy. Encapsulando porque los detalles de cada implementación se ocultan. Aunque no usemos la herencia, las estrategias concretas implementan la interfaz EncryptionStrategy creando una relación Is-A entre diferentes objetos. Pero realmente, es la posibilidad de ejecutar comportamientos diferentes bajo un mismo mensaje lo que le da flexibilidad al código.

Si hacemos una análisis de los conceptos que hemos aplicado. Tenemos el principio de Abierto/Cerrado, añadir nuevos algoritmos no implica modificar el código cliente, solo tendríamos que registrarlo en la factoría. Tenemos el patrón de diseño Strategy, que nos da solución a la problemática de cambiar algoritmos en tiempo de ejecución. Y en la base, como característica del lenguaje, tenemos el polimorfismo, que nos permite ejecutar distintos algoritmos de encriptación utilizando una misma llamada (encrypt(input)) sin conocer exactamente el algoritmo específico que se está utilizando.

3. Conclusiones

Es importante entender el concepto de polimorfismo como base para entender otros conceptos más avanzados. Principios de diseño como Abierto/Cerrado e IoC, técnicas como la inyección de dependencias y muchas de las implementaciones de patrones de diseños tienen como base esta característica clave del lenguaje.

3. Referencias

La entrada El Polimorfismo es la Clave se publicó primero en Adictos al trabajo.

Paneles basados en calendario y priorizados por fecha de entrega

$
0
0

 

En Autentia principalmente nos dedicamos a construir software de calidad (normalmente en entornos Java) aplicando prácticas ágiles. En muchos casos quien nos llama quiere que sea el catalizador del cambio en un área o empresa.

Para que construir software de calidad sea posible, parte de la empresa tiene que cambiar un poquito y no todo el mundo siquiera es consciente, ven los modelos ágiles como algo lejano de desarrolladores. Estos son algunos cambios que necesitamos:

– La dirección general tiene que querer invertir en un proyecto piloto no gigante.

– Compras debe aceptar contratar de otro modo, perfiles más especializados a otras tarifas.

– Las áreas de negocio deben definir los proyectos de modo diferente: producto mínimo viable basado en épicas e historias.

– Las oficinas de proyecto clásicas deben trabajar por ciclos cortos o Sprints haciendo el alcance variable y sin renunciar a la calidad.

– El área de calidad deben integrarse en los equipos y no verificar al final de proyecto (en cascada).

– El área de sistemas tiene que aprovisionar la infraestructura para disponer de entornos adecuados y nuevas herramientas tecnológicas (entrega continua).

– Los responsables deben apostar por un ritmo sostenible, no tratar de explotar al equipo interno o proveedor, crear solo un equipo.

Obviamente, hay áreas y personas más cerca y más lejos de todo esto.

Mientras mi equipo empieza a trabajar en el proyecto, a mí me gusta involucrarme personalmente en divulgar a esas áreas sobre otros modelos de organización. De este hablé en el #aos2019.

Que no esté muy claro qué es ser ágil (y qué es Agile) es una ventaja y un inconveniente a la vez. Todo el mundo quiere ser ágil y trato de demostrarles que hay prácticas, principios y valores de Lean/Scrum/Kanban/XP  que se pueden aplicar básicamente a todo y permite hacer cambios desde donde se está.

Les trato de enseñar poco pero utilizable esa misma semana. Creo que la precisión y la capacidad de captar la atención en un tiempo corto no se llevan bien, por tanto, tratar de demostrar todo lo que sabes siendo muy preciso (para satisfacer a un posible asistente que sepa mucho) no es más que un ejercicio de ego que puede hacer que todo el mundo se pierda o aburra.

Es fácil exponer a los asistentes que uno de los métodos ágiles (sin siquiera nombrar Scrum) tiene 3 pilares: transparencia, inspección y adaptación. Esta es una captura de la propia guía de Scrum. https://www.scrumguides.org/docs/scrumguide/v1/scrum-guide-es.pdf

En un departamento cualquiera se puede crear un panel Kanban para visualizar de un modo transparente el trabajo en curso (sí, uso los pilares de Scrum y un panel Kanban). Para los que busquéis más precisión (paneles vs método) os invito a que leáis este artículo y/o vídeo https://jeronimopalacios.com/kanban/

Experimentalmente hay un ejercicio para llevarles poco que me encanta y es pedirle a un director de un área que escriba en tres post-it (por prioridad) cuál es el trabajo más importante que tiene que hacer una persona asistente que dependa de él/ella para la siguiente semana. Luego pido a la misma persona que haga lo mismo, definir las tres cosas más importantes por prioridad. ¿Crees que coincide?  Ya os digo que no. Esto hace evidente qué podemos cambiar para saber qué estamos haciendo, si es lo más importante (y para quién), inspeccionar ese progreso y proponer adaptaciones rápidas (volver a los pilares de Scrum).

Ahora les pido a tres personas más que definan qué es lo más importante que tienen que hacer a corto plazo, pero no ordenado por importancia para ellos, sino por importancia para el cliente, entrega de valor. Muchos de ellos se quedan perplejos porque trabajan en silos, no saben lo que hacen los compañeros, no entienden la importancia para el cliente o siquiera saben qué es importante para su responsable.

Aquí se detectan rápidamente des-alineamientos y carencias en la comunicación. Una charla del responsable puede aclarar las cosas y podemos conseguir una lista priorizada de tareas. Es más, si esto se hace una semana después los miembros del equipo habrán adquirido los criterios para determinar qué es más valioso con nuevas tareas. Podrán empezar a auto-gestionarse (tras varios ciclos) sin que se pierda visibilidad ni control por el responsable pero sin tener que preocuparse de asignar trabajo.

 

Voy a pintarlo con una herramienta sencilla para entenderlo bien. 

https://cardboardit.com/

Si creamos un panel con las tareas priorizadas y añadimos columnas en curso y terminado, podremos estar seguros de que todo el mundo está trabajando en lo que más valor aporta y ahorrarnos reuniones para preguntar qué estamos haciendo y si va bien, el panel es un radiador de información.

Además, podemos crear etiquetas para visualizar qué va mejor o peor.

 

O podemos cambiar las columnas para visualizar cuándo van a estar terminados ciertos trabajos. O incluso combinar las dos cosas simplemente añadiendo marcas a los post-it.

Ya muchos departamentos se quedan flipados con la sencillez de este sistema. Obviamente, esto no vale para nada si la gente trabaja como lo hacía antes y luego lo refleja en el panel (o no lo cambia en semanas).

La gracia es que el panel organice el trabajo (y ayude a cosas como limitar el trabajo en curso) y se actualice cotidianamente. Que las personas diariamente se comuniquen al lado del panel para sincronizarse también parece una buena idea (esto es tanto de Scrum como de Kanban).

De un modo sencillo se pueden crear paneles como primer paso a un método Kanban mejor entendido.

Ahora bien, hay gente que me dice que tiene decenas de tareas entre las que elegir, que entran muchas tareas nuevas, que la prioridad cambia cada día o que lo que manda es el deadline, la fecha máxima de entrega.

Se me ocurrió que, utilizando este panel como base y rotándolo un poco podía conseguir crear otro panel donde simultáneamente tengamos la importancia, el deadline, las tareas en curso y la deuda que vamos acumulando.

Os explico cómo que me parece muy sencillo.

Primero, simplemente vamos a rotar el panel 90 grados a la derecha. Es el mismo panel, nada más que en vertical.

Ahora creamos un calendario en la parte de To Do como columnas y como filas ponemos la importancia que tiene la tarea (valor que aporta). El post-it rosa nos vale para marcar el día en el que estamos.

Imaginemos que queremos ahora poner una tarea en el panel. Supongamos que somos un departamento legal que tenemos que revisar contratos, hacer NDAs (acuerdos de confidencialidad) o cualquier otra tarea dentro de un proceso. Demos por hecho que siempre hay mucho que hacer, tareas distintas de distinta importancia (cliente y posible problema) y que lo mismo no llegamos a todo.

Empecemos. Llega una revisión de un contrato importante y lo tenemos que tener para el miércoles.

Podemos hasta estimar/predecir que necesitamos un par de días.

Puede llegar un NDA que requiere un par de horas de revisión y no es un contrato muy importante, pero tiene prisa para el que nos lo pide (la vida misma). Llega también la revisión de otro contrato y este no es tan importante pero es más elaborado.

Llegan tres NDAs más, revisión de un contrato anterior y el recurso de una multa (ya con días justos para entregar). Si os fijáis, se empieza a complicar la cosa pero podemos ver toda la información de una tacada y tenemos variables para determinar por cuál empezamos.

Es más, hay veces que a estas tareas hay que añadirles tareas diarias recurrentes, podríamos también representarlas, al menos una primera vez.

Pongamos que empezamos a trabajar. Parece que lo lógico es empezar por las labores más a corto plazo y recurrentes diarias obligatorias, a menos que haya una acumulación de labores importantes a entregar en un deadline y que se nos van a acumular.

Fijaros que al final del primer día, hemos terminado así. Si lo miramos detenidamente, hemos realizado las labores más urgentes para ese día, se nos ha quedado a medias el informe diario y hemos empezado un contrato con tiempo.

Supongamos ahora que entra otro montón de tareas a hacer en el día, más las recurrentes.

Obviamente no vamos a llegar a todas las tareas. Ahora, es hora o bien de reordenar y lanzar tareas a otros días o dar por hecho que se va a acumular deuda.

Bueno, supongo que lo habéis captado.

Obviamente se pueden añadir más filas para modelar el proceso de ese departamento legal, por ejemplo: recibido, el proceso, pendiente de revisión, pendiente comunicación, pendiente facturación, terminado.

También se puede fácilmente empezar a sacar números: tiempo de proceso, tiempo medio de demora por prioridad, etc.

Bueno, espero que a alguien le guste la idea y le pueda ser útil para dos cosas:  primero, entender que diferentes actores de la organización tienen distinto interés, tiempo, entendimiento sobre otros modelos de gestión y que aportándoles algo que puedan ver útil a su propio día a día te pueden dar cuartelillo para trabajar de otro modo en áreas técnicas. Segundo, que introducir un calendario no modifica la esencia del panel.

También añadiría que hacer que alguien que te pide ayuda porque se organice mal, use esta herramienta puede ayudar a definir unas políticas más explícitas de priorización o puede ayudar a conseguir más recursos para que el ritmo sea más sostenible.

Espero vuestro feedback.

La entrada Paneles basados en calendario y priorizados por fecha de entrega se publicó primero en Adictos al trabajo.

Pon un mapa del globo terráqueo en tu web

$
0
0

En este tutorial veremos varias alternativas para poner un mapa del globo terráqueo en nuestra web y añadir marcadores y otros elementos a nuestro gusto.

Índice de contenidos.

Introducción

Hace poco he tenido que investigar cómo mostrar un mapa del globo en una aplicación web y creo que es lo suficientemente interesante como para compartirlo aquí. La idea es dar una pequeña explicación de qué es lo que se está buscando, qué herramientas hay, comentar ligeramente cómo funcionan y las diferentes alternativas que tenemos.

Destacar que yo soy desarrollador backend, por lo que mis conocimientos de front y en especial de Javascript son bastante limitados. Pero mientras se entienda la idea estoy seguro que alguien con más conocimientos que lea esto será capaz de implementarlo de una mejor manera.

¿Qué pinta tiene un mapa del globo?

Estoy seguro que muchos habremos trabajado en algún momento con mapas. Algo del estilo de lo que vemos habitualmente en Google Maps. Pero ese mapa es plano. El mismo que podemos tener  en la pared y nos acercamos para verlo en detalle, pero al alejarnos seguimos estando ante una visión rectangular del planeta.

Mapamundi plano

Lo que buscamos nosotros es algo distinto. Empezar con una visión de todo el globo, como si estuviéramos viendo el planeta desde el espacio. Y a partir de ahí ya acercarnos a donde queramos. El efecto es mucho más llamativo así y las animaciones cuando cambias de un punto a otro se ven mucho mejor.

Además tiene una ventaja en la representación del terreno. Esto es solo un dato curioso, así que si tienes interés solo en lo técnico puedes ir a la siguiente sección.

El mapamundi plano que conocemos todos, y el más usado, sigue el modelo de Mercator. Sin entrar en detalles de cómo está hecho, las imposibilidad de transportar la superficie de una esfera (y encima irregular como la Tierra) a un plano de dos dimensiones causa que haya deformaciones. En concreto en este modelo cuanto más nos acerquemos a los polos más grandes parecen los terrenos.

El ejemplo que se suele poner es Groenlandia, que según el mapa en el que se mire puede llegar a parecer casi del tamaño de África pero en realidad cabría 15 veces en él y España tiene casi la misma longitud de este a oeste. Aunque también hay otros casos curiosos como que Brasil no es tan pequeño como parece y no se aleja tanto de la extensión de Rusia. Incluso hay una web que permite comparar los tamaños reales de los países entre sí ajustando su tamaño según los arrastramos por el mapa.

Como decía os lo cuento por curiosidad pura y dura y no tiene nada que ver con lo que vamos a hacer aquí, pero no deja de ser interesante y una de las cosas que permite los mapas como los que vamos a usar nosotros es que no se produce esta distorsión.

La Tierra desde el espacio

Pero volvamos a lo que nos ocupa…

¿No podemos usar Google Earth para esto?

Empecemos por lo obvio. Si queremos mostrar algo como lo que tenemos al abrir Google Earth, ¿por qué no lo intentamos usar? De hecho lo primero que busqué fue una API, pues suponía que habría algo similar a lo que ofrece Google Maps. Aunque pudiera ser de pago al menos sería un punto de partida o podría hacer una prueba de concepto. Pero no es así. En efecto, Google Earth tuvo en su momento una API pero la deprecaron a finales de 2015 porque estaba desarrollada sobre una tecnología que tenía fallos de seguridad. Desde entonces no la han vuelto a desarrollar. Tienen algunas herramientas con las que se integra y que se pueden usar de forma gratuita si no se hace con fines lucrativos y se puede tratar con ellos el usarlo para un negocio. Pero son para tareas como la analítica de datos o la generación de animaciones y vídeos, lo que se aleja mucho de nuestro objetivo. Lo más parecido es Google Earth Enterprise, que nos permite tener una versión liberada de Google Earth en nuestro servidor, pero tampoco es lo que buscamos.

En un futuro habrá que tener en cuenta en este aspecto a Google Maps. En verano del pasado 2018 cambiaron la versión de escritorio para que al quitar zoom llegásemos al mapa esférico en lugar de al tradicional. Lamentáblemente desde entonces aún no han migrado al resto de plataformas, por lo que por el momento no nos sirve como solución. No obstante bastante gente se lo ha pedido ya e imagino que en algún momento lo acabarán implementando de forma genérica.

Pero por el momento no nos queda otra que buscar una forma distinta de lograrlo.

Las alternativas que tenemos

Haciendo una búsqueda un poco exhaustiva me he encontrado con varias herramientas que pueden sernos de utilidad, aunque aquí solo voy a comparar las tres que me han parecido más interesantes.

CesiumJS

La primera opción con la que os encontréis seguramente será CesiumJS. Una herramienta web open source y gratuita que permite mostrar un mapa del globo terráqueo y hacer un montón de cosas con él. La cantidad de opciones es apabullante: desde ponerle simplemente un par de marcadores hasta crear modelos 3D o moverlos por el mapa. Además tienen bastantes ejemplos en su web con el código fuente que usan, lo que junto a la documentación que tiene hace que a priori no parezca excesivamente complicado desarrollar con él.

El único problema, por llamarlo de alguna forma, que tiene es que parte de las opciones no son enteramente gratuitas. Por debajo se conecta al servidor de Cesium Ion, que es donde se hacen cálculos y se guardan assets como los modelos 3D. Además tiene privados una parte de los mapas que se pueden visualizar sobre el globo. Sin embargo, aún así tendremos a nuestra disposición los mapas de Mapbox, OpenStreetMap y ESRI, que tienen una calidad bastante buena.

Sobre la licencia, tiene 3 tipos de cuentas. La Community es gratuita y nos permite trabajar con ella siempre que sea para uso personal y no para un negocio. Además tiene algunos límites, pero nada especialmente serio de primeras. Luego hay dos cuentas para uso comercial, que básicamente se diferencian en el precio y en el límite de almacenamiento y peticiones.

En principio, si solamente queremos ver el globo y añadir marcadores, los mapas de Mapbox van muy bien y no necesitaríamos contratar Cesium Ion. Pero creo que está bien comentarlo por si a alguien le pudiese ser de utilidad.

Vamos a ver un ejemplo muy sencillo de su uso.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <script src="https://cesiumjs.org/releases/1.58/Build/Cesium/Cesium.js"></script>
    <link href="https://cesiumjs.org/releases/1.58/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
    <style>
      #cesiumContainer{top: 0; right: 0; bottom: 0; left: 0; position: absolute;}
    </style>
  </head>
  <body>

    <div id="cesiumContainer"></div>
    
    <script>
      Cesium.Ion.defaultAccessToken = 'yourCesiumAccessToken';

      var viewer = new Cesium.Viewer('cesiumContainer', {
        animation : false,
        baseLayerPicker : false,
        fullscreenButton : false,
        homeButton: false,
        infoBox : false,
        sceneModePicker : false,
        timeline : false,
        imageryProvider : new Cesium.MapboxImageryProvider({
          mapId : 'mapbox.comic',
          accessToken : 'yourMapboxAccessToken'
        })
      });
      
      viewer.entities.add({
          position : Cesium.Cartesian3.fromDegrees(-6.371721, 39.474907),
          point : {
              pixelSize : 15,
              color : Cesium.Color.DARKRED,
              outlineWidth : 2
          }
      });

      var height = 20000000;
      viewer.camera.setView({
          destination: Cesium.Cartesian3.fromDegrees(-15.5845400, 28.1375500, height)
      });
    </script>
  </body>
</html>

El código es realmente sencillo:

  1. Importamos los ficheros js y css de Cesium.
  2. Ponemos un css para que el mapa ocupe toda la pantalla.
  3. Creamos el div donde vamos a tener nuestro mapa.
  4. Creamos el script que vamos a usar.
    1. Definimos el token de acceso a Cesium Ion, que se puede no hacer y usar el de por defecto.
    2. Inicializamos el viewer asociándolo al nombre del div que creamos antes. Los parámetros booleanos son simplemente para quitar muchos componentes de la interfaz que vienen por defecto y yo quería que apareciese lo más simple posible. Se inicializa el proveedor del mapa, en este caso se inicializar el de Mapbox para usar el mapa mapbox.comic (luego veréis que el estilo visual es un poco raro, también tienen mapas normales). Toda esta llamada podría ponerse con valores por defecto y quedar simplemente en new Cesium.Viewer(‘cesiumContainer’) y seguiría funcionando.
    3. Añadimos un punto en el mapa indicando las coordenadas y las propiedades que queremos.
    4. Cambiamos la posición de la cámara para que empiece apuntando a una zona desde la que se ve mejor el punto que hemos añadido.

Si lo abrimos en nuestro navegador nos quedará algo como esto:

Resultado del código de CesiumJS

Tenemos un mapa completo al que le he dejado los widgets de buscar una ciudad y ayuda. En el mapa además está el punto que hemos creado y la cámara aparece donde le hemos dicho. Además si pinchamos en el punto se acerca automáticamente. Obviad que la combinación de colores no es la mejor del mundo, pero por algo me dedico al backend.

Como veréis ha sido muy fácil inicializarlo y añadir un punto donde hemos querido. Y se podría haber hecho incluso con menos código de haber querido dejarle la interfaz que trae por defecto.

WebGL Earth

Otra alternativa completamente gratuita que permite mostrar un mapa básico y hacer cosas simples con él es WebGL Earth. Por debajo utiliza precisamente Cesium para renderizarlo todo, por lo que visualmente no se diferencia mucho de él. Lo único destacable en este aspecto es que el comportamiento por defecto difiere en que no le añade la interfaz que a mí a priori me parece innecesaria.

Es bastante fácil de usar, pero ello se debe en parte a que es muy limitado. Así como Cesium tiene muchísimas opciones y seguramente no se llegue a necesitar ni la mitad para algo medianamente complejo, WebGL Earth peca de lo contrario. Solamente nos deja visualizar el mapa, añadir nuevas capas y visualizar marcas o polígonos. Da bastante juego solo con esto y es muy sencillo de usar, pero puede que se quede corto si se quiere algo complejo.

Vamos a ver también un ejemplo de cómo utilizar WebGL Earth para lograr algo muy similar a lo que teníamos con Cesium.

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <script src="http://www.webglearth.com/v2/api.js"></script>
    <style>
      #mapContainer{top: 0; right: 0; bottom: 0; left: 0;
                 background-color: #000; position: absolute !important;}
    </style>
  </head>
  <body>
    <div id="mapContainer"></div>

    <script>
      var earth = new WE.map('mapContainer', {
        atmosphere: true,
        center: [28.1375500, -15.5845400],
        altitude: 20000000, 
        sky : true
      });
      
      WE.tileLayer('https://api.mapbox.com/v4/mapbox.comic/{z}/{x}/{y}.png?access_token=yourMapboxAccessToken', {
      }).addTo(earth);

      var marker = WE.marker([39.474907, -6.371721]).addTo(earth)
      marker.bindPopup('<b>No soy un punto, pero al menos me podéis ver ; )</b>');
    </script>
  </body>
</html>

El procedimiento es muy similar al que teníamos en Cesium:

  1. Cargamos el js de la herramienta.
  2. Ponemos un css para que el mapa ocupe toda la pantalla.
  3. Creamos el div donde vamos a tener nuestro mapa.
  4. Y por último el script.
    1. Creamos el mapa con las opciones por defecto que queremos y lo asociamos al div que ya tenemos. En este caso las opciones de la vista se inicializan aquí, junto con otras opciones visuales.
    2. Añadimos la capa con el mismo mapa de Mapbox que usamos en el ejemplo anterior.
    3. Añadimos un marcador con un pop up. WebGL Earth no tiene la funcionalidad de añadir puntos como lo hace Cesium, así que esta es la aproximación más similar que he encontrado. Se podría cambiar el icono para que fuese un círculo y quitar el popup, pero considero que para el ejemplo es suficiente así.

Si abrimos el código anterior en el navegador nos encontraremos con esto, aunque inicialmente el popup está cerrado hasta que no hacemos click en el marcador.

Resultado del código de WebGL Earth

El resultado es muy similar a lo que hemos conseguido con Cesium, por lo que para casos básicos podría ser suficiente.

Open Globus

La tercera de las opciones es Open Globus, una herramienta muy similar a las anteriores. Es gratuita, completamente open source y la API es bastante extensa. Además tiene varios ejemplos de las distintas opciones que permite implementar.

Lo que sí es que con esta herramienta me he encontrado dos problemas. El primero es que, al menos en los ejemplos que he podido hacer, el zoom no está demasiado bien afinado, se mueve excesivamente rápido y cuando la vista se aleja un poco la Tierra desaparece y en ocasiones es necesario recargar la página. El segundo es que no todos los ejemplos que tienen funcionan como se presupone se puede ver como falla al cargar algunos recursos, o al menos no lo hacen en mi navegador. Es posible que sea solo un tema de configuración. Desde luego si funciona como debe es una alternativa muy fuerte a Cesium.

Aún con esos problemas tiene funcionalidades muy interesantes, como por ejemplo añadir una capa de vídeo sobre el mapa. Recomiendo echarle un vistazo aunque solo sea por curiosear.

Veamos con un ejemplo cómo se utiliza.

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://www.openglobus.org/og.css" type="text/css">
    <script src="https://www.openglobus.org/og.js"></script>
    <style type="text/css">
        #mapContainer{top: 0; right: 0; bottom: 0; left: 0; position: absolute !important;}
    </style>
  </head>
  <body>
    <div id="mapContainer"></div>
    <script>
        let mapbox = new og.layer.XYZ("MapQuest Satellite", {
            isBaseLayer: true,
            url: "https://api.mapbox.com/v4/mapbox.comic/{z}/{x}/{y}.png?access_token=yourMapboxAccessToken",
            visibility: true
        });

        let globe = new og.Globe({
            "target": "mapContainer",
            "layers": [mapbox],
            "sun": {
                "active": false
            },
            'controls': [
               new og.control.MouseNavigation({ autoActivate: true }),
               new og.control.KeyboardNavigation({ autoActivate: true }),
               new og.control.TouchNavigation({ autoActivate: true })
           ]
        });

        new og.layer.Vector("Markers")
        .add(new og.Entity({
            lonlat: [-6.371721, 39.474907],
            billboard: {
                src: "./location-158934.svg",
                width: 30,
                height: 80
            }
        }))
        .addTo(globe.planet);

        globe.planet.viewExtentArr([-75, -45, 75, 55]);
    </script>
  </body>
</html>

No os sorprenderá a estas alturas que sea bastante similar a los anteriores.

  1. Cargamos los recursos de Open Globus.
  2. Ponemos estilo al div que tendrá el mapa para que ocupe toda la pantalla.
  3. Definimos el div donde estará el mapa.
  4. Creamos el script.
    1. Inicializamos la capa con el mapa de Mapbox que hemos estado usando hasta ahora.
    2. Inicializamos el globo con ese mapa y lo asociamos al div que hemos creado. Lo único especial es que le he pasado qué controles específicos quería para no tener algunos que aparecían por defecto y no me interesaban. Desactivar el sol es para que no haya partes de noche, que a mí me resultaba un poco molesto.
    3. Creamos un vector con el marcador en la posición que queremos y lo añadimos al mapa. La imagen la tengo descargada y en la misma carpeta que el html.
    4. Ajustamos la cámara para que se inicialice en una posición desde la que se vea mejor el marcador. En teoría también debería ser posible solo con latitud y longitud del objetivo, pero esta opción me ha funcionado mejor.

El resultado, una vez más, es muy similar a lo que teníamos hasta ahora.

Resultado del código de Open Globus

La cámara tiene un ajuste ligeramente distinto y se ve más cercana desde el principio, también por variar un poco. Una de las cosas más llamativas es que uno de los controles que trae por defecto permite manejar el mapa con el teclado, lo cual viene muy bien. Sin embargo, como he dicho antes aunque la API está bien documentada los ejemplos no tanto y a veces puede llegar a costar encontrar lo que necesitamos.

Comparativa

Por terminar de ver las 3 opciones, aquí dejo una comparativa de lo que yo he podido ver.

Precio Opciones Facilidad de uso Problemas
CesiumJS De pago si queremos usar algunos mapas o guardar nuestros modelos en su servidor Bastantes opciones, algunas muy enfocadas al modelado 3D y que necesitan el servidor Muy fácil Limitado si no pagamos, pero bastante bueno igualmente.
WebGL Earth Gratuita Muy limitada Muy fácil Funcionalidad limitada
Open Globus Gratuita Muchas opciones, aunque no del todo bien documentadas Fácil, aunque un poco engorroso Algunos problemas con el zoom y ciertas funcionalidades.

En lo personal utilizaría una u otra en función de lo que necesitase para cada caso. Si nos vale simplemente con mostrar un mapa normal y añadir algunos marcadores o polígonos creo que WebGL es la más sencilla de usar. Si necesitamos algo más complejo entonces ya me plantearía usar una de las otras dos, analizando un poco más a fondo la API de cada una para ver cuál se ajusta mejor a lo que queremos. CesiumJS está más orientado a modelado 3D mientras que Open Globus parece tener más facilidad para trabajar con capas. Aunque quizá priorizaría CesiumJS porque parece un poco más estable.

¿Y eso de las capas? ¿Cómo las usamos aquí?

Ya hemos mencionado arriba algo sobre capas, pero creo conveniente pararnos aquí un poco.

Los mapas funcionan con capas, pero son un poco especiales con respecto a lo que podríamos tener en mente inicialmente. Están compuestas por una serie de tiles (o teselas) que se organizan de forma matricial. El conjunto de todas las tiles relacionadas se conoce como tileset.

Básicamente un tileset es el conjunto de datos necesarios para representar información geoespacial en un mapa, repartidos en una matriz de tiles que puede tener varios niveles de zoom. Estas tiles además pueden ser de tipo raster o vector. Es la misma distinción que se hace con las imágenes tradicionales. Las primeras son un mapa de bits donde la imagen se representa por la matriz de píxeles del color que corresponda. Suelen ser más detalladas y es la visión tradicional que se tiene de la imágenes. Por otro lado las segundas están definidas de forma matemática mediante vectores y suelen usarse por ejemplo para logos. Tienen la ventaja de no tener una pérdida de calidad apreciable al ampliar o reducir la imagen, se puede cambiar el estilo visual fácilmente y al ser de menor tamaño la descarga es más rápida.

Será más rápido si lo vemos en un ejemplo. A continuación os enseño la misma imagen, a la izquierda en formato de mapa de bits y a la derecha en formato vectorial. Podéis apreciar que el nivel de detalle es mayor en el mapa de bits porque cuanto mayor sea la resolución a más detalle se puede llegar a trabajar. En cambio en la versión vectorial se pierden los cambios fluidos de tonalidades y algunos detalles como los altavoces. Aunque es cierto que es una imagen vectorizada rápidamente por una herramienta que lo hace automáticamente solamente para este ejemplo, pero podría ser de mayor calidad.

Comparación tipos de imagen de lejos

Pero si ampliamos la imagen podemos ver que en efecto el mapa de bits pierde calidad cuando se pueden empezar a ver los píxeles individualmente mientras que no ocurre con los vectores porque se recalcula y adapta a la escala en la que se muestra.

Comparación tipos de imagen de cerca

Ahora sí, con independencia del tipo de tile con el que tratemos todas se usan de la misma forma. Cada tile contiene unas coordenadas específicas sobre el mapa. A medida que la vista se va moviendo alrededor del mapa la herramienta que lo renderiza pide las correspondientes coordenadas a lo que ve el usuario. ¿Cómo lo hace y de dónde las obtiene?  A la hora de definir un tileset se especifica una url parametrizada del estilo de http://server.url.com/tilsetId/{z}/{x}/{y}.jpg, en la que luego se sustituye por los valores concretos de la parte de mapa que se está mostrando. Veréis que además de latitud y longitud también suele incluirse la altura (eje z) para adaptarse al nivel de zoom aplicado en cada momento.

Pero aparte de la url hay otra serie de parámetros que se suelen configurar cuando se añade un nuevo tileset a un mapa. Entre ellos tenemos por ejemplo las coordenadas que lo limitan (pues por lo general no ocupan todo el globo), los niveles de zoom que admite o el porcentaje de opacidad.

Por lo general la forma de usar un nuevo tileset suele ser mediante una función que acepta la url base junto a un juego de parámetros opcionales. Sin embargo, también es posible hacerlo mediante un fichero TileJSON. Esto no es más que un estándar abierto para la representación de tilesets y sus metadatos. En muchos casos se aceptan ambos formatos.

Aquí vemos un ejemplo de un TileJSON de TileServer:

{  
   "name":"SWISSIMAGE 25m",
   "description":"The Digital Color Orthophotomosaic of Switzerland",
   "attribution":"Federal Office of Topography, swisstopo",
   "type":"baselayer",
   "version":"1",
   "format":"jpg",
   "minzoom":6,
   "maxzoom":12,
   "bounds":[  
      5.894335,
      45.669276,
      10.567622,
      47.8415
   ],
   "basename":"swissimage25m",
   "profile":"mercator",
   "scale":1,
   "tiles":[  
      "http://tileserver.maptiler.com/swissimage25m/{z}/{x}/{y}.jpg"
   ],
   "tilejson":"2.0.0",
   "scheme":"xyz",
   "grids":[  
      "http://tileserver.maptiler.com/swissimage25m/{z}/{x}/{y}.grid.json"
   ]
}

¿Qué proveedor de tilesets usar?

Ahora imagino que podréis tener dudas sobre qué proveedor de tilesets utilizar. Yo he estado haciendo algunas pruebas, principalmente porque al principio creía que había un problema de tiempos con algunas de las herramientas y resultó solo ser el proveedor. Los tres que he comparado han sido Mapbox, ESRI y OpenStreetMap; más que nada porque son los más usados y al venir por defecto en CesiumJS es muy sencillo cambiar de uno a otro en sus ejemplos.

La conclusión a la que he llegado es que entre los tres Mapbox es superior al menos en términos de velocidad. ESRI queda segundo y OpenStreetMap parece tener bastantes problemas en este sentido. Mapbox tiene mapas vectoriales, pero en los ejemplos anteriores hemos usado tiles raster y aunque no era instantáneo la velocidad era bastante buena. Así que se queda como mejor opción en lo que a tiempos se refiere. Es cierto también que los mapas OSM son más detallados y tiene algunos toques visuales que no tienen los demás. Pero aún así a mí personalmente no me compensa la diferencia en tiempo de carga.

Además hay herramientas como MapTiler que también tienen sus propios mapas y permiten también tener los tuyos propios. Mención especial a que en su entorno de pruebas podéis ver de primera mano la diferencia entre los tipos de tiles y cómo en las vectoriales se puede, por ejemplo, cambiar el idioma de los textos.

Pero no es la única manera de poner información en un mapa

Vale, ya hemos visto qué es una capa y cómo está definida mediante un tileset. Pero quiero que tengáis una cosa en cuenta antes de poneros a meter capas como locos. Lo de añadirlas es relativamente sencillo y casi todas las APIs parecen permitirlo. El problema no es tanto ese, como crear la capa en sí. Tened en cuenta que también hay que tener las imágenes (sean del tipo que sean) para cada una de las tiles, preferiblemente con varios niveles de zoom, configurarlas y subirlas a un servidor preparado para ello. Es cierto que se puede superponer una sola imagen o incluso en una imagen representar todo el globo, pero el resultado no es igual y sigue siendo un poco complejo. Y, sobre todo, no se puede cambiar de un momento para otro, pues son recursos estáticos que están alojados en un servidor.

En su lugar, si lo que queremos es mostrar en el mapa una serie de datos en tiempo real tenemos otra alternativa. Añadir programáticamente (depende de la herramienta concreta) marcadores, textos, imágenes e incluso modelos 3D es una opción perfectamente válida. A priori es sencillo, da mucha más flexibilidad y no limita a usar solo datos estáticos. Así que personalmente antes de meterme en líos creando capas nuevas recomiendo pensar qué es lo que necesitamos realmente y cómo podemos lograrlo de la manera más simple.

Conclusiones

Como habéis visto, usar mapas del globo es bastante llamativo, hay herramientas para lograrlo y no es en exceso complicado. Tiene limitaciones, por supuesto, como que no están pensados para el cálculo y representación de rutas. Pero si lo que queremos es mostrar puntos de interés que están por todo el mundo es una manera bastante interesante de hacerlo, sobre todo si están repartidos de tal manera que se fuerza el viajar entre sitios alejados. Desde aquí os animo a que intentéis usarlos, aunque sea por probar algo nuevo.

Imágenes

La entrada Pon un mapa del globo terráqueo en tu web se publicó primero en Adictos al trabajo.

Planificación de proyectos Predictivos vs Adaptativos

$
0
0

Existe mucha gente, tanto los que están a favor de Agile como los que están en contra, que piensa que Agile es un tipo de Project Delivery donde no se tiene ningún tipo de planificación. Cosa que no es correcta.

En un sistema predictivo pronosticamos todo, lo que significa que al comienzo del proyecto intentamos entender de qué tipo es, qué vamos a tener y cómo vamos a ejecutarlo, es decir, realizamos una planificación. Esto no quiere decir que planifiquemos algo y ya sea inamovible, esto es, que no cambie después de la planificación.

Cualquier tipo de planificación necesita estar actualizada constantemente. Es un concepto dinámico. Existen muchos proyectos donde se hace una planificación al inicio, se imprime y se adhiere a la pared donde no se vuelve a tocar más. Este no es el tipo de planificación que tenemos en mente ya que siempre las cosas cambian en el mundo real y estas desviaciones y cambios tienen que estar reflejadas con la finalidad de evidenciar nuevas vías de trabajo para obtener el producto en el que estamos pensando.

Es por ello que en los sistemas predictivos tenemos mucha planificación al principio y posteriormente la cantidad de planificación es mucho más baja, pero aún está ahí.

Cuando se trata de un sistema Adaptativo no tenemos toda esa planificación detallada previamente. Lo que tenemos es una cierta cantidad de planificación al inicio de cada iteración, aunque esto va a venir determinado por el sistema adaptativo que se escoja. Por ejemplo, en Scrum casi no tenemos planificación por adelantado pero en otros sistemas Agile como DSDM (Dynamic System Development Method) sí que tenemos una planificación previa de alto nivel.

Si lo que quieres es entrar en detalles, entonces deja de ser Agile, ya que como recordarás, un proyecto Agile es aquel donde avanzamos con el proyecto, creamos el producto, intentamos entender cómo los clientes y los usuarios finales están interactuando con él y en base a ello aprendemos, hacemos cambios y nos adaptamos. Si defines todos los detalles entonces no hay cabida para la adaptación y en consecuencia dejará de ser Agile.

De igual forma que ocurre con los sistemas Predictivos, no quiere decir que los sistemas Adaptativos no tengan otro tipo de planificación a lo largo de la iteración. Aún necesitamos ajustar nuestra planificación. En Scrum, utilizamos el Sprint Backlog como artefacto que refleja la planificación del Sprint. Es aquí donde ponemos los elementos (Ítems) que hemos seleccionado del Product Backlog.

Y si te preguntas, ¿qué tipo de planificación continua tenemos en Scrum a lo largo del Sprint?, ¿cómo refinamos nuestra planificación?

Como te he contado anteriormente los elementos con los que trabaja el equipo de desarrollo son aquellos seleccionados del Product Backlog pero no son los únicos. Además, de ellos tenemos las Tareas y no creamos todas estas tareas al comienzo del Sprint porque esto sería planificar en detalle por adelantado. Por lo que creamos las tareas para sólo unos pocos elementos en el Sprint Planning; y después a lo largo del Sprint añadimos más tareas según se necesiten. Esta es la parte de la planificación continua que tenemos en Scrum.

Por tanto, tenemos planificación en ambos tipos de proyectos pero es diferente, aunque absolutamente necesaria. Si no tienes una planificación, no tendrás una buena ejecución.

La cantidad de planificación es menor en los proyectos Agile, aunque tenemos algo en lugar de la planificación que nos ayuda a encontrar el norte: la adaptación. Aquí es donde recibimos el feedback y lo usamos para encontrar nuestro camino. En cierto modo es una combinación de planificación y adaptación.

¿Qué ocurre con los sistemas Predictivos? también tenemos adaptación en los sistemas predictivos. Siempre trabajamos en el contexto de nuestro entorno y necesitamos hacer cambios. Por ejemplo, digamos que estás construyendo un puente y has supuesto que vas a usar ciertos materiales pero a mitad del proyecto te das cuenta de que uno de los proveedores del material de construcción ha entrado en bancarrota y no te puede proveer más de ese material en concreto. Tienes que usar otro material y tienes que cambiar tu planificación en base a ello.

Todo esto es adaptación a tu entorno. Quizás, diferente para el tipo de adaptación que tenemos en Agile; la capacidad de adaptación es absolutamente más bajo pero aún así debe estar ahí.

A modo de recordatorio, en un sistema predictivo tenemos disponible al final del proyecto el producto software real y en funcionamiento, mientras que en un sistema adaptativo tenemos múltiples Incrementos a lo largo del ciclo de vida del proyecto y serán éstos los que utilicemos para nuestra adaptación.

Para ser absolutamente precisos aquí, en un sistema predictivo puedes disponer de múltiples fases y crear piezas de producto funcional en mitad del proyecto. Esto es posible pero este desglose de un gran proyecto predictivo, en tres o cuatro proyectos predictivos más pequeños, no lo convierte en un proyecto Adaptativo.

Hasta aquí la diferenciación de los sistemas predictivos de los adaptativos. No quiero cerrar el post sin comentar que uno de los conceptos que tenemos en Agile cuando pensamos en la planificación es el concepto de Cono de Incertidumbre. Este es el hecho de que al inicio del proyecto tenemos más incertidumbre y según avanzamos a través de nuestro proyecto aprenderemos más sobre el producto y el proyecto así como su entorno. Por tanto tendrás menos incertidumbre y esto probablemente sea el inicio de otro post.

La entrada Planificación de proyectos Predictivos vs Adaptativos se publicó primero en Adictos al trabajo.

La estrategia: ¿productos o proyectos?

$
0
0

En las organizaciones, es frecuente encontrar que los conceptos de proyecto y producto son tratados de manera indiferente y resulta que ambos son sutílmente muy distintos cuando son usados para definir una estrategia. Tener clara esta diferencia, es importante al momento de trazar la ruta a seguir, dado que permitirá guiar esfuerzos en la dirección correcta y lograr de esta forma el éxito en la ejecución. Los invito a indagar y conocer según mi experiencia cuál es la diferencia de una estrategia basada en proyectos y una basada en productos, qué beneficios trae a la organización y por qué debemos usarla.

Hablemos de lo mismo…

Para dar inicio a esta conversación me gustaría que estuviéramos en el mismo contexto. Según el Project Management Institute (PMI), un proyecto es una actividad temporal para producir un producto, servicio o resultado. Es único, está acotado por un comienzo y un fin y tiene un alcance y recursos definidos. Por tanto, un proyecto podrá ser cualquier cosa que cumpla con estas condiciones y es importante mencionar que un proyecto tiene una vida limitada: nace y muere.

La definición de producto en cambio, es ambigua y tiene varias interpretaciones, la mayoría de ellas en el contexto de marketing:

  • Según Philip Kotler y Kevin Lane Keller (autores de Marketing Management): un producto, es todo aquello que se ofrece en el mercado para satisfacer un deseo o una necesidad.
  • Según Jerome McCarthy y William Perrault (autores de Marketing Basics): el producto es la oferta con que una compañía satisface una necesidad.
  • Según Stanton, Etzel y Walker (autores Fundamentos del Marketing): un producto es un conjunto de atributos tangibles e intangibles que abarcan el empaque, color, precio, calidad y marca, además del servicio y la reputación del vendedor; el producto puede ser un bien, un servicio, un lugar, una persona o una idea.
  • Según el PMI, un producto es un artefacto producido, cuantificable y que puede ser un elemento terminado o un componente. Otras palabras para producto puede ser un material o bien.

Definamos un producto como aquello que aporta valor, satisface una necesidad en el mercado, ayuda en la consecución de objetivos de negocio y que además puede ser comercializado a diferentes usuarios (se apoya en economías de escala). Un producto tiene un ciclo de vida largo y posee características particulares que pueden evolucionar en el tiempo.

Producto, proyecto, producto, proyecto…

La definición de una estrategia dependerá de los objetivos de la organización, de cómo quiere llegar a sus clientes y de lo qué quiere llegar a ser. Tradicionalmente, las empresas nacían creando cosas particulares para sus clientes, esta estrategia la llamaremos estrategia orientada a proyecto, donde la comercialización y el proceso de desarrollo estaba guiado por requerimientos del usuario, un costo y una fecha de entrega. Cuando el alcance era completado con una calidad aceptada el proyecto terminaba. Uno de los principales problemas que se evidencian bajo este modelo es el costo elevado ya que los errores de análisis no son conocidos sino hasta el final del proceso. 

Cada proyecto debe pasar una a una las fases del ciclo de desarrollo, lo cual propicia el desperdicio de la información, del conocimiento, los recursos, del tiempo y del esfuerzo y puede generar frustración en las personas involucradas en el proceso por la sensación de rutina y repetición a la cual están sometidos. Dadas este tipo de ejecución y su propia naturaleza 

Ahora planteemos el escenario donde una organización que se dedica a ejecutar proyectos, se da cuenta de que los clientes le piden las mismas cosas, tienen las mismas necesidades y decide crear un proyecto estándar/core que posee características básicas y otras que puede personalizar según las necesidades del cliente, este caso lo llamaremos estrategia orientada a productos

Este enfoque busca crear valor en un mercado, por tanto está en constante búsqueda de las necesidades de sus clientes y de sus clientes potenciales, que le permitan innovar y dar soluciones.Tiene un producto que es capaz de personalizar y entregar al usuario en un tiempo definido y con la certeza de que cumplirá con sus expectativas. 

En ambos casos, tenemos un punto en común que es el cliente. Ejecutando proyectos el cliente es quien nos da las pautas de lo que debemos hacer y cuando construimos productos nosotros les daremos soluciones. 

El desarrollo de proyectos tradicionales suele estar guiado por fases (analizar lo que el cliente me pide, diseñar cómo hacerlo, desarrollar y construir, probar que lo hecho funciona, entregar al cliente y por último validar y obtener feedback del cliente) con características y objetivos que deben ejecutarse secuencialmente: lo siguiente depende de lo anterior. 

La gestión del proyecto controla: cuándo empieza, cuándo termina y el alcance que debemos cumplir. Al final de la ejecución, tendremos un resultado que deberá ser validado con el cliente para saber luego de un tiempo si tuvimos éxito o no. 

La evolución de un proyecto se convierte en un nuevo proyecto, que deberá contextualizarse, definirse, planificarse y cuya necesidad es demandada por el usuario. En este proceso, se dedica mucho esfuerzo en las fases iniciales y se tiene poca flexibilidad para cambiar y adaptarse. Al final, la organización tendrá muchos proyectos en producción con características similares pero que no idénticos, en consecuencia será más difícil y costoso dar soporte, mantener y evolucionar.

El desarrollo de productos sigue cuatro fases que dan una guía sobre las tareas que deben llevarse a cabo para obtener un producto. Esta ejecución es iterativa e incremental, no existen dependencias y se ejecutan simultáneamente: conceptualizar, diseñar, construir y entregar. Un producto está en permanente validación con el mercado y posee una planificación funcional de su vida basado en ello, su construcción es evolutiva y está guiada por la definición y validación de ideas. El ciclo de desarrollo puede ejecutarse basado en cualquier método y tiene como guía la entrega de valor al cliente, es decir, que resuelva algún problema.

Proyecto Producto
  • Posee información sobre la gestión de los recursos: personas, tiempo y dinero.
  • Evaluación y reducción de riesgos de ejecución.
  • Obtención de métricas e indicadores sobre el proceso.
  • Creación del know-how de la ejecución.
  • Predicción sobre los costos basados en el alcance-tiempo.
  • Reducción de la incertidumbre
  • Existen equipos motivados y buscando soluciones centradas en el cliente.
  • Disminución de costos de desarrollo (economías de escala).
  • Reducción del time-to-market.
  • Orientación al product-market fit.
  • Adaptación, evolución y mantenimiento de los productos.
  • Satisfacción del cliente.

 

La gestión tradicional de proyectos parece brillante, sólo se tiene que seguir el plan y cumplir con éxito cada una de las tareas establecidas. Sin embargo, en la práctica queda demostrado que no es real, la mayoría de los proyectos no logran resultados esperados, por alguna o varias de las siguientes razones:

  1. El plan que se ha definido durante meses no se ejecuta porque en el camino surgen cosas o faltan muchas otras.
  2. Las actividades se retrasan y no se cumple con el tiempo de entrega acordado.
  3. Las funcionalidades empiezan a perder características y se sacrifica la calidad.
  4. Existen variables que no fueron contempladas

La premisa que impera en este tipo de gestión basada en proyectos es un contrato rígido entre el cliente y la organización.

Cuando nos centramos en productos nos enfocamos en dar valor al cliente, nuestro objetivo es resolver un problema y entregar oportunamente una solución que satisfaga sus necesidades y además exceda sus expectativas. Queremos soluciones que sean naturales, con una experiencia que invite a repetir una y otra vez. 

Los productos se centran en atender necesidades y para ello es necesario un equipo involucrado desde el inicio, se hacen constantes validaciones con el cliente y se trata de entregar prototipos rápidamente: falla temprano, falla barato. Un producto siempre está evolucionando, tomando feedback del mercado, del consumo y de las interacciones.

Cuando una organización sigue esta estrategia requiere dedicar tiempo en compartir la información y trazar un plan de acción a corto plazo que permita realizar validaciones. Las etapas del ciclo de vida de un producto nunca terminan, iteran constantemente. La idea inicial puede evolucionar o incluso ser descartada, en consecuencia, los equipos, y en consecuencia, las organizaciones deben ser capaces de adaptarse.

Cambiemos esto…

Finalmente, cuando trazamos el rumbo de nuestra organización tenemos que tener claro hacia dónde queremos ir. Una estrategia de proyectos ejecutada mediante el desarrollo ágil de productos no es una estrategia orientada a productos. Los proyectos y los productos tienen relación pero no son iguales, podemos construir nuestro producto mediante la ejecución de varios proyectos y esto no es una estrategia de proyectos. 

Tomando en cuenta que estamos en una época donde el cambio es la norma, seguir teniendo estrategias cerradas, sin cabida a errores es impensable. Las organizaciones deben cambiar el rumbo y pensar en una estrategia que sea capaz de adaptarse al mercado, que ayude a conocer a sus usuarios y aporte valor. Esto no solo contribuye en la habilidad para innovar y crear nuevas cosas sino que implica mejoras en la gestión administrativa, en la motivación de los equipos y en el crecimiento organizacional. 

La decisión sobre cuál estrategia seguir dependerá del objetivo de la organización, es importante que el norte siempre sea aportar valor a las personas. Nuestra labor es generar un conglomerado y hacer que el mercado, la organización y las personas se fusionen para crear soluciones innovadoras y disruptivas. En un mundo, bombardeado  constantemente de información, las rutinas aburren, los resultados no.

Espero les sea útil, los leo.

La entrada La estrategia: ¿productos o proyectos? se publicó primero en Adictos al trabajo.

Pruebas de rendimiento en CI – hazlo con Agile

$
0
0

Contenido

1. Introducción

Durante la últimos 10 años, cada vez más compañías están renunciando al antiguo enfoque en cascada para el desarrollo de software y están utilizando enfoques ágiles más flexibles. Las prácticas de desarrollo ágil están ayudando a cualquier equipo a lograr un tiempo de entrega más rápido, adaptándose constantemente a los cambios en los requisitos y proporcionando retroalimentación al final de cada sprint. Todos sabemos que el rendimiento es uno de los factores principales para que cualquier producto de software moderno sea un ganador en el mercado. Algunos de los beneficios del rendimiento y las pruebas de carga son saber cuánta carga puede cargar en su aplicación, si es necesario reconfigurar la red, si se debe optimizar el código o no, cómo se comporta el sistema en diferentes máquinas, etc.

Por lo tanto, en las siguientes secciones, veremos por qué incluir pruebas de rendimiento dentro de la integración continua mejora nuestros proyectos, nos ahorra tiempo y dinero y facilita la refactorización y la solución de problemas de rendimiento.

2. ¿Por qué deberíamos incluir pruebas de rendimiento en nuestro entorno de integración continua, sprint reviews o reuniones diarias?

question mark

      • 2.1 Evitar el descubrimiento tardío de problemas de rendimiento
        Si hacemos una sola prueba de rendimiento al final del ciclo de desarrollo, y la prueba indica problemas, nos enfrentamos a dos dificultades:
        a) Al final del ciclo de desarrollo, los desarrolladores a menudo se ven presionados por los plazos y no tienen tanto tiempo para depurar y refactorizar. Lo que podría ser estresante.
        b) Navegando difícil hacia el problema: la ejecución de la prueba de rendimiento al final del ciclo de desarrollo puede indicar problemas, pero dado que se han aprobado tantas versiones, será más difícil para el equipo de desarrollo resolver estos problemas y mejorar el sistema. Entonces, ¿por qué no ejecutar la prueba de rendimiento al final de cada sprint? De esta manera, podemos detectar problemas temprano y depurarlos fácilmente porque sabemos que el problema ocurrió en el último sprint.

      • 2.2 Hacer cambios antes cuando son más baratos
        Al trabajar con Agile, sabemos que el costo del cambio es menor y que cualquier equipo puede responder rápidamente a los cambios, pero aún así, somos conscientes de que el costo del cambio puede aumentar significativamente si tenemos que hacer refactorización o resolver problemas de rendimiento en un entorno de producción. Entonces, ¿por qué no incluir una prueba de rendimiento en el pipeline y ejecutarla en un determinado período de tiempo para detectar problemas durante la fase de desarrollo?

      • 2.3 Tener la oportunidad de refactorizar cuando tenemos una cantidad pequeña y no un código complejo
        Tener que hacer refactorización al final del ciclo de desarrollo es más difícil porque el ciclo se está completando y debemos tener cuidado para no romperlo. O hacer que se desempeñe peor. Siempre es más fácil refactorizar un rompecabezas más pequeño e incompleto que uno grande y terminado.

      • 2.4 Refactorizar código ‘fresco’
        Para los desarrolladores ágiles es realmente importante tener comentarios «ahora». Es un gran dolor para los desarrolladores verse obligados a refactorizar o depurar el código que escribieron hace meses. Siempre es mejor refactorizar una característica recién hecha. También es algo malo porque estos desarrolladores van a dedicar más tiempo a corregir el código antiguo en lugar de centrarse en las tareas actuales.

      • 2.5 Confianza – No importa qué herramienta de prueba de rendimiento vaya a utilizar, al final, siempre se generará un informe. Podemos archivar los informes de las pruebas y en un caso de problema o queja del lado del cliente como «hey, su aplicación está funcionando mal y lentamente, haga algo»: siempre puede mostrarles los informes de su back-end funcionando perfectamente o indicando problemas en la infraestructura del cliente (por ejemplo, un problema de red).

    3. Para tener en cuenta

    considring

    3.1. Auto mantenimiento / Automatización

    Lo primero que debe considerar al desarrollar una prueba de rendimiento para pipeline de CI es auto mantenimiento. Su prueba debe ser autosostenible y debe (por ejemplo):

      • 3.1.1 Configurar el esquema de destino – Limpie la base de datos de destino y vuelva a llenarla con los datos que serán necesarios para que se ejecute la prueba.Como tendrá diferentes escenarios de prueba, necesitará un conjunto adecuado de datos en el esquema que utiliza. Además, es importante insertar una gran cantidad de filas para poner carga también en la base de datos y no solo en el back-end.

      • 3.1.2 Inicie sesión y obtenga todos los encabezados necesarios para realizar solicitudes hacia el back-end – Considerando que cualquier API moderna debe tener un cierto nivel de seguridad, su prueba debe poder iniciar sesión en el sistema para que pueda hacer solicitudes más adelante en su flujo. Eso incluye todo, desde hacer una simple solicitud Http hasta obtener encabezados específicos, tokens y cualquier otra cosa requerida desde el back-end para que usted pueda realizar solicitudes.

      • 3.1.3 TearDown – Cualquier prueba bien hecha debe ejecutar las funciones tearDown después de que la ejecución del escenario de prueba haya finalizado para «limpiar el desorden». Algunas herramientas de prueba nos brindan posibilidades de revertir para que podamos poner el esquema en el estado en que se encontraba antes de la prueba. Si no, la prueba debería al menos limpiar el esquema desordenado y muy cargado.

    3.2. ¿Cuándo ejecutar el pipeline de una prueba de rendimiento?

      • ¿Cada noche?
      • ¿Con cada compilación de la rama de desarrollo?
      • ¿Al final de cada sprint?

    No importa lo que digan los demás, es una cuestión de preferencias del equipo decidir cuándo ejecutar la canalización de una prueba de rendimiento. Algunas personas prefieren establecer el tiempo de ejecución programado automático cada noche. Algunos dicen que es necesario hacerlo al final de cada sprint. Algunas de las personas están ejecutando las pruebas en cada compilación de la rama de desarrollo. Desde mi punto de vista, es suficiente programar el pipeline para que se ejecute al final de cada sprint, ya que si lo ejecutamos todos los días, debería haber una persona con la tarea de descargar los artefactos del informe y revisar cada día, lo que podría ser molesto e ineficiente.

    3.3. Incluyendo al miembro del equipo de control de calidad en las reuniones del comienzo de cada sprint

    Un factor realmente importante para la integración exitosa de las pruebas de desempeño en CI es involucrar al menos a un especialista en aseguramiento de la calidad en las primeras reuniones de cada sprint. De esta manera, se informará al evaluador de lo que se va a desarrollar y podrá escribir escenarios de prueba y describir los casos de prueba. Gracias a esto, el QA puede comenzar a escribir la implementación de la prueba.

    El problema aquí es que si está utilizando TDD y no confía en un equipo de pruebas para garantizar la calidad del producto, es necesario que haya un desarrollador que sepa cómo usar y escribir en al menos una herramienta para las pruebas de carga. Además, ese desarrollador debe estar familiarizado con las mejores prácticas para elaborar una prueba de rendimiento.

    4. Conclusión

    Al final, creo que vale la pena dedicar tiempo a desarrollar e integrar las pruebas de rendimiento en integración continua porque le brinda a tu equipo muchos beneficios como:

    • Encontrar problemas relacionados con el rendimiento del código del sistema.
    • Detectar problemas de infraestructura como problemas de red.
    • Localizar problemas de rendimiento de la base de datos en una carga más grande.
    • Dar a los desarrolladores la oportunidad de refactorizar el código cuando es menos complejo y fresco.
    • No luchar contra los problemas de rendimiento al final del ciclo de desarrollo, lo que ahorra dinero y tiempo.
    • Garantizar a los usuarios obtener nuevas features, no nuevos problemas de rendimiento.

     

    Cualquier equipo de desarrollo que esté trabajando con Agile puede incluir los resultados de la prueba de rendimiento en cada revisión de sprint o en reuniones diarias, haciendo uso de todos los datos que se generaron en el informe, vigilando semanalmente el rendimiento del sistema.

    Algunas de las herramientas principales para el rendimiento y la prueba de carga
    son Apache JMeter, Grinder framework , Gatling.

    success

     

La entrada Pruebas de rendimiento en CI – hazlo con Agile se publicó primero en Adictos al trabajo.


Pruebas de instrumentación en Android con Espresso

$
0
0

Índice

1. Introducción

Las pruebas instrumentadas en Android son un tipo de pruebas que comprueba el comportamiento de la interfaz gráfica. Según la pirámide de pruebas, éste se encuentra en la cima.

Pirámide de test

Además de comprobar el comportamiento de la UI, nos permite validar los flujos de ejecución de nuestra aplicación.

2. Tipos de pruebas

2.1. Tests unitarios

El objetivo de los tests unitarios es probar exclusivamente una funcionalidad en concreto o clase y para ello se utilizan diferentes frameworks de apoyo como Mockito para realizar mocks de todas las dependencias que tiene la clase a probar.

2.2 Tests de integración

Los tests de integración son pruebas de varios componentes de nuestra aplicación. Los componentes podrían ser clases, módulos, comunicación con terceros, etc. El objetivo principal es comprobar si los diferentes componentes se integran correctamente.

2.3 Tests de instrumentación

Son parecidos a los tests de integración pero más reales de cara al usuario final. El objetivo es simular el comportamiento real de la aplicación mediante herramientas como Espresso.

3. Espresso

Espresso es un framework para Android desarrollado por Google que permite crear pruebas de interfaz de usuario. Espresso permite realizar pruebas tanto en dispositivos físicos como virtuales y además en la nube con Firebase Test Lab.

4. Firebase Test Lab

Firebase Test Lab es una infraestructura de pruebas basada en la nube. Usa dispositivos reales de producción en un centro de datos de Google para probar las aplicaciones tanto para plataformas Android como IOS. Android Studio se integra muy bien con Firebase y permite configurar diferentes dispositivos para ejecutar los test de instrumentación en cada uno de ellos.

5. Ejemplo

Creamos un proyecto simple con Android Studio.

5.1. Dependencias

androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.31"
androidTestImplementation "androidx.test:core:1.2.1-alpha02"
androidTestImplementation "androidx.test:core-ktx:1.2.1-alpha02"
androidTestImplementation "androidx.test.ext:junit:1.1.2-alpha02"
androidTestImplementation "androidx.test.ext:junit-ktx:1.1.2-alpha02"
androidTestImplementation "androidx.test:runner:1.3.0-alpha02"
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0-alpha02"

5.2. Directorio de test

Cuando creamos un proyecto en Android existen dos directorios de tests dentro de src:

  • androidTest se encuentran los tests de instrumentación.
  • test se encuentran los tests unitarios y de integración.
    android tests

5.3. Crear un test de instrumentación

Para crear un test de instrumentación debemos de crear una clase dentro de la carpeta androidTest/java/[nombre.del.paquete].

test de instrumentación android

Por defecto, cuando se crea el proyecto se generan varios ficheros de tests y uno de ellos es de instrumentación, pero uno de los problemas de generar automáticamente estos ficheros es que, a veces, las dependencias no están actualizadas y algunas clases y métodos tienen la anotación @Deprecated. Por lo tanto, es importante que actualices correctamente las dependencias en tu fichero build.gradle de la aplicación.

En nuestro ejemplo ExampleInstrumentedTest contiene lo siguiente:

import android.app.Application
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {

    @get:Rule
    var activityScenarioRule = activityScenarioRule()

    @Test
    fun useAppContext() {
        val appContext = ApplicationProvider.getApplicationContext()
        assertEquals("com.autentia.demo.instrumentationtest", appContext.packageName)
    }

}

Si nos fijamos en la primera parte del código:

@get:Rule
var activityScenarioRule = activityScenarioRule()

Esta declaración permite ejecutar una actividad antes de cada test, en este caso solo tenemos MainActivity. Si seguimos analizando:

@Test
fun useAppContext() {
    val appContext = ApplicationProvider.getApplicationContext()
    assertEquals("com.autentia.demo.instrumentationtest", appContext.packageName)
}

El objetivo de este test es obtener el contexto de la aplicación y comprobar el nombre del paquete. Podrías preguntarte «¿Y para qué ejecutar la actividad principal en cada test?» Buena observación. En este caso para nada porque no hemos realizado ninguna comprobación de la interfaz gráfica. El siguiente paso es crear un test que interactúe con ella. ¡Vamos a ello!

Partimos de la siguiente interfaz:

Es un sencillo login, y nuestro objetivo es:

Si algunos de los campos están vacíos, cuando se haga clic en el botón «LOGIN», tiene que aparecer el siguiente mensaje de error en cada campo: «Este campo no puede estar vacío».

Para ello aplicaremos TDD, es decir, primero preparamos los tests y después escribimos el código. Volvemos al fichero de test de instrumentación que ha generado automáticamente y creamos un test que compruebe el estado de los campos cuando se haga clic en el botón «LOGIN».

El test quedaría de la siguiente manera:

@Test
fun usernameAndPasswordFieldShowErrorMessageIfAreEmptyWhenPressedLoginButton() {
    // When
    onView(withId(R.id.button_login)).perform(click())

    // Then
    onView(withId(R.id.field_username)).check(matches(checkErrorText {
        hasErrorText(it)
    }))
    onView(withId(R.id.field_password)).check(matches(checkErrorText {
        hasErrorText(it)
    }))
}

Si nos paramos a analizar un poco el código, en el bloque When el método onView() permite obtener un componente visual en Android a través de un Matcher. En este caso realizamos el match a partir del ID. Una vez que se realiza el match este devuelve un ViewInteraction que nos permite realizar acciones con el método perform() o validaciones con check().

En algunos casos interesa crear nuestros propios Matchers cuando el framework de Espresso no lo proporciona. En nuestro ejemplo tenemos el método checkErrorText(condition). Al utilizar una librería de componentes como Material Design, algunos match no funcionan correctamente (en este caso hasErrorText() de Espresso no funciona).

Nuestra implementación del Matcher es la siguiente:

private inline fun  checkErrorText(
    crossinline condition: (view: T) -> Boolean
): BaseMatcher {
    return object : BaseMatcher() {

        override fun describeTo(description: Description) {}

        override fun matches(item: Any): Boolean {
            val textInputLayout = item as T
            return condition(textInputLayout)
        }
    }
}

Con esta implementación podemos llegar a hacer validaciones tan complejas como queramos, y en nuestro ejemplo comprobamos que los campos usuario y contraseña tienen que mostrar error porque están vacíos. La condición es lo que queremos validar y lo recibimos como parámetro de nuestro Matcher.

En nuestro caso la condición sería la siguiente:

private fun hasErrorText(it: TextInputLayout) = it.error?.toString()?.equals(appContext.getString(R.string.empty_field_error)) ?: false

Si ejecutásemos el test en un emulador, su ejecución sería:

Y el resultado del test:

resultado test con fallo en android

En este punto solo nos falta implementar la lógica necesaria para que pase el test correctamente, es decir, mostrar error en los campos cuando estén vacíos.

Abrimos la clase MainActivity y en el método onCreate() añadimos lo siguiente:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    button_login.setOnClickListener {
        if (input_field_username.text.isNullOrEmpty()) {
            field_username.error = getString(R.string.empty_field_error)
        } else {
            field_username.error = ""
        }

        if (input_field_password.text.isNullOrEmpty()) {
            field_password.error = getString(R.string.empty_field_error)
        } else {
            field_password.error = ""
        }
    }

}

Si volvemos ejecutar el test en un emulador, su ejecución sería:

Y el resultado del test:

Resultado correcto del test

¡Ahora podemos añadir más tests!

@Test
fun usernameFieldShowsErrorMessageIfEmptyValueWhenPressedLoginButton() {
    // When
    onView(withId(R.id.input_field_password))
        .perform(typeText("password"))
        .perform(closeSoftKeyboard())

    onView(withId(R.id.button_login)).perform(click())

    // Then
    onView(withId(R.id.field_username)).check(matches(checkErrorText {
        hasErrorText(it)
    }))
    onView(withId(R.id.field_password)).check(matches(checkErrorText {
        hasNotErrorText(it)
    }))
}

@Test
fun usernameAndPasswordFieldsDoNotShowError() {
    // When
    onView(withId(R.id.input_field_username))
        .perform(typeText("username"))
        .perform(closeSoftKeyboard())

    onView(withId(R.id.input_field_password))
        .perform(typeText("password"))
        .perform(closeSoftKeyboard())

    onView(withId(R.id.button_login)).perform(click())

    // Then
    onView(withId(R.id.field_username)).check(matches(checkErrorText {
        hasNotErrorText(it)
    }))
    onView(withId(R.id.field_password)).check(matches(checkErrorText {
        hasNotErrorText(it)
    }))
}

Si ejecutásemos los tests de nuevo, el resultado es el siguiente:

Resultado ejecución de todos los test

Conclusión

Como conclusión personal puedo decir que en Android los tests de instrumentación tiene algunas desventajas, una de ellas es el tiempo de ejecución, otra los recursos necesarios para poder ejecutarlos. En cambio, una de las ventajas es la facilidad que proporciona Espresso para probar las interfaces gráficas construidas con actividades o fragmentos.

También me llama mucho la atención la integración con Firebase Test Lab porque permite preparar los tests para un grupo de dispositivos con diferentes configuraciones. Imagina una aplicación que necesita instalarse en 10 dispositivos diferentes con distintas configuraciones… ¿Te animas a crear todos los dispositivos en Android Studio? ¡Aquí lo dejo!

Referencias

La entrada Pruebas de instrumentación en Android con Espresso se publicó primero en Adictos al trabajo.

¿Product Manager o Project Manager?

$
0
0

Luego de escuchar el podcast de mis compañeros Jose Mangialomini y Jesús Angulo, me surgieron varias dudas referente a los roles que deberían acompañar y que son necesarios cuando una organización desea cambiar su estrategia de negocio de proyectos a productos:

  • ¿Qué es un Product Manager?
  • ¿Qué es un Project Manager?
  • ¿Cuál es la diferencia?
  • ¿Cuál necesito?
  • ¿Puede una sola persona ocupar ambos roles?

En las siguientes líneas intentaré esclarecer estas dudas en base a mi experiencia e investigación.

Antes que nada me gustaría definir brevemente qué es un producto y un proyecto para partir de una base común:

Un producto: puede ser cualquier cosa, desde un producto físico hasta un software o un servicio que satisfaga las necesidades de un grupo de usuarios. Pasa por un ciclo de vida, se desarrolla e introduce en el mercado, crece en aceptación hasta que madura y se retira una vez que ya no es necesario.

Un proyecto: es un esfuerzo temporal guiado por un plan con una serie de actividades con una fecha de inicio, una fecha de fin y con un resultado definido. El proyecto se completa cuando se logra ese resultado. Por lo general, pasa por cinco etapas: inicio, planificación, ejecución, monitoreo y control y cierre.

Si deseas ampliar más sobre la diferencia entre producto y proyecto o sobre la estrategia de negocio orientada a producto o a proyecto, te recomiendo el artículo: Estrategia: ¿productos o proyectos? de mi compañera Jeselys Hernández.

Parece ser entonces que la diferencia principal es la línea de tiempo. A diferencia de un proyecto, un producto no es un esfuerzo temporal, evoluciona y se adapta a las necesidades del mercado para entregar cada día más y mejores características. Por lo tanto, puede incluir varios proyectos que tienen como objetivo mantenerlo, mejorarlo o diversificarlo. Así, si pensamos en que nuestro producto es una aplicación móvil, el desarrollo del mismo puede contener muchos proyectos antes de que esté listo para ser lanzado. Todos estos proyectos tienen sus propios puntos de inicio y final. Sin embargo, la aplicación móvil es un producto que debería continuar mejorando mientras se siga comercializando en el mercado.

Ahora que ya hemos establecido una base común, intentemos aclarar las responsabilidades de los roles que gestionan los ciclos de vida de un producto y de un proyecto.

¿Qué es un Product Manager?

Cuando intentamos desarrollar un producto de software, hay muchas disciplinas involucradas:  equipos de desarrolladores, equipo de diseño y usabilidad, analistas funcionales, marketing, comercial, legal, catalogación, contenido, operaciones, atención al cliente, administración y finanzas. Todas estas partes interesadas tienen algo que aportar y en la mayoría de los casos tienen necesidades contrapuestas y dificultades para comunicarse entre sí efectivamente.  

En estas circunstancias el rol del Product Manager aparece en el centro de esas áreas como el encargado de traducir todos esos requerimientos y necesidades en soluciones a los problemas reales de los usuarios, a la vez que se alcanzan los objetivos del negocio. Sin embargo, esta no es su única responsabilidad, según Bruce McCarthy en su libro Product Manager vs. Project Manager, un Product Manager es:

«Un rol de liderazgo, responsable de la concepción, desarrollo, crecimiento y eventual retiro de un producto. Es un papel estratégico, que abarca el ciclo de vida completo y la vida útil de un producto, desde la cuna hasta la tumba. Piense en el Product Manager como la persona que decide si criar ovejas, vacas o una combinación de ambas.»

En definitiva, un Product Manager es el responsable de establecer la estrategia del producto, esto es trabajar en la intersección entre el negocio, tecnología y la experiencia de usuario:

  • Negocio: Los Product Manager deben estar obsesionados con la optimización de un producto para lograr los objetivos comerciales al tiempo que maximizan el retorno de la inversión, se enfocan en crear valor para el negocio y buscan ideas que sean rentables.
  • Tecnología: No tiene sentido definir qué construir si no se sabe cómo se construirá. Esto no significa que un Product Manager deba poder sentarse y codificar pero sí debería entender el conjunto de tecnologías lo suficientemente bien como para comprender el nivel de esfuerzo que implica y así poder tomar las decisiones correctas.
  • Experiencia de usuario: por último, pero no menos importante, el Product Manager es la voz del usuario dentro de la empresa y debe ser un apasionado de la experiencia del usuario. Nuevamente, esto no significa que deba ser un impulsor de píxeles, pero sí es necesario estar constantemente probando el producto, hablando con los usuarios y obteniendo sus comentarios de primera mano.  Los Product Manager deben comprender las necesidades de los clientes que conforman el mercado objetivo.

Pero, sobre todo, un Product Manager tiene que tener un sentido del producto, es decir, debe poseer la intuición para saber cuándo mover un producto de pruebas alfa a las beta, cuándo retrasar un lanzamiento o cuándo quitar un producto o parte de él porque ya no tiene sentido estratégico y/o económico.

Los Product Manager también son responsables de la función de pérdidas y ganancias de un producto. Es por eso que deben colaborar con los equipos de ventas, marketing, éxito de clientes y asistencia técnica, para asegurar que cumplen con los objetivos comerciales generales, en términos de ingresos, ventaja competitiva y satisfacción del cliente.

El objetivo de un Product Manager es saber responder claramente a: ¿Qué problema resuelve esto? ¿Qué se va construir y por qué? y ¿cuáles serán los beneficios? En definitiva, el objetivo de un Product Manager es entregar un producto que los clientes adoren y en consecuencia sean rentables.

¿Qué es un Project Manager?

Una vez que el Product Manager ha decidido la visión de producto a seguir, hace falta quien ejecute ese plan, es ahí donde el rol del Project Manager o Jefe de Proyecto entra en acción, tomando esa visión y plasmándola en líneas de tiempo de uno o más proyectos, para planificar y organizar los recursos con la finalidad de alcanzar los objetivos propuestos y lograr el éxito del proyecto dentro de las limitaciones establecidas. Estas limitaciones suelen ser: alcance, tiempo, calidad y presupuesto.  Según Bruce McCarthy, un Project Manager es:

«Un rol de gestión, responsable de garantizar la entrega a tiempo y dentro del presupuesto de un producto de trabajo o la finalización de una iniciativa de tiempo limitado. Si su Product Manager ha decidido criar ovejas, piensa en el Project Manager como el pastor.»

En definitiva, un Project Manager es el responsable de la:

  • Planificación: determinar las personas, los materiales y el equipo necesarios para cumplir con los requisitos del proyecto.
  • Programación: administrar activamente la disponibilidad oportuna de personas, materiales y equipos para evitar retrasos en los proyectos.
  • Manejo de costos: administrar los gastos del proyecto para evitar sobrecostos.
  • Gestión de riesgos: anticipar y trabajar para mitigar los riesgos para cumplir de manera eficiente los requisitos del proyecto.

Un Project Manager debe saber responder claramente a: ¿Qué recursos se necesitan? ¿Cuándo se entregará el proyecto? ¿Quién podría hacer qué?

La siguiente imagen ilustra cómo los Product Manager son responsables de cada parte del ciclo de lanzamiento del producto durante la vida útil de un producto, mientras que un Project Manager colabora con sus homólogos de productos en las fases de ejecución, coordinando los recursos que desarrollan, envían y (a veces) monitorean los resultados.

En la siguiente tabla resumo los detalles de quién hace qué, dado que aquí es donde a menudo surgen la incertidumbre y el conflicto entre ambos roles.

Product Manager

Project Manager

Responsabilidad Éxito del producto Entrega del proyecto
Objetivo Construir las soluciones correctas Construir soluciones eficientemente
Habilidades requeridas y experiencia Liderazgo, clientes, tecnología, negocios. Gestión, planificación, programación, gestión de costes, gestión de riesgos.
Medida de éxito Desempeño de negocios:  crecimiento, ingresos, ganancias. Entrega a tiempo, dentro del presupuesto y con la calidad adecuada.

 

Podríamos decir entonces que un Product Manager es el responsable del éxito de un producto en el mercado y un Project Manager es el responsable de gestionar los recursos de manera eficiente para respaldar esos objetivos comerciales.

¿Puede una sola persona asumir ambos roles?

Particularmente creo que, si se quiere una verdadera y efectiva gestión de producto y de proyecto, no. Superponer ambos roles puede propiciar que se presenten los siguientes problemas:

  • Pérdida de foco: los Product Manager tienen una orientación externa. Sus actividades diarias pueden implicar hablar con los clientes, realizar pruebas de usabilidad, seguir al equipo de ventas en sus interacciones con los clientes. Estas actividades generalmente ocurren fuera de la oficina, lo que hace difícil mantener un ojo en la visión del producto y las necesidades del equipo de desarrollo.
  • Posibles cuellos de botella: debido a que se centra en una sola persona el éxito del producto y del proyecto.

Además, sus responsabilidades son lo suficientemente diferentes como para que se tienda a descuidar un conjunto por el otro y, desafortunadamente, son las actividades de investigación, estrategia y priorización de la gestión de productos, importantes pero menos urgentes, las que generalmente se descuidan.

Conclusión

Podemos concluir entonces que son roles complementarios. Los Product Manager y Project Manager ven el mismo trabajo a través de diferentes prismas. Y eso es algo positivo cuando se intenta lograr algo especial, cómo traer un nuevo producto al mercado. Por lo que considero que, en una estrategia orientada a producto, ambos roles son necesarios.

Los Product Manager tratan con el ¿Qué? ¿Y por qué ?, Project Manager con el ¿Cómo? ¿y cuándo?

Cambiemos esto…

Sin embargo, en base a mi experiencia las organizaciones tienen dos grandes asuntos pendientes que resolver para poder sacar el máximo provecho a estos roles y son:

  1. No tienen clara la estrategia de negocio (orientada a proyecto o producto) por lo que las personas que ejecutan estos roles generalmente tienen una amalgama de responsabilidades entremezcladas que les confunde y no permite a la organización potenciar las ventajas de tener ambos roles.
  2. Los Product Manager y Project Manager rara vez tienen realmente toda la autonomía y autoridad sobre el producto o proyecto, por lo que no es posible llevar a cabo todas las responsabilidades que abarcan sus roles, trayendo como consecuencia personas desmotivadas y poco implicadas en el éxito del producto o proyecto.     

Cuando las organizaciones definan y aclaren su estrategia de negocio y potencien y empoderen adecuadamente estos roles, conseguirán que ambos formen un poderoso dúo que complementarán sus diferencias y potenciarán su negocio garantizando el éxito a largo plazo de la empresa. 

La entrada ¿Product Manager o Project Manager? se publicó primero en Adictos al trabajo.

Gestión de Bloqueos en Kanban

$
0
0

1. Introducción

Durante los últimos años me he dedicado a ayudar a numerosas empresas a implantar el Método Kanban, esto me ha proporcionado experiencia y me ha curtido sobre los problemas que se dan durante este proceso y que nadie suele tener en cuenta porque surgen de situaciones reales, cotidianas que ningún curso te llega a enseñar.

Es por ello, que hoy os vengo a hablar desde mi experiencia personal de un tema bastante doloroso en la Transformación Digital de las organizaciones: la Gestión de Bloqueos con Kanban.

Cuando trabajas con el Método Kanban en un equipo, siempre tarde o temprano y por cualquier razón aparecen los bloqueos. Para los equipos de hecho cualquier cosa que les impida trabajar es un bloqueo. ¡ERROR!

En este momento, tanto como si eres un Coach o un Team Kanban Practitioner, tu misión es dejar claro qué es un bloqueo y qué no. Así como lo que implica tener un bloqueo a tener un elemento restringiendo el flujo de trabajo.

Empecemos con lo que se suele considerar bloqueo y NO lo es:

  • Un cuello de botella (bottleneck) NO es un bloqueo. Un cuello de botella restringe y limita el trabajo en curso. Los cuellos de botella pueden ser de dos tipos y en ningún caso son bloqueos:
    • Los hay de capacidad limitada, aquellos que te impiden hacer más trabajo. La limitación del WIP impuesta.
    • Y de disponibilidad no instantánea. Capacidad limitada debido a la disponibilidad limitada.
  • Retrasos o impedimentos debido a Bugs. Tu falta de empirismo programando o estimando no es un Bloqueo.

Vamos a utilizar una metáfora para entender la diferencia de lo que es un Bloqueo de lo que no.

Imagina que has comprado una vivienda de dos plantas de segunda mano y al par de semanas de vivir allí surgen numerosos problemas con las cañerías de la vivienda. Has detectado que en la planta superior el agua caliente llega de forma muy limitada o en poca cantidad y en la planta baja hay grifos que no funcionan.

Al llamar al fontanero y éste hacer un estudio, determina que el motivo de que el agua caliente llegue de forma tan limitada a la planta superior se debe al tipo de cañería y su grosor, lo cual no permite más caudal (esto sería nuestro cuello de botella de capacidad limitada).

En cuanto a los grifos de la planta baja, nos indica que han estado tanto tiempo sin uso que se ha generado un tapón de suciedad que impide el paso del agua y es necesario usar un ácido para limpiarlas (impide el paso del agua, este sería nuestro bloqueo).

Para terminar el fontanero le indica que no puede desatascar los grifos a la vez porque la cantidad de ácido en las cañerías para su limpieza por seguridad está limitado. (este sería el cuello de botella de disponibilidad no instantánea). 

Decides que no vas a esperar al lunes y tú mismo intentas arreglar la grifería, destrozando una de las válvulas y teniendo que llamar de nuevo al fontanero (retrasos e impedimentos).

En definitiva, un bloqueo es un tipo de impedimento que no puede resolver internamente el equipo y que interrumpe el flujo de trabajo.

 

2. ¿Por qué hay que saber distinguir cuellos de botella de bloqueos? 

Porque cada cual conlleva un pensamiento diferente a la hora de resolver el problema. Los Bloqueos deben ser vistos como aquellas variaciones de causa especial en nuestro flujo de trabajo.

Los ítems bloqueados requieren que la organización desarrolle la capacidad para la gestión y resolución de problemas para restaurar el flujo lo más rápido posible. De igual modo debe desarrollar la capacidad para analizar la causa principal y resolverla para prevenir la recurrencia.

Cuello de botella y Bloqueo

3.Gestionando los Bloqueos

Ahora que sabemos identificar un bloqueo necesitamos visualizarlo en el panel kanban. Por convención se recomienda el uso de post it rosas que se colocan encima del ítem bloqueado.

Conviene aclarar que un ítem bloqueado consume WIP o de lo contrario no se abordará el problema hasta que sea quizás demasiado tarde. Los bloqueos no se resuelven poniéndonos a trabajar en otra cosa.

La experiencia me ha mostrado que luego de conocer los bloqueos se suele dar otro problema: No basta con decir que un ítem está bloqueado, ya que esto no conduce a que el equipo desarrolle las capacidades lo suficientemente sólidas como para desbloquearlo.

Lo que debemos hacer es: a este nuevo ítem se le va a llamar Issue, este nuevo elemento debe tener un número de seguimiento asignado, así como un miembro del equipo asignado (generalmente el Team Leader o Jefe de Proyecto) para asegurarse la resolución. Entraremos más en detalle aquí en la sección 4. Monitorización y reporting de bloqueos.

Algunos ejemplos típicos de bloqueos son:

  • Requerimos una persona experta para dar resolución a una ambigüedad en los requisitos y no está disponible
  • Necesitamos configurar un entorno y el ingeniero encargado de tal acción está ausente.
  • Solicitamos un especialista en Bases de Datos para trabajar con un ítem pero este se encuentra ausente por vacaciones.

El mejor momento en Kanban para discutir y poner foco en los bloqueos se da en la Kanban Meeting (Daily). En ella el equipo debe centrarse en tratar de conocer los  bloqueos y en el progreso realizado en su resolución. Las preguntas típicas de una Kanban Meeting deben ser:

  • ¿Quién está trabajando en resolver la Issue ###?
  • ¿Cuál es el estado de la resolución de la Issue ###?
  • ¿Necesita la Issue ### ser escalada?
  • ¿A quién?

Un equipo con un buen nivel de madurez será aquel en el que los miembros del equipo se muestran voluntarios en ayudar a la resolución de las Issues.

En otras situaciones donde este nivel de madurez no es tan evidente o aún está emergiendo, será el Jefe de Proyecto el que quizás tenga que asignar miembros del equipo para trabajar en la resolución de las Issues.

4. Monitorización y reporting de bloqueos

Como otros ítems los Issues deben ser monitorizados. Dicha monitorización debe incluir fecha de inicio y fecha de fin así como un enlace a todos los ítems afectados.

Hasta ahora hemos hecho referencia al tablero kanban físico pero, ¿qué ocurre con las herramientas digitales?

Sea cual sea la herramienta digital que se utilice para hacer la monitorización de los bloqueos, debe permitir esta monitorización o bien ser lo suficientemente personalizable como para que nosotros la podamos crear.

Como ya se mencionó anteriormente para los Issues utilizaremos los post it rosas con los siguientes campos:

  • Fecha de Inicio y Fin
  • Miembro del equipo asignado
  • Descripción del problema
  • Enlaces a los ítems afectados (bloqueados)

Otros campos que se podrían emplear y que nos aportan diferentes fuentes de información son:

  • Histórico del esfuerzo realizado hasta su resolución
  • Histórico de todos los individuos asignados
  • Vía de escalado
  • Tiempo estimado de resolución
  • Evaluación del impacto
  • Soluciones para atajar el problema de raíz y prevenir futuros fallos.

Aunque mostrar los Issues con post its rosas nos proporcionan un gran impacto visual y podemos determinar cuántos ítems se encuentran bloqueados al momento, resulta también útil el hacerles un seguimiento y reporting.

Un Diagrama de Flujo Acumulado de los Issues e ítems bloqueados nos proporcionan un gran indicador de las capacidades de la organización en la gestión y resolución de los Issues.

La organización debe ser consciente del impacto que los Issues pueden generar. Esta conciencia debe permitir tomar decisiones sobre las oportunidades de mejora y los posibles beneficios de invertir en reparar los problemas de raíz para prevenir posibles variaciones de ésta.

5. La infame columna Blocked

He visto en multitud de ocasiones como los equipos emplean la columna blocked en su sistema kanban. Tengo que reconocer que se me ponen los pelos de punta cada vez que la veo ya que demuestran un bajo nivel de madurez y poco conocimiento de las Prácticas Generales de Kanban. Además, de que el hacerlo supone generar más problemas de los que ya se pueden tener.

Usar la columna Blocked en el sistema kanban va en contra de las prácticas generales de Kanban. Concretamente la que más le afecta es la que hace referencia a Gestionar el Flujo.

El flujo de trabajo de un sistema kanban debería maximizar la entrega de valor, minimizar los tiempos de entrega y ser tan fluido como sea posible, para ello este debe ser continuo e ininterrumpido.

El uso de la columna kanban interrumpe y hace discontinuo el flujo. El usar dicha columna ya de por sí implica que todos los ítems en el tablero deben pasar por el estado Bloqueado ¿no te chirría ya esto de por sí?.

Por tanto, el que se desbloquee un ítem va a hacer que tengamos que volver a un estado anterior (o posterior dependiendo de donde coloques tu columna Blocked). Sin contar que la experiencia hace que los equipos se desentienden del contenido de la columna Blocked y la suelen usar como cajón desastre o agujero negro donde todo lo que entra nunca sale.

Los sistemas de flujo pueden ofrecer gran variedad de métricas que son importantes para todos aquellos Jefes de Proyecto, manager del sistema para construir estimaciones fiables. Te remito a la sección de métricas Lean y Kanban del siguiente artículo: https://www.extremeuncertainty.com/agile-metrics-ultimate-guide/

Además, te recomiendo la lectura de Agile Metrics in Action para aprender cómo obtener los datos que realmente importan. Métricas que en caso de usar una columna Blocked se verían comprometidas y no nos podríamos beneficiar de ellas.

La alternativa que puedes emplear y que ya se ha mencionado aquí en varias ocasiones, es emplear la convención de usar post its rosas para visualizar el ítem bloqueado.

Otra opción que puede satisfacer más a todos los fans de la columna Blocked puede ser el uso de un Swimlane en la misma columna del trabajo bloqueado. Considerándola un Aparcamiento para esas tareas pero ojo, teniendo en cuenta de que cada elemento bloqueado sigue consumiendo del Límite WIP impuesto.

Panel con Swimlane de Ítems Aplazados para los Ítems Blocked

 6. Escalando Bloqueos

Cuando el equipo es incapaz de resolver un Issue por sí mismo o se requiere de una parte externa para resolverlo y no está disponible o no responde, el Issue debe ser escalado.

Es importante para la organización el desarrollar una fuerte capacidad de escalado de Issues ya que si esta capacidad es pobre o no existe, el mantenimiento y restauración del flujo después de un bloqueo puede ser problemático.

La base de una buena capacidad de escalado es un proceso o política de escalado bien documentado, mediante documentación, en un sitio web o wiki disponible para todos los miembros del equipo.

Es fundamental que no existan ambigüedades sobre cómo y dónde escalar el problema. El equipo de desarrollo se debe tomar su tiempo para definir estas vías de escalado y escribirlas en políticas. Haciendo esto el equipo sabrá a dónde enviar los Issues para su resolución. 

No nos tenemos que olvidar del personal senior (managers), de los cuales se espera que formen parte del proceso tomando la responsabilidad de resolver los Issues.

7. Conclusiones

A lo largo del artículo hemos visto la importancia de distinguir los bloqueos y visualizarlos. Se ha mencionado el método usado mediante convención que se basa en el uso de post it rosas para indicar el Issue.

Cabe destacar que no es la única forma de realizarlo y cada equipo debe utilizar la que más se adapte a sus necesidades y más valor le aporte, siempre y cuando no se dejen de lado los principios kanban y permitan hacer el seguimiento del mismo hasta su resolución y no caer en el olvido.

A la hora de gestionar los bloqueos se ha mencionado la existencia de herramientas de monitorización y reporting. Desde mi punto de vista la más completa es el Diagrama de Flujo Acumulado ya que concentra una gran cantidad de información con la que poder trabajar en una única gráfica. Como puede ser el WIP, el Lead Time, Cycle Time, etc. Te dejo un recurso para que conozcas más sobre las métricas pinchando aquí.

Hay que indicar que los bloqueos son inevitables y pueden surgir por causas muy diversas, es trabajo de la organización estar preparada para actuar de la forma más adecuada según el caso y que ese procedimiento esté bien documentado.

En mi experiencia profesional, puedo asegurar que la gran mayoría de los casos de bloqueos estos se podrían haber evitado con una correcta definición de Políticas Explícitas que determinen el Definition of Ready de cada fase del flujo de trabajo de nuestro sistema kanban.

Por último, comentar que existen técnicas para prevenirlos, que dada su extensión se han quedado fuera del artículo, como es el caso de la técnica kanban Dark Matter Planning.

La entrada Gestión de Bloqueos en Kanban se publicó primero en Adictos al trabajo.

Dificultades a la hora de estimar

$
0
0

Desde que me dedico a dar apoyo a organizaciones y a equipos de desarrollo en sus procesos de transformación ágil, he tenido la oportunidad de facilitar y participar en muchas sesiones de Scrum, siendo en la Sprint Planning donde me he topado una y otra vez con una misma pregunta cuando llega el momento de realizar estimaciones relativas: 

¿Cómo puedo estimar el esfuerzo que implica hacer toda una nueva funcionalidad, si yo sólo sé cómo hacer una parte de la misma? Desconozco lo que hay que hacer en las otras partes, por lo tanto solo puedo estimar mi parte.

Siempre que pensamos en sesiones en las que tenemos que por algún motivo realizar estimación, nos imaginamos a todos los miembros del equipo aplicando planning poker, estimando el que esfuerzo nos conlleva entregar una funcionalidad  de una forma muy amena y colaborativa independientemente de la experiencia técnica de cada uno de los miembros. Pero la realidad es que esto ocurre en muy pocas ocasiones, y es completamente comprensible, yo diría que es hasta natural. El desarrollo de una funcionalidad implicará realizar tareas de diversas áreas:  UI, UX, front, back, BBDD, etc; pretender que todos los miembros del equipo estimen todo este trabajo, es casi como pretender que todos los miembros del equipo sean full stack y sabemos que esto es irreal. Entonces: 

  1. ¿cómo se estima en la vida real?
  2. ¿todos estiman todo aunque no sea su terreno? 
  3. ¿cada uno estima lo suyo y luego se suma para obtener la estimación total del esfuerzo que conlleva construir una nueva funcionalidad?

En este artículo me gustaría desenmarañar lo que hay detrás de esta disyuntiva y, más importante aún, plasmar según mi experiencia cómo creo que deberíamos abordar esta dificultad para conseguir verdaderos equipos Scrum.

Comencemos por definir qué es un equipo Scrum. Según la guía de Scrum, un equipo Scrum se define como un equipo auto-organizado y multifuncional, es decir que tienen todas las competencias necesarias para llevar a cabo el trabajo sin depender de otras personas que no son parte del equipo.

Y aquí aprovecho la ocasión para aclarar una mal interpretación muy extendida sobre lo que es un equipo multifuncional: un equipo multifuncional no significa que todo el mundo sabe hacer de todo; multifuncional significa que el equipo en su conjunto, uniendo lo que cada persona sabe hacer, posee todas las competencias necesarias para lograr completar el trabajo, sin depender (o dependiendo mínimamente) de otros equipos, áreas, departamentos o roles fuera del mismo. 

Entonces, si el propio framework prescribe equipos multidisciplinares: ¿por qué las estimaciones persiguen estimaciones globales a nivel de una funcionalidad específica? Porque el hecho de que el framework prescriba equipos multifuncionales, no significa que promocione el trabajo por silos, significa que es realista en cuanto a cómo somos las personas y los equipos, pero también es realista en cuanto al poder del trabajo en equipo y a que las personas somos mucho más productivos y capaces cuando trabajamos en equipo. Por otro lado,  cuando se pide una estimación es sólo eso: una valoración, una aproximación, no un compromiso irrefutable e inamovible escrito sobre piedra. Estimar, sea como sea (días o puntos de historia), es solo una excusa para fomentar el principal objetivo de la estimación, que es: la conversación. Sí la conversación es lo verdaderamente valioso para el equipo, mucho más que el número que obtengamos, porque nos permite: 

  1. Detectar posibles tareas ocultas y posibles obstáculos. La sesión de estimación es una de las primeras oportunidades de detectar riesgos que pueden comenzar a tratarse para que no se conviertan en impedimentos.
  2. Tener una visión compartida de lo que se viene encima. Si todos conocemos las implicaciones de desarrollar una nueva funcionalidad es mucho más sencillo comprometerte con un plan de trabajo a que si las estimaciones nos vienen «impuestas».

Por todo lo anterior expuesto, lo ideal es que todos estimen el esfuerzo que implica desarrollar una funcionalidad ya que con ello promovemos:   

  1. El sentimiento de equipo, en el que trabajo y la responsabilidad es de todos y no que cada uno hace su parte y se desentiende del resto.
  2. El conocimiento en T de los miembros del equipo, es decir que cada integrante tenga un conocimiento básico o medio de todo y un conocimiento muy especializado en un área (su especialidad). 
  3. Evitamos el bus factor, favoreciendo que el desarrollo de nuestro software pueda continuar a pesar de la ausencia de alguno de los desarrolladores. 

Y ¿cómo se consigue esto? con mucha conversación, colaboración, práctica y con personas que sean virtuosas en práctica de los cinco valores de Scrum: compromiso, coraje, foco, apertura y respeto. Como sabemos, cada equipo es un mundo y la forma de hacerles coaching debe adaptarse a cada uno, por lo que no hay una fórmula única para conseguir llevarlos a la estimación global. Sin embargo, partiendo del principio de que estamos realizando estimaciones relativas, es decir, estimaciones que son relativas a una funcionalidad base que el equipo ya ha implementado o que le es sencillo estimar, propongo dos estrategias que pueden ayudar a los equipos a estimar a nivel de funcionalidades:

  • Hacer las cosas bien desde un principio: esto es, ante la pregunta inicial, se explica con argumentos sólidos y claros por qué todos deben estimar todo el esfuerzo y como la experiencia puede ser abrumadora en un principio, pero también como, con el paso del tiempo y a base de mucha conversación sobre lo que implica el trabajo del compañero, todos comenzaremos a comprender más y mejor su competencia, trayendo como consecuencia que nuestras estimaciones sean cada vez más realistas (no más precisas).      
  • Un paso intermedio antes: esto es, permitimos que se sub-dividan una funcionalidad  en sub-tareas técnicas y que se estimen por separado, pero incentivamos de alguna manera el intercambio de conocimientos. Propongo dos técnicas:
    • En las conversaciones en la que se discute el trabajo de una tarea de front, por ejemplo, los de back deben estar atentos comprendiendo a groso modo las implicaciones de «su trabajo» y viceversa. 
    • Otra técnica, es que: dada un tarea de front, pidamos a los especialistas en back que describan lo que ellos consideran que implica llevar a cabo esa tarea, luego pedir a los front que corroboren o confirmen esa aproximación y luego a la inversa con una tarea de back.

Lo importante es motivar que haya un intercambio de papeles logrando con ello reducir el desconocimiento sobre el área de conocimiento del compañero. Finalmente, después de realizar varias sesiones de estimación de esta manera deberíamos cambiar al modelo de estimaciones a nivel de funcionalidades. Hacerlo así permite una evolución paulatina hacia el modelo ideal.   

En conclusión, generalmente al principio los equipos serán pocos maduros y muy especializados, por lo que es casi seguro que nos topemos con esta duda o dificultad, pero es nuestro trabajo como promotores del cambio, conseguir una transformación cultural tal, que les permita comenzar a ver el trabajo como una unidad, de la cual todos son responsables y que a pesar de que no todos sepan de todo a bajo nivel, puedan comprender el trabajo del otro lo suficiente como para poder estimar el esfuerzo que cuesta una unidad de trabajo en conjunto. ¿Tú que opinas? ¿Alguna otra experiencia o técnica que nos puedas compartir? 

La entrada Dificultades a la hora de estimar se publicó primero en Adictos al trabajo.

Como instalar Redmine y su Agile Plugin utilizando Docker y la interfaz gráfica Kitematic

$
0
0

1. Introducción

Hola a todos, seguro que muchos diréis que ya existía una entrada previa que contaba cómo instar una versión de Redmine en una máquina virtual y conectarla con Subversion.

Pero aprovechando la tendencia de los contenedores y las ventajas que nos dan, voy a enseñaros cómo montar rápidamente una instalación del software de gestión Redmine con una versión actualizada, usando para ello la interfaz gráfica Kitematic para la gestión del contenedor Docker correspondiente.

Es importante recordar que aunque Redmine es GPL, no sucede lo mismo con su plugin, que sí es de pago, aunque podemos usar una versión reducida Light, que sí es gratuita. En cualquier caso, esta versión es muy limitada y para trabajar medianamente bien necesitaremos pasar por caja para adquirir la versión PRO con un coste de 399$. Eso sí, podemos usar su Trial de 30 Días.

2. Entorno

Para realizar la instalación y el tutorial he utilizado:

Hardware: MacBook Pro 15″ 2018 – 2,2GHz i7 – 32GB (macOS Mojave 10.14.4)

Software:

3. Instalación

3.1 Registrarnos en DockerHub

En primer lugar nos registramos (gratis) en  DockerHub, para ello accedemos a https://hub.docker.com/signup y seguimos los pasos del registro.

DockerHub página de registro
DockerHub página de registro

3.2 Descargar Docker Desktop

Una vez registrados accedemos a https://hub.docker.com/ y seleccionamos la opción Get started with Docker Desktop que nos llevará al menú de descarga.

Página de acceso a la documentación y descargas de Docker Desktop
Docker Desktop descarga

Entre las opciones de descarga tenemos Windows y Mac, por defecto nos ofrecerá la que se corresponda con nuestro sistema operativo. Pulsamos Download Docker Desktop for Mac para comenzar la descarga.

Descarga directa del Docker.dmg para macOS
Descarga directa del Docker.dmg para macOS

Una vez descargado el archivo Docker.dmg vamos a la carpeta Descargas de nuestro mac y lo ejecutamos haciendo doble clic. Seguimos el proceso de instalación y acabamos añadiendo Docker a las aplicaciones.

Para finalizar la instalación de Docker, arrastramos para añadir a aplicaciones
Para finalizar la instalación de Docker, arrastramos para añadir a aplicaciones

Cuando intentemos ejecutarlo por primera vez, el sistema operativo nos pedirá por seguridad que confirmemos que queremos abrirlo al tratarse de una descarga de internet, confirmamos pulsando Open o Abrir (depende del idioma que tengáis configurado).

Ventana de macOS donde pide que se confirme por seguridad que queremos ejecutar Docker
Aviso de seguridad de macOS para Docker

3.3 Instalar Kitematic

Una vez arrancado Docker desktop nos aparecerá un icono en la barra superior. Para instalar la interfaz gráfica, sacamos el menú secundario y seleccionamos la opción Kitematic.

Esto lanzará la instalación, una vez instalada, seguimos de nuevo estos pasos para arrancar la interfaz.

Instalación de Kitematic desde el icono de Docker.
Instalación de Kitematic desde el icono de Docker.

Una vez finalizada la instalación nos aparecerá la interfaz gráfica.

Pantalla principal de la interfaz de Kitematic.
Pantalla principal de la interfaz de Kitematic.

Para poder utilizarla  debemos conectarnos a Docker Hub desde donde podremos descargar las imágenes de los Docker que queremos utilizar, en nuestro  caso el de Redmine.

Debemos logarnos en Kitematic para poder usarlo.
Debemos logarnos en Kitematic con el usuario de DockerHub para poder usarlo.

3.4 Obtener la imagen de Redmine

Una vez logados en Docker Hub buscamos la imagen de Redmine a través del cuadro de búsqueda. os aparecerán los resultados, y entre ellos cabe destacar dos, redmine official, que es la imagen que vamos a usar para este  tutorial y redmine bitnami, que os la recomiendo  al final del tutorial para empezar a curiosear más allá.

Buscamos la imagen de Redmine con el buscador en DockerHub
Buscamos la imagen de Redmine con el buscador

Pulsamos el botón CREATE de la imagen redmine official y veremos cómo empieza a descargarse y arranca de Docker.

Imagen de como aparece agregada la imagen de Redmine a nuestro Kitematic.
Imagen de como aparece agregada la imagen de Redmine a nuestro Kitematic.

Una vez arrancado el Docker, en la parte superior aparecerá la etiqueta verde RUNNING. Podemos comprobar que está funcionando nuestro  Redmine accediendo a la interfaz web a través de la opción WEB PREVIEW en la parte  derecha de la interfaz, en el icono que es un  cuadrado con  una flecha, justo al lado de los ajustes.

Arrancamos el contenedor usando la imagen como base mediante la acción START de Kitematic
Arrancamos el contenedor usando la imagen como base mediante la acción START de Kitematic

Nos aparecerá la pantalla principal de Redmine para que empecemos a configurarlo. Podemos  entrar con admin/admin.

Usamos la opción WEB PREVIEW de Kitematic para acceder a la URL de Redmine a través del navegador.
Usamos la opción WEB PREVIEW de Kitematic para acceder a la URL de Redmine a través del navegador.

3.5 Descargar el plugin Agile

Antes incluso de empezar a configurar nada, vamos a instalar el plugin que nos permitirá usar redmine en modo ágil. Accedemos a https://www.redmineup.com/pages/plugins/agile y pulsamos Download now

RedmineUp Agile plugin
RedmineUp Agile plugin para descargar

Entre las opciones seleccionamos Light (Free), nos aparecerá un popup para introducir nuestro email y que nos llegue el enlace de descarga.

Redmine Agile plugin precios
Redmine Agile plugin precios
Redmine Agile Plugin solicitud de email para descarga
Redmine Agile Plugin solicitud de email para descargar

Esta versión nos ofrecerá la funcionalidad mínima, los Agile boards y la Burn Down Chart.

3.6 Instalar el plugin Agile

Una vez descargado el archivo redmine_agile.zip vamos a la carpeta de descargas y descomprimimos el fichero por el método que prefiráis, en mi caso doble clic en el .zip desde la interfaz gráfica. Se nos creará un directorio redmine_agile que tendremos que mover a la carpeta de plugins de nuestro contenedor.

Para ello vamos a necesitar abrir nuestro Terminal y movernos a la carpeta de descargas, habitualmente: /Users/vuestrousuario/Downloads

Usaremos el comando docker cp. para poder copiar la carpeta a nuestro contenedor:

docker cp redmine_agile redmine:usr/src/redmine/plugins/

Podemos comprobar que la instalación ha sido correcta arrancando Redmine y comprobando que está activa la opción de configuración Agile en el menú de Administración.

También podemos verificar que se ha copiado arrancando el terminar de nuestro Docker a través de Kitematic. Al arrancar el Docker en la parte superior aparecerá un nuevo botón EXEC, lo pulsamos y nos lanzará el terminal con el que trabajar directamente en el Docker.

NOTA: Alguna instalación de Kitematic esto nos lleva al terminal local y no al del Docker. Para lanzar manualmente el del docker correspondiente ejecutamos docker exec -it mnombredeldocker sh lo que abrirá el terminal con el que operar sobre nuestro contenedor. En nuestro caso será docker exec -it redmine sh. 

Gracias a mi compañera Anaís por este aporte, ya que probando el tutorial ha encontrado esta excepción.

Una vez en nuestro contenedor. Navegamos a la  ruta /usr/src/redmine , accedemos al directorio de plugins con cd plugins y listamos con ls para comprobar que se ha copiado el plugin.

Directorio de plugins de Redmine en el Docker vacío
Directorio de plugins de Redmine en el Docker vacío
Directorio de plugins de Redmine en el Docker tras copiar el plugin
Directorio de plugins de Redmine en el Docker tras copiar el plugin

Ahora vamos al directorio raíz de Redmine (será aquel que contenga el archivo config.ru),  en mi caso /usr/src/redmine Y desde este directorio instalamos sus dependencias. Ejecutamos:

bundle install --without development test

Redmine agile, instalación de las dependencias (gems)
Redmine agile, instalación de las dependencias (gems)

En caso de que os falle la ejecución del comando, podéis instalar Bundler antes que es el responsable de gestionar las dependencias, lo hacemos con:

gem install bundler

Instalación de bundler por comando para gestionar las dependencias.
Instalación de bundler por comando para gestionar las dependencias.

A continuación migramos las tablas del plugin con el comando:

bundle exec rake redmine:plugins NAME=redmine_agile RAILS_ENV=production

Y por último reiniciamos la aplicacióin de Redmine con: touch tmp/restart.tx

touch tmp/restart.tx

3.7 Configurar el almacenamiento de archivos adjuntos

Para que tengamos persistencia de los ficheros  que subimos a nuestro Redmine, tenemos  que configurar un volumen local en nuestra máquina de forma, que al «tirar abajo» nuestro contenedor no se borren.

Para ello en Kitematic, en la parte superior derecha vamos a la pestaña Settings y la opción Volumes. Veremos que por defecto la imagen está configurada para persistir el directorio /usr/src/redmine/files que es el lugar donde se almacenarán los ficheros que  subamos a nuestro redmine.

Kitematic configurar persistencia con volumes
Kitematic configurar persistencia con volumes

Lo que haremos será mapear un directorio local de la máquina física en la que esté corriendo el docker para que los cambios se mantengan. Pulsamos el botón CHANGE, y añadimos la ruta del directorio local, en mi caso, dentro del usuario …/Documents/docker_redmine

Volumen local de persistencia configurado en Kitematic
Volumen local de persistencia configurado en Kitematic

Y con esto ya estamos preparados para empezar a usar nuestro Redmine.

Para probar que funciona correctamente podemos simplemente ir Redmine, abrir la wiki ya cargar un archivo.

OJO!! Durante la elaboración del tutorial han cambiado las versiones tanto de Redmine (4.0.3 a la 4.0.4) como del plugin (1.4.10 a 1.4.12), lo que ha provocado que se desalinearan las versiones originales con las que empecé el tutorial. La versión 1.4.10 del plugin no es compatible con la 4.0.4 de Redmine. Yo tenía el plugin en local y estaba lanzando un docker con la nueva  versión de redmine de Dockerhub, lo que hacía que fallasen las dependencias y me devolviese constantemente un error rake aborted! Os recomiendo siempre que comprobéis las versiones.

Si tenéis algún problema durante la instalación, os recomiendo echar un vistazo a esta página de la documentación oficial donde aparecen los errores más comunes.

4. Conclusiones

Esto solo es el principio, solo está pensado para familiarizarse un poco con Docker a través de la herramienta visual Kitematic y poder hacer algunas pruebas con la herramienta sin tener que realizar una configuración pesada.

Para poder usar Kitematic sobre Docker de una forma intensiva, es necesario pensar en montar una base de datos externa más allá del SQLite que trae embebido Redmine. Esto podemos hacerlo también en otro Docker y orquestarlo.

Si queréis probar esta configuración con una base de datos externa, os recomiendo utilizar como imagen de referencia la de Bitnami en lugar de esta que hemos usado aquí.

La entrada Como instalar Redmine y su Agile Plugin utilizando Docker y la interfaz gráfica Kitematic se publicó primero en Adictos al trabajo.

Viewing all 990 articles
Browse latest View live