Lemoncode blog


Artículos, ideas, desarrollos, notas y mucho más

Beca Yes We Tech - Bootcamp JavaScript Lemoncode

Presentación

En anteriores ocasiones hemos colaborado con Yes We Tech en ofrecer una beca para cubrir los estudios de nuestro Máster Front End, dicha beca estaba orientada a desarrolladoras que ya tenían conocimientos previos de fundamentos de programación.

Esta vez ofrecemos dos Becas que cubren el 100% del coste en nuestro Bootcamp JavaScript: una formación que partiendo de cero, te permite adquirir conocimientos básicos en programación, teniendo como lenguaje de aplicación JavaScript.

logoyeswetech.png

¿Cómo funciona este Bootcamp?

- El bootcamp con una serie de módulos que te introduce en el mundo de la programación.
- Cada módulo cuenta:
- Con unas guías para su estudio.
- Con unas sesiones grabadas donde se explican los conceptos básicos (estas son clases que se impartieron
donde también se da respuesta a preguntas de alumnos).
- Con unos laboratorios que la alumna debe entregar para poner en práctica lo aprendido.
- Para ayudarte a lo largo de este camino a la alumna se le asigna un mentor, que está disponible tanto para
dudas cortas como para tutorías (las tutorías se imparten vía videoconferencia).

bootcamp-javascript-online-1-638.jpg


¿ Qué prerréquisitos tiene?


- Tener conocimientos avanzados de informática a nivel de usuario (manejo del ordenador, herramientas etc...).
- Tener ganas de aprender a programar.
- Tener tiempo disponible.

¿Qué sabré al acabar esta formación?

- Si la programación es algo a lo que te quieres dedicar y se te da bien.
- Dejar de "copiar y pegar" y saber cómo implementar algoritmos.
- Unos conocimientos básicos en desarrollo web.

Es decir, vas a tener una base sólida sobre la que continuar aprendiendo para forjar tu carrera para
desarrolladora Front End.

¿Dónde puedo encontrar más información acerca de este Bootcamp?

Su página oficial: https://lemoncode.net/bootcamp-javascript#bootcamp-javascript/inicio

En qué consiste: https://lemoncode.net/lemoncode-blog/2019/10/29/bootcamp-javascript-continuo

Preguntas frecuentes: https://lemoncode.net/lemoncode-blog/2019/11/13/preguntas-frecuentes-bootcamp-javascript-continuo

¿Cuáles son las bases de esta beca?

Hemos trabajado con Yes We Tech y resuelto unas bases para poder inscribiros y seleccionar a la aspirante:

Requisitos

- Pertenecer a minorías con baja representación en el sector profesional, es decir, mujeres y en especial quienes - sean inmigrantes, pertenezcan al colectivo LGTBQIA+, estudiantes y mujeres con diversidad funcional.

- Ingresos inferiores a 27.000€ brutos año o contar con cargas familiares.

- Tener conocimientos de informática a nivel de usuario avanzado.

Criterios de evaluación

- Horas de estudio que la candidata pueda dedicar a la semana.

- Curriculum Vitae.

- Entrevista personal y opción de realizar prueba técnica a las finalistas.

Información mínima a enviar

- Documento personal identificativo (DNI, NIE, Pasaporte)

- Carta explicando los motivos por los que la alumna está interesada en realizar el máster.

- Número de horas de estudio semanal que le puede dedicar al máster

- Curriculum Vitae.

- Información salarial (no es necesario el envío de documentación acreditativa, pero se podría requerir si la candidata llega a finalista).

Fechas

Aceptación candidatas del 2 de noviembre al 31 de diciembre de 2020.

Fecha resolución beca (2 plazas) 11 de Enero de 2020.

Fecha arranque 18 de Enero de 2020

Comite evaluador

Miembros de la comunidad YesWeTech.

Miembros de Lemoncode Formacion S.L.

Para aplicar envíanos un correo con la información que te pedimos a nuestra cuenta de EMail: formacion@lemoncode.net







Comment

Beca Yes We Tech - Novena edición Máster Front End Lemoncode

En colaboración con YesWeTech ofrecemos nuestra cuarta beca de estudios para aportar nuestro granito de arena a conseguir que haya más diversidad e igualdad de oportunidades en esta profesión. ¿En qué consiste? En cubrir el 100% del coste del Máster Front End Online Lemoncode a una alumna que pertenezca a una minoría con baja representación en el sector profesional. Ofrecemos una plaza, y para ello hemos resuelto unas bases para poder inscribiros y seleccionar a la aspirante.

Comment

Bootcamp Javascript Online Lemoncode - Opiniones de los alumnos

Un tema importante a valorar en una formación técnica es la opinión que tienen antiguos alumnos sobre la misma ¿ Qué tal ha ido la cosa? ¿ Se aprende? ¿ Lo ha cursado alguien con mi perfil?

Aprovechando que tenemos alumnos que han superado la primera edición, así como el bootcamp continuo, os adjuntamos testimonios de primera mano.

Comment

Máster Front End Online Lemoncode - Calendario VIII Edición

La semana que viene arrancamos con la octava edición del Máster Front End Online Lemoncode.

Hemos estado ajustando el calendario lectivo, este año:

  • Arrancamos el 17 de Abril de 2020.

  • Finalizamos el periodo de docencia el 30 de Octubre de 2020

  • Hacemos un pausa en Agosto para que todos tomemos aire (estaremos disponibles para tutorías y corregir entregas).

  • Las clases tiene una duración de 3 horas, y el grueso de las mismas las impartimos los viernes de 19:00 a 22:00 y los sábados de 10:00 a 13:00, además de esto tenemos algunas máster class que se imparten entre semana (si no te cuadra este horario también puedes optar por la versión continua de nuestro máster).

¿ Cómo hemos repartido las clases? Veamos el calendario…

Comment

Hola Docker CI / CD - GitHub Actions

Introducción

Como hemos dicho en anteriores artículos de nuestra serie Hola Docker, ejecutar manualmente procesos de builds, lanzar baterías de test y desplegar puede llegar a ser una pesadilla y no estaríamos a salvo de numerosos erróres.

  • Los errores humanos suceden con mucha facilidad.
  • Cuando los proyectos crecen, los procesos de build tienden a complicarse.
  • Lidiar con diferentes versiones entre entornos no es algo sencillo.
  • Cambiar de versiones no es un proceso fácil en muchos casos.

¿Y si...?:

  • Los procesos de build pudieran automatizarse, incluyendo indicadores que nos informen sobre si el proceso se ha completado (por ejemplo, ejecutando baterías de test).
  • Los procesos de build pudieran arrancar cuando ocurriera un merge para una rama concreta, o se realizará un push al repositorio remoto.
  • Cada proceso de build generase una imagen de Docker, y de este modo no nos tendríamos que preocupar por configuraciones en el servidor, o no tener instaladas las versiones exactas de software.
  • No ocupáramos demasiado espacio en disco ya que comenzaríamos desde una imagen base de Docker que tuviera el sistema operativo y software preinstalado.
  • Pudiéramos subirlo a un cloud hub registry, permitiendo ser consumido desde cualquier proveedor local o de cloud (Amazon, Azure, Google Cloud...)
  • Pudiéramos intercambiar fácilmente distintas versiones de build.

Todos estos son los beneficios que podemos obtener al configurar un servidor de CI/CD con GitHub Actions (CI proviene de Continuos Integration, CD significa Continuos Delivery) y mezclarlo con la tecnología de contenedores Docker.

Este es el tercer artículo de la serie Hola Docker, en estos enlaces tienes disponible la primera y segunda publicación.

TL; DR;

En este artículo usaremos GitHub Actions para activar automáticamente los siguientes procesos en cada merge to master o pull request:

  • Levantaremos una instancia linux + nodejs limpia.
  • Descargaremos el código fuente del repositorio.
  • Instalaremos las dependencias necesarias del proyecto.
  • Ejecutaremos la batería de test asociados.
  • Generaremos la imagen de docker, incluyendo la build de producción.
  • Etiquetaremos y publicaremos la imagen generada en el Docker Hub Registry.

Esta configuración la usaremos tanto para el proyecto de Front End como para el proyecto de Back End.

build-process-summary.jpg

Si quieres profundizar en los detalles sigue leyendo :)

Agenda

Los pasos para llevarlo a cabo son los siguientes:

  • Presentaremos el proyecto de ejemplo.
  • Trabajaremos de forma manual con Docker Hub.
  • Después enlazaremos nuestro proyecto de GitHub con GitHub Actions.
  • Estableceremos los pasos para un proceso de CI.
  • Comprobaremos el resultado de nuestra batería de test.
  • Verificaremos si el contenedor de Docker puede ser generado.
  • Subiremos una imagen al Docker Hub Registry (incluyendo el número de compilación).
  • Comprobaremos si nuestro proceso de Continuous Delivery ha tenido éxito para consumir nuestras imágenes desde un fichero docker compose.

Proyecto de ejemplo

Tomaremos como ejemplo una aplicación de chat. La aplicación está dividida en dos partes: el cliente (Front End) y el servidor (Back End) que serán encapsulados en contenedores Docker (a esta acción la denominaremos "dockerizar" de ahora en adelante), y desplegados mediante Docker Containers.

chat-app.gif

Ya tenemos listos un par de repositorios que juntos crean la aplicación de chat:

Siguiendo la aproximación actual de despliegue (revisar primer post de esta serie), una tercera parte será incluida también: un balanceador de carga. Su responsabilidad será direccionar el tráfico al front o back dependiendo de la url de la petición. El balanceador de carga también será "dockerizado" y desplegado usando Docker Container.

Docker Hub

En nuestro último artículo tomamos como punto de partida una imagen de Docker con Ubuntu + Nodejs; fue estupendo recuperarlas desde Docker Hub y tener el control de que versión estábamos descargando.

¿No sería genial ser capaces de subir nuestras propias imágenes a Docker Hub Registry, incluyendo la versión? Eso es lo que Docker Hub nos ofrece: puedes crear tu propia cuenta y subir las imágenes de Docker.

Ventajas de usar Docker Hub:

  • Podemos mantener varias versiones de las imágenes de nuestros contenedores (genial para soportar distintos entornos, A/B testing, canary deployment, green blue deployment, rolling back, etc...).
  • Dado que Docker se crea a partir de una serie de capas, nuestra imagen custom no ocupará más espacio del necesario, es decir, un contenedor construido sobre un contenedor linux no desplegará el SO, sólo apuntará a la imagen anterior.
  • Como la imagen del contenedor ya esta en la nube, desplegar en el proveedor de cloud es sencillo.

Docker Hub es fantástico para arrancar: podemos crear una cuenta gratis y subir nuestras imágenes Docker (la versión gratuita permite crear repositorios públicos ilimitados y uno sólo privado).

Si más tarde necesitamos usarlo para propósitos de negocio y restringir el acceso, podemos usar un registro de Docker privado, algunos proveedores son:

GitHub Actions

Aunque Docker nos ayuda a estandarizar la creación de un entorno y una configuración predeterminadas, la creación de nuevas versiones de forma manual puede convertirse en un proceso complicado y propenso a errores:

  • Tenemos que descargar el estado del código adecuado para el proceso de build.
  • Ejecutar los tests automatizados y comprobar que pasan en verde.
  • Tenemos que generar nuestra imagen de Docker.
  • Añadir el versionado adecuado (etiquetar la imagen en los términos de Docker).
  • Y por último, debemos subir la imagen manualmente al registro de Docker Hub.

Imaginemos hacer esto en cada proceso de merge to master; quedaríamos agotados con este infierno de despliegue... ¿Existe alguna manera de automatizar esto? ¡GitHub Actions al rescate!

Simplemente invirtiendo algo de tiempo en crear una configuración inicial, GitHub Actions automáticamente:

  • Notificará de cualquier PR o merge to master (se pueden establecer políticas de notificación).
  • Creará un entorno limpio (por ejemplo, en nuestro caso tomará una imagen de Ubuntu + Nodejs).
  • Descargará el estado de código adecuado desde el repositorio.
  • Ejecutará los tests.
  • Creará la imagen de Docker que contendrá la build de producción.
  • En caso de tener éxito, desplegará en el registro de Docker Hub (añadiendo la etiqueta de versión siguiendo la notación de Docker).

Una de las ventajas de GitHub Actions es que es bastante sencillo de configurar:

  • No necesitas instalar infraestructura (está basado en un entorno cloud).
  • La principal configuración se realiza mediante un fichero yml.
  • Ofrece una versión community edition donde podemos jugar con nuestros proyectos de prueba o usarlo para nuestros proyectos open source (sólo proyectos públicos).
  • Ofrece una versión enterprise para nuestros proyectos privados.

Configuración de cuentas

Fork sobre los proyectos de ejemplo

Si quieres seguir este tutorial, puedes empezar por hacer un fork de los repositorios de Front End y Back End:

fork.png

Al hacer fork de estos repositorios, una copia se creará en tu cuenta de GitHub y podrás enlazarlos a tu cuenta de GitHub Actions y configurar el proceso de CI.

Registro de Docker Hub

En nuestro artículo previo de esta serie, consumíamos una imagen de Docker Hub. Si queremos subir nuestras propias imágenes a Docker Hub (gratuito para imágenes públicas), necesitamos crear una cuenta. Puedes crear tu cuenta en el siguiente enlace.

Comencemos

En este tutorial, aplicaremos la automatización a los repositorios (los cuales hemos hecho previamente un fork para que aparezcan en nuestra cuenta de Github) de la aplicación de chat usando herramientas a disposición en GitHub Actions. GitHub Actions lanzará una tarea después de cada commit donde se realizarán lss siguientes pasos:

  • Se ejecutarán los test.
  • La aplicación será "dockerizada".
  • La imagen creada será subida al Docker Registry, en nuestro caso Docker Hub.

Subiendo una imagen de forma manual a Docker Hub

Antes de comenzar con la automatización, hagamos este proceso manualmente.

Una vez que tengamos una cuenta en Docker Hub, podemos interactuar con la plataforma desde la shell (abrir un bash terminal, o windows cmd).

Nos autentificamos contra Docker Hub.

$ docker login

Para poder subir nuestras imágenes, tienen que estar etiquetadas siguiendo el siguiente patrón:

<nombre_usuario_Docker_Hub>/<nombre_imagen>:[version]

La versión es opcional. Si no la especificamos, se aplicará latest.

$ docker tag front <nombre_usuario_Docker_Hub>/front

y finalmente la subimos

$ docker push <nombre_usuario_Docker_Hub>/front

Desde ahora, la imagen estará disponible para el uso de cualquier usuario.

$ docker pull <nombre_usuario_Docker_Hub>/front

Para crear el resto de las imágenes que necesitamos, debemos seguir exactamente los mismos pasos.

Como hemos dicho anteriormente, todas las tareas que se repiten se pueden llegar a convertir en un proceso tedioso y donde podemos cometer errores. En los siguientes pasos aprenderemos como automatizarlo usando la CI/CD de GitHub Actions.

Enlazando las credenciales de Docker Hub

Una vez hemos hecho fork de los proyectos, necesitamos entrar en cada una de las configuraciones del proyecto (Back y Front) y establecer como variables de entorno el usuario y la clave de Docker Hub.

Front

01-github-secrets.png

Back

01-github-secrets.png

Estas variables se usarán después para registrarnos en Docker Hub (nota: la primera vez que introducimos los datos, aparecen como texto plano. Una vez ya establecidas, aparecen como campos de clave).

Finalmente podemos empezar la automatización de nuestras tareas.

Configuración de GitHub Actions

Para llevar a cabo la configuración de GitHub Actions necesitamos crear un fichero llamado main.yml en la carpeta .github/workflows de tu proyecto. Es aquí donde describiremos las acciones que serán ejecutadas por GitHub Actions. Podemos tener tantos ficheros yml como quisiéramos.

Back End

Los pasos para crear el fichero yml para la aplicación de back end son los siguientes:

  1. Nombrar el flujo de trabajo a crear: en este caso, vamos a elegir Backend Chant CI/CD.
  2. Definir los triggers o desencadenadores de eventos: indicamos a GitHub Actions cuando ejecutar un flujo de trabajo específico.
  3. Seleccionar el SO: seleccionar el sistema operativo donde queremos ejecutar nuestra aplicación.
  4. Obtener el repositorio: obtener acceso a todos los archivos en el repositorio.
  5. Configuración de node: instalar nodejs con una versión específica.
  6. Instalar dependencias: como en un entorno en local, podemos ejecutar npm install.
  7. Testing: en nuestro caso ejecutaremos las pruebas unitarias que se han implementado para la aplicación.
  8. Iniciar sesión en Docker Hub: antes de enviar una imagen al Docker Hub Registry, debemos iniciar sesión en Docker Hub.
  9. Crear imagen de Docker: si las pruebas pasan en verde, simplemente creamos la imagen del contenedor (esto buscará un archivo Dockerfile en el raíz de repositorio y seguirá las instrucciones de ese fichero para crear una build de producción, almacenándola en un contenedor de imagen Docker).
  10. Etiquetar imágenes de Docker: necesitamos identificar la imagen del contenedor con una etiqueta determinada.
  11. Subir las imágenes de Docker: subir la imagen generada en el Docker Hub Registry.

Un resumen del proceso de build:

backend-flow.gif

1. Nombrar el flujo de trabajo a crear

Vamos a crear nuestro fichero main.yml en la carpeta .github/workflows de nuestro repositorio de backend:

00-main.yml-file.png

Comenzaremos indicando el nombre del flujo de trabajo.


  ./.github/workflows/main.yml

  + name: Backend Chat CI/CD
  

2. Definir los triggers o desencadenadores de eventos

Podemos activar este flujo de trabajo en varios eventos como push, pull_request, etc.

En este caso, vamos a inicial el proceso de CI/CD cada vez que haya un push to master o un pull request hacia master.


  ./.github/workflows/main.yml

  name: Backend Chat CI/CD

  + on:
  +   push:
  +     branches:
  +       - master
  +   pull_request:
  +     branches:
  +       - master
  

3. Seleccionar el SO

Seguidamente, podemos crear diferentes trabajos para builds, despliegues, etc... Comenzaremos con el trabajo de CI (build y test) y seleccionaremos el sistema operativo en el que se ejecutarán todas las pruebas. En este enlace tienes la lista de sistemas operativos disponibles.

En este caso, elegiremos una instancia de Linux (Ubuntu).


  ./.github/workflows/main.yml

  name: Backend Chat CI/CD

  on:
    push:
      branches:
      - master
    pull_request:
      branches:
        - master

  + jobs:
  +   ci:
  +     runs-on: ubuntu-latest
  

4. Obtener el repositorio

Necesitamos acceder a todos los ficheros del repositorio, por lo que debemos clonar el repositorio en este entorno. En lugar de definir manualmente todos los pasos para clonarlo desde cero, podemos usar una action ya creada, es decir, tenemos actions oficiales disponibles de los equipos de GitHub u otras compañías en el GitHub Marketplace. En este caso, utilizaremos la action checkout previamente definida para realizar el proceso de obtener nuestro repositorio de Github (descargaremos el repositorio en una carpeta determinada):


  ./.github/workflows/main.yml

  ···
  
  jobs:
    ci:
      runs-on: ubuntu-latest

  +   steps:
  +     - uses: actions/checkout@v1
  

5. Configurar node

Nosotros usaremos otra action para configurar node. Esta action está desarrollada también por el equipo de GitHub y nos permitirá poner en marcha el entorno de nodejs (incluso podremos especificar una versión):

En este caso vamos a solicitar la versión 12.x de nodejs.


  ./.github/workflows/main.yml

  ···
  
    steps:
      - uses: actions/checkout@v1
  +   - uses: actions/setup-node@v1
  +     with:
  +       node-version: '12.x'
  

6. Instalar dependencias

Comenzamos teniendo una máquina Ubuntu + nodejs levantada y en ejecución. Con la action checkout ya hemos descargado el código fuente de nuestro repositorio, así que es el momento de ejecutar npm install antes de comenzar la ejecución de las pruebas unitarias.

Para ejecutarlo, crearemos un nuevo paso con el nombre Install y ejecutaremos el comando npm install en la sección run.


  ./.github/workflows/main.yml

  ···
  
     steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
        with:
          node-version: '12.x'
  +   - name: Install
  +     run: npm install
  

7. Testing

Todo la fontanería está lista, así que vamos a agregar un comando npm test. De esta forma lanzaremos nuestra batería de test.


  ./.github/workflows/main.yml

  ···

     steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
        with:
          node-version: '12.x'
  -   - name: Install
  +   - name: Install & Tests
  -     run: npm install
  +     run: |
  +       npm install
  +       npm test
  

Fíjate, hemos incluido los scripts de install y test en un paso común (tenemos que agregar un carácter de barra inclinada para indicar que vamos a ejecutar varios scripts en el mismo paso), pero también podíamos haberlo dividirlo en pasos separados.

8. Iniciar sesión en Docker Hub

Justo después de que se hayan ejecutado todos los scripts, queremos subir la imagen de Docker que generaremos en el Docker Hub Registry.

Como GitHub Actions se ejecuta sobre el sistema operativo que habíamos definido en la sección runs_on, podemos hacer uso del software preinstalado en estos entornos virtuales, para acceder a docker sin necesidad de ejecutar un paso de instalación de Docker.

Lo primero que haremos será iniciar sesión en Docker Hub (para ello haremos uso de las variables de entorno agregadas a la sección secrets de nuestro repositorio. Revisa la sección Enlazando las credenciales de Docker Hub de este artículo).

Vamos a crear una nueva tarea, esta será la tarea de cd (Continous Delivery) y hará lo siguiente:

  • Esperará a que la tarea previa de ci (Continous Integration) se complete de forma satisfactoria.
  • Como estamos definiendo una nueva tarea, necesitamos volver a revisar el repositorio (estamos usando una instancia nueva).
  • Iniciará sesión en Docker Hub utilizando las credenciales secrets del contexto de GitHub que hemos definido.

  ./.github/workflows/main.yml

  ···

   jobs:
    ci:
      runs-on: ubuntu-latest
  
      steps:
        - uses: actions/checkout@v1
        - uses: actions/setup-node@v1
          with:
            node-version: '12.x'
        - name: Install & Tests
          run: |
            npm install
            npm test
  + cd:
  +   runs-on: ubuntu-latest
  +   needs: ci

  +   steps:
  +     - uses: actions/checkout@v1
  +     - name: Docker login
  +       run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
  

En el siguiente paso continuaremos trabajando con la tarea de cd (ahora es el momento de construir la imagen de Docker).

9. Construir la imagen de Docker

En el primer artículo de esta serie, creamos un Dockerfile configurando los pasos del proceso de build. Vamos a copiar el contenido de ese archivo y lo colocaremos en el raíz de nuestro repositorio (nombre del archivo: Dockerfile).

05-docker-file.png

./.Dockerfile

FROM node
WORKDIR /opt/back
COPY . .
RUN npm install
EXPOSE 3000
ENTRYPOINT ["npm", "start"]

A modo de recordatorio sobre esta configuración, echemos un vistazo al contenido:

  • FROM node Establecemos la imagen base de node desde Docker Hub image.
  • WORKDIR /opt/back Establecemos nuestro directorio de trabajo en /opt/back.
  • COPY . . Copiamos el contenido en el contenedor. La ruta donde será copiado el contenido será en el directorio de trabajo seleccionado, en este caso /opt/back.
  • RUN npm install Instalamos las dependencias.
  • EXPOSE 3000 Notificamos cuál es el puerto que expone nuestra aplicación.
  • ENTRYPOINT ["npm", "start"] Comando para arrancar nuestro contenedor.

Volvamos al fichero yml: dentro de la tarea cd, justo después del inicio de sesión de Docker, añadimos el comando para construir la imagen del contenedor Docker.


  ./.github/workflows/main.yml

  ···

      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
  +     - name: Build
  +       run: docker build -t back .
  

Este comando buscará el fichero Dockerfile, que creamos en la raíz del repositorio de backend, y seguirá los pasos para realizar el proceso de build de la imagen.

¡Un momento! Acabo de caer en la cuenta de que aquí hay algo extraño: estamos utilizando distintos contenedores. GitHub Actions ejecuta los tests en una instancia de linux y el Dockerfile usa otra configuración de linux / node extraída del Docker Hub Registry. Esto huele mal, ¿no?

¡Totalmente de acuerdo! Ambas configuraciones, la de node y la del fichero Dockerfile deberían arrancar desde la misma imagen. Necesitamos asegurarnos de que la prueba se ejecute con la misma configuración como si estuviéramos en producción. La mejor solución pasa por configurar la sección container dentro de una tarea para ejecutar una action desde la imagen de Docker:

jobs:
  my_job:
    container:
      image: node:10.16-jessie
      env:
        NODE_ENV: development
      ports:
        - 80

Podríamos actualizar nuestra configuración y tener algo como lo siguiente:


  ./.github/workflows/main.yml

  ···
  jobs:
    ci:
      runs-on: ubuntu-latest
  +   container:
  +     image: node
  
      steps:
        - uses: actions/checkout@v1
  -     - uses: actions/setup-node@v1
  -       with:
  -         node-version: '12.x'
  
        - name: Install & Tests
          run: |
            npm install
            npm test
    cd:
      runs-on: ubuntu-latest
      needs: ci
  
      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
        - name: Build
          run: docker build -t back .
  

Usaremos esta imagen para el paso de ci, pero para el de cd no necesitaremos configurarlo ya que sólo estamos construyendo la imagen Docker definida en el Dockerfile.

10. Etiquetar las imágenes de Docker

La imagen actual de Docker que hemos generado tiene el siguiente nombre: back. Para subirlo al Docker Hub Registry, necesitamos agregar un nombre más elaborado y único:

  • Vamos a etiquetarlo con el nombre de usuario de Docker.
  • Agregaremos un sufijo con un número de build único (en este caso el commit SHA del contexto de Github).

Por otro lado, indicaremos que la imagen actual que hemos generado es la última imagen Docker disponible.

En un proyecto real, esto puede variar según tus necesidades.


  ./.github/workflows/main.yml

  ···
      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
        - name: Build
          run: docker build -t back .
  +     - name: Tags
  +       run: |
  +         docker tag back ${{ secrets.DOCKER_USER }}/back:${{ github.sha }}
  +         docker tag back ${{ secrets.DOCKER_USER }}/back:latest
  

11. Subir las imágenes de Docker

Ahora que hemos identificado nuestra imagen con un nombre único, necesitamos subir la imagen de Docker al Docker Hub Registry. Para ello usaremos el comandos docker push.


  ./.github/workflows/main.yml

  ···
      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
        - name: Build
          run: docker build -t back .
        - name: Tags
          run: |
            docker tag back ${{ secrets.DOCKER_USER }}/back:${{ github.sha }}
            docker tag back ${{ secrets.DOCKER_USER }}/back:latest
  +     - name: Push
  +       run: |
  +         docker push ${{ secrets.DOCKER_USER }}/back:${{ github.sha }}
  +         docker push ${{ secrets.DOCKER_USER }}/back:latest    
  

Fíjate que primero estamos subiendo la imagen ${{ secrets.DOCKER_USER }}/back:${{ github.sha }}, y después la imagen ${{ secrets.DOCKER_USER }}/back:latest

¿Significa esto que la imagen será subida dos veces?... La respuesta es no.

Docker es lo suficientemente listo para identificar que la imagen es la misma, así que le asignará dos "nombres" diferentes a la misma imagen en el Docker Repository

El resultado final

El fichero main.yml debería ser parecido a esto:


  ./.github/workflows/main.yml

  name: Backend Chat CI/CD
  
  on:
    push:
      branches:
        - master
    pull_request:
      branches:
        - master
  
  jobs:
    ci:
      runs-on: ubuntu-latest
      container:
        image: node
  
      steps:
        - uses: actions/checkout@v1
        - name: Install & Tests
          run: |
            npm install
            npm test
    cd:
      runs-on: ubuntu-latest
      needs: ci
  
      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
        - name: Build
          run: docker build -t back .
        - name: Tags
          run: |
            docker tag back ${{ secrets.DOCKER_USER }}/back:${{ github.sha }}
            docker tag back ${{ secrets.DOCKER_USER }}/back:latest
        - name: Push
          run: |
            docker push ${{ secrets.DOCKER_USER }}/back:${{ github.sha }}
            docker push ${{ secrets.DOCKER_USER }}/back:latest
  

Ahora, si subimos esta configuración a GitHub automáticamente lanzará la build.

02-build-in-progress.png

Una vez finalizado, podemos comprobar si la imagen ha sido generada correctamente.

03-build-successfully.png

Y finalmente, la imagen estará disponible en tu cuenta de Docker Hub Registry.

04-published-image.png

Front End

Los pasos para crear le fichero main.yml son muy parecidos a los anteriores (backend):

  1. Nombrar el flujo de trabajo a crear.
  2. Definir los triggers de los eventos.
  3. Definir la tarea de CI (build y test).
  4. Definir la tarea de CD.
  5. Iniciar sesión en Docker Hub.
  6. Construir la imagen de Docker.
  7. Etiquetar las imágenes de Docker.
  8. Subir las imágenes de Docker.

1. Nombrar el flujo de trabajo a crear

Como hicimos con el backend, vamos a definir el flujo de trabajo en el fichero main.yml.


  ./.github/workflows/main.yml

  + name: Frontend Chat CI/CD
  

2. Definir los triggers de los eventos

Seguiremos una aproximación similar al flujo de trabajo de backend. Recuerda que podemos activar este flujo de trabajo en varios eventos como push, pull_request, etc.

En este caso, vamos a inicial el proceso de CI/CD cada vez que haya un push to master o un pull request hacia master.


  ./.github/workflows/main.yml

  name: Frontend Chat CI/CD

  + on:
  +   push:
  +     branches:
  +       - master
  +   pull_request:
  +     branches:
  +       - master
  

3. Definir la tarea de CI (build y test)

Vamos a definir nuestra tarea de ci:

  • Indicaremos que comience desde la imagen de Docker con nodejs (Ubuntu + nodejs) y ejecutará los comandos de npm install y npm test.

  ./.github/workflows/main.yml

  ···

  + jobs:
  +   ci:
  +     runs-on: ubuntu-latest
  +     container:
  +       image: node
  
  +     steps:
  +       - uses: actions/checkout@v1
  +       - name: Install & Tests
  +         run: |
  +           npm install
  +           npm test
  

4. Definir la tarea de CD

Ahora vamos a crear una tarea de cd que comenzará justo después de que la tarea de ci haya sido completada satisfactoriamente y se descargará todos los ficheros del repositorio en la instancia de ejecución.


  ./.github/workflows/main.yml

  ···

  jobs:
    ...
  + cd:
  +   runs-on: ubuntu-latest
  +   needs: ci

  +   steps:
  +     - uses: actions/checkout@v1
  

5. Iniciar sesión en Docker Hub

El siguiente paso será identificarnos en Docker Hub.


  ./.github/workflows/main.yml

  ···

  jobs:
    ...
    cd:
      runs-on: ubuntu-latest
      needs: ci
  
      steps:
        - uses: actions/checkout@v1
  +     - name: Docker login
  +       run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
  

6. Construir la imagen de Docker

Ahora vamos a indicarle que cree la imagen de Docker.


  ./.github/workflows/main.yml

  ···

  jobs:
    ...
    cd:
      runs-on: ubuntu-latest
      needs: ci
  
      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
  +     - name: Build
  +       run: docker build -t front .
  

7. Etiquetar las imágenes de Docker

Recuerda que como hicimos con la aplicación de backend, vamos a etiquetar la versión actual con el commit SHA y definirla como latest.


  ./.github/workflows/main.yml

  ···

  jobs:
    ...
    cd:
      runs-on: ubuntu-latest
      needs: ci
  
      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
        - name: Build
          run: docker build -t front .
  +     - name: Tags
  +       run: |
  +         docker tag front ${{ secrets.DOCKER_USER }}/front:${{ github.sha }}
  +         docker tag front ${{ secrets.DOCKER_USER }}/front:latest
  

8. Subir las imágenes de Docker

Ahora necesitamos subir las imágenes al Docker Hub Registry.


  ./.github/workflows/main.yml

  ···

  jobs:
    ...
    cd:
      runs-on: ubuntu-latest
      needs: ci
  
      steps:
        - uses: actions/checkout@v1
        - name: Docker login
          run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
        - name: Build
          run: docker build -t front .
        - name: Tags
          run: |
            docker tag front ${{ secrets.DOCKER_USER }}/front:${{ github.sha }}
            docker tag front ${{ secrets.DOCKER_USER }}/front:latest
  +     - name: Push
  +       run: |
  +         docker push ${{ secrets.DOCKER_USER }}/front:${{ github.sha }}
  +         docker push ${{ secrets.DOCKER_USER }}/front:latest
  

Y nuestro main.yml debería parecerse a esto:


  ./.github/workflows/main.yml

  name: Frontend Chat CI/CD
  
  on:
    push:
      branches:
        - master
    pull_request:
      branches:
        - master
  
    jobs:
      ci:
        runs-on: ubuntu-latest
        container:
          image: node
  
        steps:
          - uses: actions/checkout@v1
          - name: Install & Tests
            run: |
              npm install
              npm test
  cd:
    runs-on: ubuntu-latest
    needs: ci
  
    steps:
      - uses: actions/checkout@v1
       - name: Docker login
         run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
       - name: Build
         run: docker build -t front .
       - name: Tags
         run: |
           docker tag front ${{ secrets.DOCKER_USER }}/front:${{ github.sha }}
           docker tag front ${{ secrets.DOCKER_USER }}/front:latest
       - name: Push
         run: |
           docker push ${{ secrets.DOCKER_USER }}/front:${{ github.sha }}
           docker push ${{ secrets.DOCKER_USER }}/front:latest
  

Para finalizar, incluiremos el fichero Dockerfile al proyecto frontend como hicimos en el artículo anterior.

./.Dockerfile

FROM node AS builder
WORKDIR /opt/front
COPY . .
RUN npm install
RUN npm run build:prod

FROM nginx
WORKDIR /var/www/front
COPY --from=builder /opt/front/dist/ .
COPY nginx.conf /etc/nginx/

Además, incluir el fichero nginx.conf.

worker_processes 2;
user www-data;

events {
  use epoll;
  worker_connections 128;
}

http {
  include mime.types;
  charset utf-8;
  server {
    listen 80;
    location / {
      root /var/www/front;
    }
  }

}

Vamos a recordar qué definíamos en este fichero nginx.conf:

  • worker_processes el número de procesos dedicados.
  • user define el usuario que los worker_processes usarán.
  • events conjunto de directivas para el manejo de conexiones.
    • epoll método eficiente usado en linux 2.6+.
    • worker_connections nº máximo de conexiones simultáneas que puede abrir un worker_processes.
  • http define las directivas del servidor HTTP.
    • server en contexto http define un servidor virtual.
    • listen el puerto que escuchará el servidor virtual.
    • location la ruta de los ficheros que serán servidos. Fijaos en que /var/www/front es el sitio donde copiamos los ficheros del build de nuestra aplicación.

Ejecutando un sistema de multi contenedores

Vamos a comprobar si nuestra Configuración de CI funciona como esperamos.

Primero nos aseguramos de que GitHub Actions ha ejecutado con éxito al menos un proceso de build.

Deberíamos ver en GitHub Actions que el proceso de build ha sido lanzado para los repositorios de Front End y Back End (inicia sesión en cada repositorio de GitHub).

También deberíamos ver las imágenes disponibles en el docker registry (iniciando sesión en Docker Hub):

Como hicimos en nuestra publicación anterior, podemos lanzar todo nuestro sistema utilizando Docker Compose. Sin embargo, en este caso para el Front End y Back End vamos a consumir las imágenes que hemos subido a Docker Hub Registry.

Los cambios que vamos a introducir al fichero docker-compose.yml son los siguientes:


  ./docker-compose.yml

  version: '3.7'
  services:
    front:
  -    build: ./container-chat-front-example
  +    image: <nombre_usuario_Docker_Hub>/front:<version>
    back:
  -    build: ./container-chat-back-example
  +    image: <nombre_usuario_Docker_Hub>/back:<version>
    lb:
      build: ./container-chat-lb-example
      depends_on:
        - front
        - back
      ports:
        - '80:80'

Así queda el fichero docker-compose.yml:

version: "3.7"
  services:
    front:
      image: <nombre_usuario_Docker_Hub>/front:<version>
    back:
      image: <nombre_usuario_Docker_Hub>/back:<version>
    lb:
      build: ./container-chat-lb-example
      depends_on:
        - front
        - back
      ports:
        - "80:80"

Podemos arrancarlo utilizando:

$ docker-compose up

Entonces se descargarán las imágenes de back y front desde Docker Hub Registry (la última disponible). Podemos comprobar cómo funciona abriendo un navegador web e introduciendo http://localhost/ en la barra del navegador (más información acerca de cómo funciona en nuestro primer artículo Hola Docker)

Recursos

En Resumen

Al introducir este proceso de CI/CD, hemos obtenido múltiples ventajas:

  • El proceso de build se ha automatizado, de esta manera evitamos los errores humanos.
  • Podemos desplegar distintas versiones de la build fácilmente (como de si una máquina de discos se tratará, ver diagrama).
  • Podemos dar marcha atrás fácilmente una publicación errónea.
  • Podemos aplicar testeo A/B o tener entornos Canary.
deploy.jpg

¿Y qué hay del despliegue? En el próximo artículo de esta serie aprenderemos a crear despliegues automatizados usando Kubernetes.

Permanece atento a nuestro blog :)


Puedes seguir leyendo más artículos de esta serie:

  1. Hola Docker
  2. Hola Docker CI/CD - Travis

¿Te gusta el mundo Devops?

En Lemoncode impartimos un Bootcamp Devops Online, si quieres saber más puedes pinchar aquí para más información.

Comment

Hola Docker CI / CD - Travis

Introducción

El proceso de ejecutar de forma manual builds, test y despliegues puede convertirse en una pesadilla y en un proceso propenso a errores:

  • Los errores humanos suceden a menudo, ocurren con facilidad.
  • Cuando los proyectos crecen, los procesos de build tienden a complicarse.
  • Lidiar con diferentes versiones entre entornos no es algo sencillo.
  • Cambiar de versiones no es un proceso fácil en muchos casos.

¿Y si...?:

  • Los procesos de build pudieran automatizarse, incluyendo indicadores que nos informen sobre si el proceso se ha completado (por ejemplo, ejecutando baterías de test).
  • Los procesos de build pudieran arrancar cuando ocurriera un merge para una rama concreta, o se realizará un push al repositorio remoto.
  • Cada proceso de build generase una imagen de Docker, y de este modo no nos tendríamos que preocupar por configuraciones en el servidor, o no tener instaladas las versiones exactas de software.
  • No ocupáramos demasiado espacio en disco ya que comenzaríamos desde una imagen base de Docker que tuviera el sistema operativo y software preinstalado.
  • Pudiéramos subirlo a un cloud hub registry, permitiendo ser consumido desde cualquier proveedor local o de cloud (Amazon, Azure, Google Cloud...)
  • Pudiéramos intercambiar fácilmente distintas versiones de build.

Todos estos son los beneficios que podemos obtener al configurar un servidor de CI/CD con Travis (CI proviene de Continuos Integration, CD significa Continuos Delivery) y mezclarlo con la tecnología de contenedores Docker.

Este es el segundo artículo de la serie Hola Docker, el primer artículo está disponible en este enlace.

TL; DR;

En este artículo usaremos Travis para activar automáticamente los siguientes procesos en cada merge to master o pull request:

  • Levantaremos una instancia linux + nodejs limpia.
  • Descargaremos el código fuente del repositorio.
  • Instalaremos las dependencias necesarias del proyecto.
  • Ejecutaremos la batería de test asociados.
  • Generaremos la imagen de docker, incluyendo la build de producción.
  • Etiquetaremos y publicaremos la imagen generada en el Docker Hub Registry.

Esta configuración la usaremos tanto para el proyecto de Front End como para el proyecto de Back End.

fullflow.jpg

Si quieres profundizar en los detalles, sigue leyendo :)

Agenda

Pasos que vamos a seguir:

  • Presentaremos el proyecto de ejemplo.
  • Trabajaremos manualmente con Docker Hub.
  • Enlazaremos nuestro proyecto de GitHub con Travis.
  • Estableceremos los pasos para un proceso de CI.
  • Comprobaremos el resultado de nuestras baterías de test.
  • Verificaremos si el contenedor de docker puede ser generado.
  • Subiremos una imagen a Docker Hub Registry.
  • Comprobaremos si nuestro proceso de Continuous Delivery ha tenido éxito, consumiendo nuestras imágenes desde un fichero docker compose.

Proyecto de ejemplo

Tomaremos como ejemplo una aplicación de chat. La aplicación está dividida en dos partes: el cliente (Front End) y el servidor (Back End) que serán encapsulados en contenedores Docker (a esta acción la denominaremos "dockerizar" de ahora en adelante), y desplegados mediante Docker Containers.

chat-app.gif

Ya tenemos listos un par de repositorios que juntos crean la aplicación de chat:

Siguiendo la aproximación actual de despliegue (revisar artículo previo de esta serie), una tercera parte será incluida también: un balanceador de carga. Su responsabilidad será direccionar el tráfico al front o back dependiendo de la url de la petición. El balanceador de carga también será "dockerizado" y desplegado usando Docker Container.

Docker Hub

En nuestro último artículo tomamos como punto de partida una imagen de Ubuntu + Nodejs; fue estupendo recuperarlas desde Docker Hub y tener el control de que versión estábamos descargando.

¿No sería maravilloso ser capaces de subir nuestras imágenes a Docker Hub Registry, incluyendo la versión? Eso es lo que Docker Hub nos ofrece: puedes crear tu propia cuenta y subir las imágenes de Docker.

Ventajas de usar Docker Hub:

  • Podemos mantener varias versiones de las imágenes de nuestros contenedores (genial para soportar distintos entornos, A/B testing, canary deployment, green blue deployment, rolling back, etc...).
  • Dado que Docker se crea a partir de una serie de capas, nuestra imagen custom no ocupará más espacio del necesario, es decir, un contenedor construido sobre un contenedor linux no desplegará el SO, sólo apuntará a la imagen previa.
  • Como la imagen del contenedor ya esta en la nube, desplegar en el proveedor de cloud es sencillo.

Docker Hub es fantástico para arrancar: podemos crear una cuenta gratis y subir nuestras imágenes Docker (la versión gratuita permite crear repositorios públicos ilimitados y uno sólo privado).

Si más tarde necesitamos usarlo para propósitos de negocio y restringir el acceso, podemos usar un registro de Docker privado, algunos proveedores son:

Travis

Aunque Docker nos ayuda a estandarizar la creación y configuración de un determinado entorno, generar nuevas versiones manualmente puede resultar tedioso y propenso a errores:

  • Tenemos que descargar el estado del código adecuado para el proceso de build.
  • Ejecutar los tests automatizados y comprobar que pasan en verde.
  • Tenemos que generar nuestra imagen de Docker.
  • Añadir el versionado adecuado (etiquetar la imagen en los términos de Docker).
  • Y por último, debemos subir la imagen manualmente al registro de Docker Hub.

Imaginemos hacer esto en cada proceso de merge to master; quedaríamos agotados con este infierno de despliegue... ¿Existe alguna manera de automatizar esto? ¡Travis al rescate!

Simplemente invirtiendo algo de tiempo en crear una configuración inicial, Travis hará de manera automática lo siguiente:

  • Notificar de cualquier PR o merge to master (se pueden establecer políticas de notificación).
  • Crear un entorno limpio (por ejemplo, en nuestro caso tomará una imagen de Ubuntu + Nodejs).
  • Descargar el estado de código adecuado desde el repositorio.
  • Ejecutar los tests.
  • Crear la imagen de Docker que contendrá la build de producción.
  • En caso de tener éxito, desplegar en el registro de Docker Hub (añadiendo la etiqueta de versión siguiendo la notación de Docker).

Una de las ventajas de Travis es que es bastante sencillo de configurar:

  • No necesitas instalar infraestructura (está basado en un entorno cloud).
  • La configuración se realiza mediante un fichero yml.
  • Ofrece una versión community edition donde podemos jugar con nuestros proyectos de prueba o usarlo para nuestros proyectos open source (sólo proyectos públicos).
  • Ofrece una versión enterprise para nuestros proyectos privados.

Configuración de Cuentas

Fork sobre los proyectos de ejemplo

Si quieres seguir este tutorial, puedes empezar por hacer un fork de los repositorios de Front End y Back End:

fork.png

Al hacer fork de estos repositorios, una copia se creará en tu cuenta de github y podrás enlazarlos a tu cuenta de Travis y configurar el proceso de CI.

Registro de Docker Hub

En nuestro artículo previo de esta serie, consumíamos una imagen de Docker Hub. Si queremos subir nuestras propias imágenes a Docker Hub (gratuito para imágenes públicas), necesitamos crear una cuenta. Puedes crear tu cuenta en el siguiente enlace.

Registro de Travis

Travis ofrece dos portales:

  • travisci.org: Permite añadir CI/CD a tus proyectos open source de manera gratuita.
  • travisci.com: Permite añadir CI/CD a tus proyectos privados a coste.

Como estamos usando Travis para aprendizaje, saltemos a travisci.org.

El siguiente paso que tenemos que dar, es enlazar nuestra cuenta de Github con Travis (iniciamos sesión con Github). Al hacer esto:

  • Ya tenemos nuestra cuenta creada en Travis.
  • Autorizamos a Travis para extraer información de nuestro repositorio (por ejemplo, descargar el repositorio para ejecutar el proceso de CI/CD).
  • Autorizamos a Travis para recibir notificaciones desde nuestro repositorio (por ejemplo, si una build se realizó para una rama determinada).
  • Github puede verificar el estado de la build desde Travis (por ejemplo, indicando si una pull request ha pasado el proceso de CI).

Comencemos

En este tutorial, aplicaremos la automatización a los repositorios (los cuales hemos hecho previamente un fork para que aparezcan en nuestra cuenta de Github) de la aplicación de chat usando una Travis Pipeline. Travis lanzará una tarea después de cada commit donde se realizarán lss siguientes pasos:

  • Se ejecutarán los test.
  • La aplicación será "dockerizada".
  • La imagen creada será subida al Docker Registry, en nuestro caso Docker Hub.

Subiendo una imagen de forma manual a Docker Hub

Antes de comenzar con la automatización, hagamos este proceso manualmente.

Una vez que tengamos una cuenta en Docker Hub, podemos interactuar con la plataforma desde la shell (abrir un bash terminal, o windows cmd).

Nos autentificamos contra Docker Hub.

$ docker login

Para poder subir nuestras imágenes, tienen que estar etiquetadas siguiendo el siguiente patrón:

<nombre_usuario_Docker_Hub>/<nombre_imagen>:<version>

La versión es opcional. Si no la especificamos, se aplicará latest.

$ docker tag front <nombre_usuario_Docker_Hub>/front

Y finalmente la subimos

$ docker push <nombre_usuario_Docker_Hub>/front

Desde ahora, la imagen estará disponible para el uso de cualquier usuario.

$ docker pull <nombre_usuario_Docker_Hub>/front

Para crear el resto de las imágenes que necesitamos, debemos seguir exactamente los mismos pasos.

Como hemos dicho anteriormente, todas las tareas que se repiten se pueden llegar a convertir en un proceso tedioso y donde podemos cometer errores. En los siguientes pasos aprenderemos como automatizarlo usando la CI/CD de Travis.

Enlazando el repositorio de Travis y las credenciales de Docker Hub

Primero tenemos que activar nuestros repositorios en Travis

travis-repos.png

Una vez activados, necesitamos entrar en cada una de las configuraciones del proyecto (Back y Front) y establecer como variables de entorno el usuario y la clave de Docker Hub

Front

frontend-travis-settings.png
travis-env.png

Back

backend-travis-settings.png
travis-env.png

Estas variables se usarán después para registrarnos en Docker Hub (nota: la primera vez que introducimos los datos, aparecen como texto plano. Una vez ya establecidas, aparecen como campos de clave).

Posiblemente, el repositorio sobre el que has realizado el fork no está registrado en tu cuenta de Travis. Puedes intentar sincronizar Travis con tu cuenta de Github manualmente, mediante el botón Sync

sync-travis.png

Finalmente podemos empezar la automatización de nuestras tareas.

Configuración de Travis

Para configurar Travis, necesitamos crear un fichero llamado .travis.yml en la raíz de nuestro proyecto. Es aquí, donde describimos las acciones que serán ejecutadas por Travis.

Como ya hemos enlazado tanto el repositorio de Back End como el de Front End en Travis, éste automáticamente comprobará los ficheros yml, y en cuanto estén disponibles, los analizará.

Back End

Los pasos para crear el fichero .travis.yml para la aplicación de back end son los siguientes:

  1. Escoger lenguaje y versión: en este caso vamos a eligir nodejs (tenemos otras opciones disponibles: ruby, java, python...).
  2. Docker Service: indicamos a Travis que vamos a hacer uso de Docker (Travis CI puede ejecutar imágenes Docker de build y subir estas imágenes al registro de contenedores).
  3. Instalar dependencias: como en un entorno en local, podemos ejecutar npm install.
  4. Testing: en nuestro caso, ejecutará los tests unitarios que han sido implementados en nuestra aplicación.
  5. Construir la imagen Docker: si los tests pasan, simplemente creamos la imagen del contenedor (buscará un fichero Dockerfile en la raíz del repositorio y seguirá las instrucciones del fichero para crear una build de producción, almacenándola en un contenedor de imagen Docker).
  6. Autenticación en Docker Hub: antes de subir la imagen generada a Docker Hub Registry necesitamos autenticarnos en Docker Hub.
  7. Etiquetar las imágenes de Docker: Tenemos que identificar la imagen del contenedor con una etiqueta.
  8. Subir las imágenes de Docker: subir la imagen generada al Docker Hub registry.

Un resumen del proceso de build:

backend-flow.gif

1. Escoger lenguaje y versión

Vamos a crear nuestro fichero .travis.yml en la raíz del repositorio de backend:

Empezaremos por indicar, que vamos a utilizar nodejs como nuestro language, después estableceremos que estamos usando la versión 12 de nodejs (más información acerca de los lenguajes en éste enlace).

  
  ./.travis.yml
  
  + language: node_js
  + node_js:
  +   - "12"
  

2. Servicio Docker

Primero pediremos ejecutar los comandos con sudo (administrador), sólo por si alguno de los comandos que estamos ejecutando requiere de permisos elevados.

Para utilizar Docker necesitamos solicitarlo como servicio en el fichero yml.

  
  ./.travis.yml
  
  language: node_js
  node_js:
    - "12"
  + sudo: required
  + services:
  +   - docker
  

3. Instalar dependencias

Comenzamos teniendo una máquina Ubuntu + nodejs levantada y en ejecución. Así que indicamos que queremos usar Docker. Travis ya ha descargado el código fuente desde nuestro repositorio de código, así que es el momento de ejecutar npm install antes de comenzar la ejecución de las pruebas unitarias.

  
  ./.travis.yml
  
  language: node_js
  node_js:
    - "12"
  sudo: required
  services:
    - docker
  + before_script:
  +   - npm install
  

4. Testing

Todo la fontanería está lista, por lo que podemos comenzar a definir nuestros scripts principales. Vamos a añadir dentro del fichero Travis yml una sección que se llame script, y dentro de la misma vamos a añadir un comando npm test; este simple comando, hará que se ejecuten nuestra batería de tests.

  
  ./.travis.yml
  
  language: node_js
  node_js:
    - "12"
  sudo: required
  services:
    - docker
  before_script:
    - npm install
  + script:
  +   - npm test
  

5. Construir la imagen de Docker

Si la ejecución de los tests ha tenido éxito, estamos listos para construir la imagen Docker. En el artículo anterior creamos un Dockerfile configurando los pasos del proceso de build.

docker-git.png

./Dockerfile

FROM node
WORKDIR /opt/back
COPY . .
RUN npm install
EXPOSE 3000
ENTRYPOINT ["npm", "start"]

Refresquemos la memoria acerca de la configuración reflejada en el Dockerfile:

  • FROM node Establecemos la imagen base de node desde Docker Hub image.
  • WORKDIR /opt/back Establecemos nuestro directorio de trabajo en /opt/back.
  • COPY . . Copiamos el contenido en el contenedor. La ruta donde será copiado el contenido será en el directorio de trabajo seleccionado, en este caso /opt/back.
  • RUN npm install Instalamos las dependencias.
  • EXPOSE 3000 Notificamos cuál es el puerto que expone nuestra aplicación.
  • ENTRYPOINT ["npm", "start"] Comando para arrancar nuestro contenedor.

Volvamos al fichero Travis yml: dentro de la sección script, justo después de npm test, añadimos el comando para construir la imagen del contenedor Docker.

  
  ./.travis.yml
  
  language: node_js
  node_js:
    - "12"
  sudo: required
  services:
    - docker
  before_script:
    - npm install
  script:
    - npm test
  + - docker build -t back.
  

Este comando buscará el fichero Dockerfile, que creamos en la raíz del repositorio de backend, y seguirá los pasos para realizar el proceso de build de la imagen.

¡Un momento! Acabo de caer en la cuenta de que aquí hay algo extraño: estamos utilizando distintos contenedores. Travis ejecuta los tests en una instancia de linux y el Dockerfile usa otra configuración de linux / node extraída del Docker Hub Registry. Esto huele mal, ¿no?

¡Totalmente de acuerdo! Ambas configuraciones, la del fichero Travis yml y la del fichero Dockerfile deberían arrancar desde la misma imagen. Necesitamos asegurarnos de que los tests se ejecutan con la misma configuración que tendremos en nuestro entorno de producción - esto es una limitación de la versión gratuita Travis.org (aquí puedes encontrar algunos workarounds). La versión de pago permite configurar la imagen del contenedor que quieras como punto de partida, más información en éste enlace.

6. Autenticarnos en Docker Hub

Justo después de que todos los scripts hayan sido ejecutados, y la imagen de docker haya sido generada, queremos subir la Docker image al Docker Hub Registry

Travis yml expone una sección denominada _aftersuccess. Esta sección sólo es ejecutada, si todos los pasos de la sección script, se han ejecutado con éxito. Es aquí donde realizaremos las acciones necesarias, para subir la imagen al registro de docker.

El primer paso es autenticarnos en Docker Hub (haremos uso de las variables de entorno que añadimos a la configuración de nuestro proyecto de Travis, ver la sección Enlazando el repositorio de Travis y las credenciales de Docker Hub de este artículo).

  
  ./.travis.yml
  
  language: node_js
  node_js:
    - "12"
  sudo: required
  services:
    - docker
  before_script:
    - npm install
  script:
    - npm test
    - docker build -t back.
  + after_success:
  +   - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
  

7. Etiquetar las imágenes de Docker

La imagen de Docker que acabamos de generar tiene el nombre de back. A la hora de subir la imagen a Docker Hub Registry, debemos proveer un nombre más elaborado y único:

  • Lo prefijamos con el nombre del usuario de Docker.
  • Añadimos un sufijo con un número único de build (en este caso \$TRAVIS_BUILD_NUMBER).

Por otro lado, indicaremos que la imagen que se ha generado es la última imagen de Docker disponible.

En un proyecto real esto puede variar dependiendo de nuestras necesidades.

  
  ./.travis.yml
  
  language: node_js
  node_js:
    - "12"
  sudo: required
  services:
    - docker
  before_script:
    - npm install
  script:
    - npm test
    - docker build -t back.
  after_success:
    - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD    
  + - docker tag back $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER
  + - docker tag back $DOCKER_USER/back:latest
  

8. Subir las imágenes de Docker

Ahora que tenemos identificadas nuestras imágenes de Docker con nombres únicos, necesitamos subirlas al Docker Registry. Utilizaremos el comando docker push para este fin.

  
  ./.travis.yml
  
  language: node_js
  node_js:
    - "12"
  sudo: required
  services:
    - docker
  before_script:
    - npm install
  script:
    - npm test
    - docker build -t back.
  after_success:
    - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
    - docker tag back $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER
  +  - docker push $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER
    - docker tag back $DOCKER_USER/back:latest
  +  - docker push $DOCKER_USER/back:latest
  

Fíjate que primero estamos subiendo la imagen $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER, y después la imagen $DOCKER_USER/back:latest

¿Significa esto que la imagen será subida dos veces?... La respuesta es no.

Docker es lo suficientemente listo para identificar que la imagen es la misma, así que le asignará "nombres" diferentes a la misma imagen en el Docker Repository

El resultado final

Nuestro fichero .travis.yml debería ser parecido a este:

language: node_js
node_js:
  - "12"
sudo: required
services:
  - docker
before_script:
  - npm install
script:
  - npm test
  - docker build -t back .
after_success:
  - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
  - docker tag back $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER
  - docker push $DOCKER_USER/back:$TRAVIS_BUILD_NUMBER
  - docker tag back $DOCKER_USER/back:latest
  - docker push $DOCKER_USER/back:latest

Ahora, si subimos toda esta configuración a Travis, automáticamente disparará un proceso de build (también puedes lanzar un proceso de build desde la web UI de Travis).

travis-build.png

Una vez finalizado, puedes comprobar que la imagen de Docker ha sido generada con éxito (comprobar la consola web de Travis):

travis-build-docker.png

Y también podemos comprobar si la imagen está disponible en nuestra cuenta de Docker Hub Registry:

docker-hub-published.png

Front End

Los pasos para crear le fichero .travis.yml son muy parecidos a los anteriores (backend), la única diferencia es que no hemos implementado pruebas unitarias (nos saltaremos ese paso):

  1. Servicio de Docker.
  2. Construir la Imagen de Docker.
  3. Autenticarse en Docker Hub.
  4. Etiquetar las imágenes de Docker.
  5. Subir las imágenes de Docker.

1. Servicio de Docker

La aplicación de frontend será "dockerizada". Por lo que, comenzaremos indicado que el servicio es necesario, y sudo es requerido; exactamente igual que en la configuración de backend

  
  ./.travis.yml
  
  + sudo: required
  + services:
  +   - docker
  

2. Construir la imagen de Docker

Generamos la imagen de Docker dentro de la sección script.

  
  ./.travis.yml
  
  sudo: required
  services:
    - docker
  + script:
  +  - docker build -t front .
  

3. Autenticarnos en Docker Hub

Si la imagen de Docker se construyó con éxito, el siguiente paso es autenticarnos en Docker Hub.

  
  ./.travis.yml
  
  sudo: required
  services:
    - docker
  script:
    - docker build -t front .
  + after_success:
  +   - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
  

4. Etiquetar las imágenes de Docker

Como hicimos con la aplicación de backend, vamos a etiquetar la versión actual utilizando Travis Build number y definirla como latest.

  
  ./.travis.yml
  
  sudo: required
  services:
    - docker
  script:
    - docker build -t front .
  after_success:
    - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
  +  - docker tag front $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER
  +  - docker tag front $DOCKER_USER/front:latest
  

8. Subir las imágenes de Docker

Ahora sólo necesitamos subir las imágenes de Docker.

  
  ./.travis.yml
  
  sudo: required
  services:
    - docker
  script:
    - docker build -t front .
  after_success:
    - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
    - docker tag front $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER
  +  - docker push $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER
    - docker tag front $DOCKER_USER/front:latest
  +  - docker push $DOCKER_USER/front:latest  
  

Y el fichero final .travis.yml debería ser como éste:

sudo: required
services:
  - docker
script:
  - docker build -t front .
after_success:
  - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
  - docker tag front $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER
  - docker push $DOCKER_USER/front:$TRAVIS_BUILD_NUMBER
  - docker tag front $DOCKER_USER/front:latest
  - docker push $DOCKER_USER/front:latest

Ejecutando un sistema de multi contenedores

Vamos a comprobar si nuestra Configuración de CI funciona como esperamos.

Primero nos aseguramos de que Travis ha ejecutado con éxito al menos un proceso de build (en caso contrario, podemos disparar el proceso de build de manera manual o simplemente realizar una subida de código trivial, a los repositorios de backend y frontend)

Deberíamos ver en Travis que el proceso de build ha sido lanzado para los repositorios de Front End y Back End (autenticándote en travis.org):

También deberíamos ver las imágenes disponibles en el docker registry (autenticándote en Docker Hub):

Como hicimos en nuestro artículo previo, podemos arrancar todo nuestro sistema usando Docker Compose. Sin embargo, en este caso para el Front End y Back End vamos a consumir las imágenes que hemos subido a Docker Hub Registry.

Los cambios que vamos a introducir al fichero docker-compose.yml son los siguientes:

  
  version: '3.7'
  services:
    front:
  -    build: ./container-chat-front-example
  +    image: <nombre_usuario_Docker_Hub>/front:<version> 
    back:
  -    build: ./container-chat-back-example
  +    image: <nombre_usuario_Docker_Hub>/back:<version> 
    lb:
      build: ./container-chat-lb-example
      depends_on:
        - front
        - back
      ports:
        - '80:80'
  

Así queda el fichero docker-compose.yml:

version: "3.7"
services:
  front:
    image: <nombre_usuario_Docker_Hub>/front:<version>
  back:
    image: <nombre_usuario_Docker_Hub>/back:<version>
  lb:
    build: ./container-chat-lb-example
    depends_on:
      - front
      - back
    ports:
      - "80:80"

Podemos arrancarlo utilizando:

$ docker-compose up

Entonces se descargarán las imágenes de back y front desde Docker Hub Registry (las últimas disponibles). Podemos comprobar cómo funciona abriendo un navegador web e introduciendo http://localhost/ en la barra del navegador (más información acerca de cómo funciona en nuestro artículo previo Hola Docker)

Recursos

En Resumen

Al introducir este proceso de CI/CD, hemos obtenido múltiples ventajas:

  • El proceso de build se ha automatizado, de esta manera evitamos los errores manuales.
  • Podemos desplegar distintas versiones de la build fácilmente (como de si una máquina de discos se tratará, ver diagrama).
  • Podemos dar marcha atrás fácilmente una publicación errónea.
  • Podemos aplicar testeo A/B o tener ambientes Canary.
deploy.jpg

¿Y qué hay del despliegue? En el próximo artículo de esta serie aprenderemos a crear despliegues automatizados usando Kubernetes.

Permanece atento a nuestro blog :)


Puedes seguir leyendo más artículos de esta serie:

  1. [Hola Docker][0]

¿Te gusta el mundo Devops?

En Lemoncode impartimos un Bootcamp Devops Online, si quieres saber más puedes pinchar aquí para más información.

Comment

Beca Yes We Tech - Octava edición Máster Front End Lemoncode

Después de varias ediciones presentando la beca Yes We Tech, estamos muy contentos con los resultados, de las tres alumnas becadas, todas se han incorporado al mundo laboral y con muy buenos resultados, con estos resultados estamos muy contentos de publicar convocatoria de beca para está edición del Máster.

Comment

Hola Docker

Docker ha dejado de ser una tecnología desconocida para convertirse (casi) en un estándar: La mayoría de empresas y desarrolladores lo utilizan a diario por su simplicidad para empaquetar y ejecutar aplicaciones (especialmente en sistemas distribuidos). Docker nos evita tener que instalar miles de dependencias, lo que hace que nuestros sistemas de producción sean mucho más fáciles actualizar y mantener.

Si (casi) todo el mundo está utilizando Docker para desplegar sus aplicaciones en contenedores... es hora de ponernos las pilas ;)

4 Comments

Bootcamp Javascript ¿Es para mí?

Hace unos meses decidimos montar un Bootcamp Online en JavaScript, desde entonces muchos nos habéis escrito con dudas, acerca de que tipo de formación elegir (Máster Front o Bootcamp), que contenido se imparte, y a que perfiles está orientado, si ese es tu caso, esperamos que este post te sea de ayuda :-).

Comment

Configurando las pipelines de Bitbucket en tu proyecto front end

Las pipelines de Bitbucket permiten configurar fácilmente un entorno de integración continua en tu proyecto. Teóricamente, en teoría solo necesitas habilitarlas y configurar un fichero yml en tu raíz del proyecto.

Sin embargo …

  • Bitbucket factura por minutos de compilación, y por defecto tu integración se dispara en cada push, en cada rama. Debemos alcanzar un equilibrio de costes:

    • Lanzar la compilación en cada push hacia la rama master.

    • Lanzar la compilación en cada pull request.

  • Poner en marcha un fichero YAML de configuración es un proceso propenso a errores, especialmente si no tienes tiempo para leer detenidamente la documentación. Por tanto, necesitamos una manera fácil de comprobar que el YAML generado es válido.

Comment

Componentes de Clase en React: Inconvenientes

Introducción

Hasta hace unos meses, los componentes de clase de React (también llamados stateful components) estaban de moda. Pero eso se acabó con la llegada de los Hooks. ¿Por qué han dejado de ser una buena idea? En este post vamos a enumerar los principales inconvenientes a la hora de trabajar con componentes de clase en React.

La pesadilla del “this

En componentes de clase, el valor que adopta this dentro de una función dependerá de cómo sea invocada dicha función. Cuando nuestra función es utilizada como manejador de eventos (event handler), el valor al que apunta this será undefined. ¿Por qué? Las declaraciones y expresiones de clase (como por ejemplo nuestra función) se ejecutan en modo estricto, bajo el cual se aplica el binding por defecto a undefined y no a la instancia del componente como cabría esperar.

Más información al respecto aqui: https://medium.freecodecamp.org/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb

Por tanto, el this en un event handler disparado fuera de nuestro componente de clase, perderá la referencia a la instancia a la que pertenece.

Si ejecutamos el siguiente ejemplo y hacemos click en el botón Change Text, obtendremos el error:

Cannot read property ‘setState’ of undefined
export class MyHelloComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { myText: "hello" };
  }

  onChangeText() {        
    this.setState({ myText: "world" });
  }

  render() {
    return (
      <>
        <h3>{this.state.myText}</h3>
        <button onClick={this.onChangeText}>Change text</button>
      </>
    );
  }
}

La solución pasa por “atar” manualmente el this a la instancia de clase en cada una de las funciones declaradas como métodos de clase. Para ello contamos con dos mecanismos, o bien hacer un binding explícito en el constructor o bien utilizar funciones flecha experimentales (fat arrow) como método de clase (ver linea afectada marcada con el caracter “+”).

export class MyHelloComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { myText: "hello" };
+    this.onChangeText = this.onChangeText.bind(this);
  }

  onChangeText() {        
    this.setState({ myText: "world" });
  }

  render() {
    return (
      <>
        <h3>{this.state.myText}</h3>
        <button onClick={this.onChangeText}>Change text</button>
      </>
    );
  }
}

Demo: https://codesandbox.io/s/71opm61rzq

Esta característica de las clases puede llegar a convertirse en una fuente de problemas. ¿Cuántas veces has olvidado hacer adecuadamente el bind a tus event handlers, obteniendo finalmente un error en tiempo de ejecución?

Estado monolítico y funcionalidad difícilmente extraíble

En un componente de clase, tenemos un único lugar donde definir todo el estado al completo, y al mismo tiempo, una única forma de actualizarlo. ¿Inconveniente? Es complicado separar intereses y extraer la funcionalidad para su reuso. Recordemos que la separación de intereses (separation of concerns) y la reutilización de código (code reuse) son dos de los más importantes principios de programación que aparecerán en cualquier manual de buenas prácticas.

En el siguiente ejemplo, tenemos dos conceptos diferentes encapsulados en el mismo estado de clase:

export class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { color: "teal", name: "John", lastname: "Doe" };
  }

  getFullname() {
    return `${this.state.name} ${this.state.lastname}`;
  }

  render() {
      return (
          <div style={{ background: this.state.color }}>
              <h3>{this.getFullname()}</h3>
          </div>
      )
  }
}

Demo: https://codesandbox.io/s/yp9v4yyr8x

Veamos como podríamos implementar el mismo escenario usando hooks. En este caso podremos separar el estado para cada concepto:

import React from "react";

export const MyComponent = () => {
  const [color, setColor] = React.useState("teal");
  const [clientInfo, setClientInfo] = React.useState({name: 'John', lastname: 'Doe'});

  const getFullname = () => {
      return `${clientInfo.name} ${clientInfo.lastname}`;
  }

  return (
    <div style={{ background: color }}>
      <h3>{getFullname()}</h3>
    </div>
  );
};

Demo: https://codesandbox.io/s/ppom543mj7

Podemos dar un paso más y encapsular la funcionalidad relativa al usuario en un custom hook:

const useClientInfo = (name, lastname) =>  {
  const [clientInfo, setClientInfo] = React.useState({
    name,
    lastname,
  });

  const getFullname = () => {
    return `${clientInfo.name} ${clientInfo.lastname}`;
  };

  return {clientInfo, setClientInfo, getFullname}
}

export const MyComponent = () => {
  const [color, setColor] = React.useState("teal");
  const {getFullname} = useClientInfo('John', 'Doe');

  return (
    <div style={{ background: color }}>
      <h3>{getFullname()}</h3>
    </div>
  );
};

Demo: https://codesandbox.io/s/ly7wlq9mo9

Gestión de intereses relacionados en manejadores separados

Cuando usamos componentes de clase en React, disponemos de distintos eventos durante su ciclo de vida en los cuales ejecutar código: componentDidMount, componentDidUpdate, componentWillUnmount, etc. Podría darse el caso de que cierta funcionalidad relacionada tenga que ser dividida entre los distintos handlers del ciclo de vida del componente. Por ejemplo:

Definamos un componente padre:

export class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { visible: false };
  }

  render() {
    return (
      <>
        {this.state.visible && <MyChildComponent />}
        <button onClick={() => this.setState({ visible: !this.state.visible })}>
          Toggle Child component visibility
        </button>
      </>
    );
  }
}

Ahora, un componente hijo que simplemente mostrará la información de usuario:

export class MyChildComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: "John", lastname: "Doe" };
  }

  componentDidMount() {
    console.log("Hey Im mounting");
    console.log(`${this.state.name} ${this.state.lastname}`);
  }

  componentDidUpdate() {
    console.log("Just updating...");
    console.log(`${this.state.name} ${this.state.lastname}`);
  }

  componentWillUnmount() {
    console.log("bye bye, unmounting...");
  }

  render() {
    return (
      <div>
        <h3>
          {this.state.name} {this.state.lastname}
        </h3>
        <input
          value={this.state.name}
          onChange={e => this.setState({ name: e.target.value })}
        />
        <input
          value={this.state.lastname}
          onChange={e => this.setState({ lastname: e.target.value })}
        />
      </div>
    );
  }
}

Demo: https://codesandbox.io/s/oqyo3159jq

Mediante el empleo de hooks podemos agrupar toda esta funcionalidad en una única función:

const MyChildComponent = () => {
  const [userInfo, setUserInfo] = React.useState({name: 'John', lastname: 'Doe'})

  React.useEffect(() => {
    console.log('called when the component is mounted and right after it gets updated');

    return () => console.log('Clean up from the previous render before running effect next time ... ');
  })

  return (
    <div>
      <h3>
        {userInfo.name} {userInfo.lastname}
      </h3>
      <input
        value={userInfo.name}
        onChange={e => setUserInfo({ ...userInfo, name: e.target.value })}
      />
      <input
        value={userInfo.lastname}
        onChange={e => setUserInfo({ ...userInfo, lastname: e.target.value })}
      />
    </div>
  );
}

En este ejemplo, el código declarado dentro del useEffect es ejecutado una vez que el componente se ha montado pero también justo después de cada render. Por otro lado, la función de limpieza (devuelta en el useEffect) se ejecuta después de que el componente se haya montado y después de cada ejecución del efecto.

Demo: https://codesandbox.io/s/5zllr3k09p

Bien, pero … ¿y si quiero ejecutar un trozo de código solo cuando el componente se haya montado y hacer la limpieza cuando el componente vaya a desmontarse? Para ello, podemos jugar con el segundo parámetro del useEffect (en el ejemplo marcado con “-” las lineas que se eliminarían, con “+” las líneas que se añaden).

const MyChildComponent = () => {
  const [userInfo, setUserInfo] = React.useState({name: 'John', lastname: 'Doe'})

  React.useEffect(() => {
-    console.log('called just when the component is mounted and when after it gets updated');
+    console.log('called just when the component is mounted');

-    return () => console.log('Clean up from the previous render before running effect next time ... ');
+    return () => console.log('Clean up executed just when the component gets unmounted ... ');
-  })
+  }, [])

  return (
    <div>
      <h3>
        {userInfo.name} {userInfo.lastname}
      </h3>
      <input
        value={userInfo.name}
        onChange={e => setUserInfo({ ...userInfo, name: e.target.value })}
      />
      <input
        value={userInfo.lastname}
        onChange={e => setUserInfo({ ...userInfo, lastname: e.target.value })}
      />
    </div>
  );
}

Demo: https://codesandbox.io/s/q91qjql8r4

Ruido al utilizar High Order Components

Un HOC o High Order Component es una excelente forma de añadir nuevas capacidades a tu componente mediante composición, sin embargo:

  • Necesitas refactorizar su interfaz para poder aplicarlo.

  • Encadenar diversos HOC simultáneamente puede ser una pesadilla.

Veamos un ejemplo mínimo. El siguiente componente se emplea para saludar al usuario que está actualmente logado:

import React from "react";

export const MyComponent = (props) => {
  return (
    <>
      <h3>Hello: </h3>
    </>
  )
}

Para inyectar el usuario que está actualmente logado podemos crear un HOC:

const withUserInfo = (ComponentToWrap) => (props) =>
  <>
    <ComponentToWrap {...props} user="John" />
  </>
Let's make usage of this HoC in MyComponent

- export const MyComponent = (props) => {
+ const 

Podemos hacer uso de este HOC en MyComponent del siguiente modo:

const MyComponentInner = (props) => {  
  return (
    <>
      <h3>Hello: {props.user}</h3>
    </>
  )
}

export const MyComponent = withUserInfo()

Demo: https://codesandbox.io/s/l7qznjjyw9

Esta forma de añadir funcionalidad es bastante potente, pero, ¿no sería más sencillo y expresivo un mecanismo con el que podamos decir: “quiero usar esta funcionalidad”? Veamos como implementar este comportamiento utilizando hooks:

import React from "react";

const useUserInfo = () => {
  const [userInfo, setUserInfo] = React.useState('John');

  return {userInfo, setUserInfo}
}

export const MyComponent = (props) => {
  const {userInfo} = useUserInfo();

  return (
    <>
      <h3>Hello: {userInfo}</h3>
    </>
  )
}

Demo: https://codesandbox.io/s/pwj6z446xq

Ventajas:

  • No necesitamos reescribir el interfaz de nuestro componente: borrar el export, crear un componente para envolverlo, etc.

  • Sin complicaciones al tener anidando HOCs.

  • El contrato es claro, no tenemos que adivinar que propiedades está inyectando el HOC a nuestro componente.

Migrando componentes de su forma funcional a clase, y también en el sentido contrario

Cuando iniciamos la implementación de un componente tratamos de mantenerlo sencillo, por lo que es típico comenzar con un componente funcional. Un ejemplo simple podría ser el siguiente: mostrar un mensaje de bienvenida a un usuario (nos lo informa el padre a través de la propiedad userName).

import React from "react";

export const MyComponent = () => {
  return (
    <>
      <MyChildComponent userName="John"/>
    </>
  )
}

export const MyChildComponent = (props) => {
  return (
    <>
      <h3>Hello: {props.userName}</h3>      
    </>
  )
}

Demo: https://codesandbox.io/s/985wyr1olw

Supongamos que, en lugar de recibirlo desde el padre, queremos almacenarlo en el estado local de nuestro componente. Una aproximación clásica sería migrar nuestro componente a su forma stateful, es decir, refactorizarlo por completo, prestando atención de no equivocarnos con el this.state, añadiendo su constructor, implementando el método render, etc. Algo tal que así:

export const MyComponent = () => {
  return (
    <>
      <MyChildComponent/>
    </>
  )
}

export class MyChildComponent extends React.Component {  
  constructor(props) {
    super(props);
    this.state = {userName: 'John'}
  } 

  render() {
      return (
        <>  
         <h3>Hello: {this.state.userName}</h3>      
        </>
      )
  }
}

Demo: https://codesandbox.io/s/40l5k4q1o9

¿Cómo podría haber sido este refactor utilizando hooks? Tan sencillo como esto:

export const MyComponent = () => {
  return (
    <>
     <MyChildComponent/>
    </>
  )
}

export const MyChildComponent = (props) => {
  const [userName]  = React.useState('John');
  return (
    <> 
     <h3>Hello: {userName}</h3>      
    </>
  )
}

Demo: https://codesandbox.io/s/jlkoxm6plw

Combinando componentes funcionales y de clase

Por último, pero no menos importante, en un proyecto de dimensiones considerables, una aproximación mixta donde componentes funcionales y de clase tengan que convivir juntos podría traer diversos inconvenientes de mantenibilidad:

  • Inconsistencia en nuestra base de código.

  • Dificultad añadida para que nuevos desarrolladores se inicien en el proyecto, teniendo que aprender ambas formas y decidir cuando es más conveniente una u otra, o cuando deben refactorizar un componente existente en uno u otro sentido.

  • La refactorización de componentes, ya sea en un sentido u otro, es una tarea propensa a errores, por ejemplo: se nos olvida eliminar el this, nos equivocamos en la sintaxis de clase vs función, errores con la función de render, etc.

Resumiendo

Antes de tirarnos a la piscina con la nueva característica de moda, es importante aprender por qué la necesitamos y que problemas viene a resolver. Esperamos que este artículo pueda ayudaros a entender mejor por qué los hooks han generado tanta expectación en la comunidad y se han convertido en un estándar de facto. En los siguientes artículos de esta serie comenzaremos a indagar en los detalles que hacen a los hooks tan poderosos, mostrando su cara amable y también los casos frontera, basándonos en casos de uso en proyectos reales. ¡Estad atentos!

Esperamos que hayáis disfrutado el artículo. ¡Muchas gracias!

Comment

Beca Yes We Tech - Septima edición Máster Front End Lemoncode

En 2018 presentamos nuestra primera beca Yes We Tech, seleccionamos a dos candidatas para que cursaran el máster , ¿Cual fue el resultado? Tener a dos alumnas muy motivadas, con muchas ganas de trabajar y que como fruto del esfuerzo consiguieron un empleo al finalizar el máster, con estos resultados… ¿ Por qué no repetir? :)

En colaboración con YesWeTech ofrecemos nuestra segunda beca de estudios para aportar nuestro granito de arena a conseguir que haya más diversidad e igualdad de oportunidades en esta profesión. ¿En que consiste? En cubrir el 100% del coste del Máster Front End Online Lemoncode a una alumna que pertenezca a una minoría con baja representación en el sector profesional. Ofrecemos una plaza, y para ello hemos resuelto unas bases para poder inscribiros y seleccionar a la aspirante.

yeswetech.jpeg


La convocatoría se abre el 15 de Abril y se cierra el 15 de Mayo, el 1 de Junio anunciaremos la persona seleccionada. 

A continuación te detallamos las bases de la misma:

Requisitos

  • Pertenecer a minorías con baja representación en el sector profesional, es decir, mujeres y en especial quienes sean inmigrantes, pertenezcan al colectivo LGTBQIA+, estudiantes y mujeres con con diversidad funcional.

  • Ingresos inferiores a 27.000€ brutos año o contar con cargas familiaries.

  • Tener conocimientos previos de programación (sea experiencia laboral, como estudiante, o autodidacta demostrable).

Criterios de evaluación

  • Horas de estudio semana que la candidata pueda dedicar a la semana.

  • Curriculum Vitae.

  • Entrevista personal y opción de realizar prueba técnica a las finalistas.

Información mínima a enviar

  • Documento personal identificativo (DNI, NIE, Pasaporte)

  • Carta explicando los motivos por los que la alumna está interesada en realizar el máster.

  • Número de horas de estudio semanal que le puede dedicar al máster

  • Curriculum Vitae.

  • Información salarial (no es necesario el envío de documentación acreditativa, pero se podría requerir si la candidata llega a finalista).

Fechas

  • Aceptación candidatas del 15 de Abril al 15 de Mayo de 2019.

  • Fecha resolución beca (1 plaza) 1 de Junio de 2019.

  • Fecha arranque Máster 22 de Septiembre de 2019

Comite evaluador

  • Miembros de la comunidad YesWeTech.

  • Miembros de Lemoncode Formacion S.L.

Para aplicar envíanos un correo con la información que te pedimos a nuestra cuenta de EMail: formacion@lemoncode.net

Te estamos muy agradecidos si nos ayudas a difundir esta beca.

Comment