Saltar al contenido

Coding With No Try Catch Entradas

Contenerización en .NET y Buenas Prácticas de Seguridad

En este post, vamos a centrarnos en como contenerizar una aplicación en .NET, primero revisaremos que novedades tenemos con NET 8 y luego veremos un enfoque más clásico usando Docker. Adicionalmente, veremos que buenas prácticas es recomendable seguir y que consideraciones respecto a la seguridad debemos de tener en cuenta cuando lo realizamos.

CONTENERIZACIÓN EN NET 8

A partir de NET 7 y consecuentemente en NET 8, tenemos la posibilidad de contenerizar nuestras aplicaciones directamente desde nuestra solución (.csproj), sin necesidad de usar un “dockerfile” como seguramente hemos usado ampliamente si queríamos conseguir este resultado con anterioridad. Esto gracias al uso de la librería “Microsoft.NET.Build.Containers”, esta mejora nos permitirá estandarizar como generamos los containers para nuestras aplicaciones.

Veamos como conseguimos realizar esto. Una ves que tengamos nuestra aplicación lista para contenerizar, usamos el siguiente comando.

dotnet publish /p:PublishProfile=DefaultContainer

El resultado de este comando termina siendo algo parecido a este mensaje:

Resultado dotnet publish

Notemos que, por default, esta contenerización esta utilizando la siguiente imagen ‘mcr.microsoft.com/dotnet/aspnet:8.0’. Como veremos más adelante, es importante tener control sobre que imagen base vamos a usar, veamos como podemos cambiar este valor.

En el ‘.csproj’, añadimos:

Cambiando csproj

Y el resultado obtenido sería el siguiente:

Resultado cambio de version a usar

También es posible usar imágenes base personalizadas, veamos como hacer esto, usando los siguientes comandos.

Indicando la imagen base

Siendo el resultado, el siguiente:

Resultado de indicar la imagen base

Adicionalmente, podemos realizar configuraciones adicionales a nuestra contenerización, mencionaremos algunas relevantes:

  • Apuntar a otras arquitecturas de SO:
Otras arquitecturas
  • Agregar labels a nuestro container:
Labels
  • Usar variables de entorno:
Variables de entorno
  • Indicar el puerto que vamos a usar y el protocolo:
Puerto y protocolo

Se tienen más opciones de configuración, para lo cual, es recomendable referirnos a la documentación oficial.

CONTENERIZACIÓN USANDO UN DOCKER

Partamos usando un proyecto común usando por el mismo repositorio de Docker como ejemplo de contenerización:

git clone https://github.com/dockersamples/student-record-management

Al clonar este repositorio, revisemos rápidamente el Docker compose file.

Archivo docker compose

Como podemos ver, para levantar la aplicación, necesitamos a su vez levantar tres componentes:

  • Una base de datos (postgres).
  • Un administrador de esta base de datos (adminer).
  • Nuestra aplicación (web ´student-record-management’). Se hace referencia al archivo ‘Dockerfile’ que tenemos en nuestra carpeta root de nuestra aplicación clonada.
Archivos del proyecto

Si revisamos que contiene nuestro archivo ‘Dockerfile’.

Ejemplo dockerfile

Analicemos algunos puntos de nuestro archivo linea a linea:

  1. Le indicamos a Docker que  imagen base usar
  2. Se define la carpeta del directorio de trabajo.
  3. Copiamos el contenido de nuestro repositorio a la carpeta del directorio de trabajo que hemos definido.
  4. Ejecutamos el comando “dotnet build -o /app” para compilar nuestra solución.
  5. Ejecutamos el comando “dotnet publish -o /publish” para generar una publicación de nuestra solución y establecermos la ruta de salida de en la carpeta “publish”.
  6. Establecemos un nuevo directorio de trabajo, a la carpeta “publish”.
  7. Seteamos una variable de entorno de nuestra imagen Docker, llamada  “ASPNETCORE_URLS” a cualquier dirección IP, con el puerto 80.
  8. Exponemos en nuestra imagen Docker el puerto 80 para que pueda ser consumido desde fuera del container.
  9. Finalmente, levantamos nuestra aplicación.

Como pueden ver, con estos simples pasos ya tenemos una aplicación completa corriendo en un ambiente aislado, que nos asegura independencia y control sobre que ocurre en nuestra imagen.

Sin embargo, que funcione, no nos asegura que estamos cumpliendo con buenas prácticas, el primer punto y que debería resultar el más evidente que podríamos mejorar es la imagen sobre la cual corremos toda nuestra aplicación, dado que estamos usando el SDK completo de .NET (versión 6.0.0, que pasa alrededor de 760 MB). Veamos cuales son los puntos (buenas prácticas) que debemos considerar.

ESCOGER LA IMAGEN DE DOCKER CORRECTA

Las más evidente – espero – a simple vista, podemos guiarnos del repositorio oficial de Microsoft, teniendo en cuenta que se tienen imágenes para desarrollo y para producción, entre las cuales para producción hay imágenes con el runtime de .NET y optimizaciones que nos pueden servir, adicionalmente a esto, se pueden elegir imágenes basadas en SO Linux o Windows también.

Considerando esto, en la última .Net conf 2023, tuvimos una actualización con las imágenes base que tenemos disponibles para desplegar nuestros containers.

Tamaño de imágenes

Mejora en las imágenes

OPTIMIZAR NUESTRO DOCKERFILE PARA COMPILAR NUESTRO PROYECTO

En el mundo de .NET, cuando se buscan restaurar las dependencias que pueden tener nuestro proyecto, requerimos de un archivo tipo “.csproj”, “.sln” o un “nuget.config”. Podríamos aprovechar estas características, para hace run restore de nuestra imagen al inicio y permitir que Docker realice un cache de esta información y de este optimizar el tiempo construcción de nuestra imagen. Veamos en una imagen, como conseguimos esto.

Optimización del dockerfile

Al copiar primero a nuestro directorio de trabajo todos los archivos “.csproj” que tenga nuestra solución y hacerle un “restore”, estamos consiguiendo, optimizando como Docker construye nuestra imagen.

COMPILAR NUESTRA IMAGEN CON MULTIPLES ETAPAS

En el ejemplo que desarrollamos, estábamos usando el SDK completo para compilar nuestra aplicación (y este generaría que nuestra imagen Docker, pesará como mínimo 760 MB). 

El motivo por el que se puede estar haciendo esto, es porque para generar el compilado de nuestra solución, se pueden requerir características no son propias del runtime, estas pueden usarse en la ejecución de los test unitarios, entre otros posibles usos.

Sin embargo, una ves que hemos generado el build usando estas características del SDK, es posible generar nuestro publish de otra imagen de Docker, una más acorde a la optimización que buscamos de nuestras imágenes, vemos un ejemplo de esto.

Dockerfile con Multiple Stage

Esta segunda imagen, está optimizada para producción y es sobre está imagen que generamos nuestra imagen de Docker final, copiando el compilado que hemos obtenido de la anterior. No analizaremos el paso a paso, porque lo hicimos líneas arriba, pero el resultado final de usar ambas imágenes para generar nuestro archivo Docker, es que la imagen final de nuestra aplicación tendrá aproximadamente 240 MB.

PREFERIR USAR TAGS ESPECIFICOS DE IMÁGENES DOCKER, SOBRE LOS “LATEST”

Si hemos usado Docker, sabemos que en las imágenes se generan tenemos la posibilidad de indicar un tag en específico. Y a su vez, cuando usamos imágenes de terceros, podemos indicar un tag en específico a usar. Este último punto es importante siempre tenerlo en cuenta, dado que, si no lo hacemos, por defecto se usa el tag “latest”, lo cual significa que cuando se construya nuestra imagen Docker, buscará la última versión reciente, y esto en muchos casos implica cambios que pueden romper nuestro build o tener comportamientos inesperados.

EJECUTAR NUESTRO PROCESO EN DOCKER, NO COMO UN USUARIO ROOT

Recordemos que al final de cuentas, una imagen Docker corre bajo un sistema operativo, por lo que el manejo de usuarios y los permisos que estos tienen es un punto fundamental que considerar cuando queremos evitar alguna posible brecha de seguridad. Teniendo esto en mente, recordemos que en Docker tenemos la posibilidad de usar el comando “USER” para poder modificar el usuario por defecto que usa nuestro Docker file (root).

Se genera un usuario especifico para nuestro docker

Como se puede ver en el dockerfile, creamos el usuario en el SO con el comando “RUN” y luego hacer el cambio para ejecutar los procesos con un usuario distinto usando el comando “USER”.

USAR “.dockerignore” PARA MANTENER LIMPIA NUESTRA IMAGEN

Muy parecido a su uso en GIT, con esto, buscamos obviar que se consideren en nuestro build información que no nos sirve, por ejemplo, los archivos de compilación local que tenemos en nuestro proyecto, archivos con un READMNE, etc. Veamos un ejemplo que de podríamos encontrar en este archivo.

Ejemplo dockerfile

USO DE HEALTH CHECKS

El comando “HEALTHCHECKS” nos permite indicarle a Docker como está corriendo nuestro servicio, ya sea por si por algún error de codificación se cayó y no puede procesar solicitudes. De este modo, podemos gestionar mejor nuestros contenedores. Adicionalmente, este punto es relevante cuando usamos orquestadores como Kubernetes, dado que le damos visibilidad de que es lo que está pasando con nuestro container. Para implementar los health checks, tenemos que considerar si nuestra imagen va a correr sobre un SO del tipo Linux o Windows. Linux

Healtcheck en linux

Windows

Healtcheck en windows

Con esto llegamos al final del artículo sobre contenerización. Quedan varios puntos en el tintero, espero que les haya servidor este overview sobre algunas consideraciones y sigamos mejorando en nuestra contenerización de aplicaciones en .NET.

Deja un comentario

Tool: MySQL Profiler con Python

Hace ya algún tiempo que no hacia un post, tengo varios en el tintero y busco hacerme un tiempo en adelante para seguir actualizando el blog.

Dicho esto, en este post voy a presentar una herramienta sencilla que he realizado para suplir la necesidad de un «SQL Profiler» para MySQL. Como saben, en la suite de SQL Server, tenemos el IDE (management studio) y también un Profiler, es último nos sirve para poder monitorear que está ocurriendo en nuestra BD. Una herramienta extremadamente util, para distintos perfiles técnicos (DBAs, desarrolladores, etc).

SQL Profiler
SQL Profiler

Me encontraba revisando una aplicación que llego como un proyecto de fin de semana y me vi con la necesidad de usar una herramienta similar, pero que funcione en MySQL. Revisando un poco las herramientas opensource actuales, vi que ninguna me brindaba la información que necesitaba, por ahí vi que hay algunas herramientas que si cubrían esta necesidad, pero son de pago.

Teniendo esto en mente, la lógica detrás de una herramienta así, no se me hacía tan complicada, y ya antes había visto que uno puede habilitar que se genere un log de los queries que se ejecutan en el servidor.

Opción MySQL Logging en Windows
Cambiar configuración del Logging

Así que de pasada para poner practicar un poco de python, realice un script que nos permite monitorear los comando que ejecutamos.

Primero, debemos asegurarnos de tener habilitada la opción en nuestro MySQL, para que se registren los logs de los queries que se ejecutan (importante considerar que esta opción no es recomendado para servidores productivos, por lo que esta tool esta orientada a desarrolladores).

Ahora, un paso importante en esta configuración, es asegurarnos que esta información no la envie a una archivo de text (opción por default), sino que la mande a una tabla en base de datos. Esto lo conseguimos cambiando un atributo en nuestro archivo de configuración.

Configuración necesaria

Una vez que hemos hecho esto, corramos un script en nuestra base de datos para asegurarnos que se correctamente configurada nuestra tabla en la que se van a grabar todos los queries.

SET @old_log_state = @@global.general_log; 
SET GLOBAL general_log = 'OFF'; 
ALTER TABLE mysql.general_log ENGINE = MyISAM; 
ALTER TABLE mysql.general_log ADD INDEX (event_time); 
SET GLOBAL general_log = @old_log_state;

Con estos cambios, reiniciemos el servidor («services.msc» en windows y «systemctl restart mysql» en linux) y demosle una revisada a la tabla con un simple select, para asegurarnos que ya se están registrando valores.

Es recomendable que también creemos un usuario especifico para nuestra aplicación, esto nos va a servir mucho para poder filtrar solo los queries que pertenecen a nuestra aplicación.

Ahora si vamos a la parte de python. El código es sencillo, veamos un poco cuales han sido las funciones que hemos definido para conseguir nuestro cometido:

  • connect_to_database: Nos permite inicializar una conexión a la base de datos (cambién por la información que requieran para sus ambientes de desarrollo).
connect_to_database
  • fetch_general_log:
    • Realizamos la consulta a la base de datos, acá tenemos un query que permite obtener todas las consultas de un usuario en específico y sanitizando un poco la información que vamos a obtener, obviando consultas automáticas que no realiza nuestra aplicación. No hay mucha ciencia en el query, prefiltramos la data, para no traernos toda la información, dado que la idea de esta herramienta es ver las últimas consultas que se van realizando.
    • Hay una lógica para obtener la fecha del último query valido, esto nos sirve, sólo para mostrar los últimos registros.
    • Si los criterios se cumplen, pintamos en la consola el query realizado. Con la libreria antes instalada, podemos darle color a esto, para identificar cual es la marca de tiempo y cual es el contenido del query.
    • Finalmente, actualizamos cerramos el cursos y nuestra conexión a la base de datos.
fetch_general_log
  • main: Nuestra función inicial, acá inicializamos la fecha actual de nuestro sistema (esta la usaremos para asegurarnos de obtener sólo resultados, luego de esta fecha), indicamos el nombre del usuario por el que luego vamos a filtrar los registros en esta tabla y llamamos a nuestra función «fetch_general_log». Es importante tener en cuenta, que todo esto corre dentro de un bucle «while ‘true'», y se va a realizar cada 1 segundo. Tenemos un try/catch, para capturar algún error en la conexión a la BD y cuando cancelemos con un comando
main

Como ven, es un código sencillo, pero que nos brinda una utilidad importante al momento de desarrollar. Adiconalmente a esto, cree un script en bash, para poder llamarlo como un comando más del SO.

Les dejo una captura de como funciona la herramienta:

Profiler corriendo

Espero que les sirva, al menos para mi es una herramienta más cuando tengo que trabajar en poryecto que usa MySQL como base de datos. Finalmente, les comparto el repositorio donde he subido la herramienta.

REPO GITHUB

Saludos.

Deja un comentario

Integrando AWS Lambda a un Pipeline de Azure Devops

Hola, espero que estén bien. Este post es una continuación al post anterior, en el cual revisamos como podemos realizar el deploy de un api en un AWS Lambda. Ahora seguiremos con lo que es más lógico que suceda luego de hacer esto, automatizar este proceso. En post anteriores, he mostrado el uso de Azure Devops para los procesos de devops que uso, veamos como realizamos en este caso.

Primero, debemos tener creado un rol en IAM para que nuestro pipeline de devops se pueda authenticar. Con esto realizado, ingresar a azure devops y nos dirigimos a la siguientes opción.

Configuramos una nueva credencial
Elegimos AWS

Con esto hecho, vamos a AWS para crear un bucket, que es donde configuraremos luego para que nuestra pipeline deje los archivos.

Creamos un nuevo bucket

En nuestro pipeline, vamos a seleccionar la siguiente tarea.

Tarea a seleccionar
Información a completar

Para que funcione nuestro proceso de devops, es necesario crear una serie de archivos en nuestro proyecto, son dos los archivos, el primero es «serverless.template» y «aws-lambda-tools-defaults.json». Les dejo el detalle de la información que va en cada archivo y les comento cual es la información que debe variar.

serverless.template

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Transform": "AWS::Serverless-2016-10-31",
  "Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.",
  "Parameters": {},
  "Conditions": {},
  "Resources": {
    "AspNetCoreApi": {
      "Type": "AWS::Serverless::Api",
      "Properties": {
        "StageName": "Prod",
        "BinaryMediaTypes": [
          "*~1*"
        ]
      }
    },
    "AspNetCoreFunction": {
      "Type": "AWS::Serverless::Function",
      "Properties": {
        "Handler": "Exphadis.Services.WebApi::Exphadis.Services.WebApi.LambdaEntryPoint::FunctionHandlerAsync",
        "Runtime": "dotnetcore3.1",
        "CodeUri": "",
        "MemorySize": 512,
        "Timeout": 30,
        "Role": null,
        "Policies": [
          "AWSLambda_FullAccess"
        ],
        "Environment" : {
          "Variables" : {
            "ASPNETCORE_ENVIRONMENT" : "",
            "SENTRY_CONNECTION" : "",
            "DATABASE_CONNECTION" : "",
            "JWT_KEY" : "",
            "JWT_EXPIRATION" : ""
          }
        },
        "Events": {
          "ProxyResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/{proxy+}",
              "Method": "ANY",
              "RestApiId": {
                "Ref": "AspNetCoreApi"
              }
            }
          },
          "RootResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "Method": "ANY",
              "RestApiId": {
                "Ref": "AspNetCoreApi"
              }
            }
          }
        }
      }
    }
  },
  "Outputs": {
    "ApiURL": {
      "Description": "Site for Exphadis Support",
      "Value": {
        "Fn::Sub": "https://${AspNetCoreApi}.execute-api.${AWS::Region}.amazonaws.com/site/"
      }
    }
  }
}

Consideren, que deben modificar de esta plantilla:

  • Handler
  • Environment – Variables

aws-lambda-tools-defaults.json

{
    "Information" : [
        "All the command line options for the Lambda command can be specified in this file."
    ],
    "profile"     : "default",
    "region"      : "us-east-1",
    "configuration" : "Release",
    "framework"     : "netcoreapp3.1",
    "s3-prefix"     : "exphadis-service/",
    "template"      : "serverless.template",
    "template-parameters" : "",
    "s3-bucket"           : "exphadis-service",
    "stack-name"          : "Exphadis-Service-Serverless"
}

Consideren modificar la información que visualizan, de-acuerdo al escenario que tengan.

Veamos como tendría que quedar nuestro archivo «pipeline.yml».

trigger:
  - feature/deploy-lambda

pool:
  vmImage: 'windows-latest'

variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'

steps:
  - task: LambdaNETCoreDeploy@1
    name: 'Deploy_Lambda'
    displayName: 'Deploy to lambda'
    inputs:
      awsCredentials: 'AWS Admin'
      regionName: 'us-east-1'
      command: 'deployServerless'
      packageOnly: false
      lambdaProjectPath: '$(System.DefaultWorkingDirectory)\\Exphadis.Services.WebApi'
      stackName: 'Exphadis-Service-Serverless'
      s3Bucket: 'exphadis-service'
      s3Prefix: 'Exphadis.Services.WebApi/'


Notemos especificamente estas partes.

Información importante

Fuera de elegir las credenciales que ya hemos configurado previamente, la región, es necesario indicar:

  • lambdaProjectPath: La ruta en nuestro proyecto donde está nuestro entrypoint para lambda.
  • stackName: El nombre de nuestro lambda
  • s3Bucket: El nombre del bucket en S3 que hemos creado
  • s3Prefix: El prefijo de la carpeta en la que se va a desplegar nuestra proyecto compilado.

Probemos ahora toda nuestra configuración.

Despliegue en lambda desde azure devops

Finalmente, validamos en AWS este cambio.

Publicación visualizada desde AWS
Consumo del api

La demo de todo lo anteriormente explicado, pueden revisarla en el siguiente video.

Espero que les sea util, si tienen alguna duda, los espero en la sección de comentarios.

Saludos.

Deja un comentario

Publicando NetCore Api en AWS Lambda y AWS API Gateway

Hola, en un par de post anteriores les explicaba como dockerizar un entorno de trabajo:

Primera parte

Segunda parte

Hoy quiero explicarles como podemos desplegar nuestro api (para este ejercicio en NetCore 3.1) en AWS Lambda, usando un AWS API Gateway para poder consumir los recursos.

Empezamos en nuestra aplicación en visual studio.

Aplicación en visual studio

Notemos que en esta aplicación hemos modificado nuestro archivo «Program.cs» y los hemos renombrado «LocalEntryPoint.cs» y además hemos añadido un archivo «LambdaEntryPoint.cs». Sobre el primer archivo no entrare en detale por que es el tipico archivo que tenemos cuando creamos un api en netcore. Pero el segundo si nos interesa, dado que ese es el handler que usará nuestro lambda. Veamos entonces, que nos encontramos.

Contenido de LambdaEntryPoint

Este archivo hereda de «API GatewayProxyFunction», veremos más adelante que se usa un método dentro de esta clase para el handler, por lo demás estamos usando el overload de uno de los «Init», especificamente el que tiene como entrada un «IWebHostBuilder» que es una interfaz ya conocida. Ahora vemos que tenemos que hacer en AWS. Como nota aparte, les comento que las demos las estoy usando la web de AWS. Sin embargo, todo esto puede ser reemplazada por la configuración desde la consola para crear los recursos y conectarlos entre si. Sin embargo, pienso que las demás son más dinámicas de este modo.

CREACIÓN DEL LAMBDA

Creación del lambda

Configuramos el handler

Handler por defecto
Nuevo hanlder

El handler que vamos a usar, se configura del siguiente modo.

Exphadis.Services.WebApi::Exphadis.Services.WebApi.LambdaEntryPoint::FunctionHandlerAsync

Donde, el orden es el siguiente:

Orden de elementos para el handler
  1. La ruta del proyecto en la cual esta ubicado
  2. El namespace de nuestra clase
  3. El nombre de la clase
  4. Un método dentro de la clase de la cual estamos heredando

Subimos nuestro código fuente

Realizamos un build de nuestras fuentes y comprimimos
Seleccionamos subir .zip
Seleccionamos nuestro archivo

CREACIÓN DEL API GATEWAY

Crear nueva api
Seleccionamos api rest
Finalizamos la creación

Vamos a configurar nuestro API Gateway para que consuma los recursos de nuestro lambda.

Creamos un nuevo recurso
Configuramos el recurso como un proxy
Indicamos el ARN de nuestro lambda

Mapeamos el ARN de nuestro lambda en el api gateway y aceptamos que nuestro api gateway pueda consumir nuestro lambda.

Brindamos permisos al lambda
Realizamos el deploy del api
Ponemos el nombre del stage que deseemos
Obtenemos el url de nuestro api

En este punto, para mi caso, es necesario configurar algunas variables de entorno, dado que sin esto va a fallar nuestra ejecución. Para poder hacer esto, debemos ir a nuestro lambda, a configuración y adicionar las variables de entorno.

Configuramos variables de entorno, de ser necesario

Finalmente, vamos a realizar la prueba de uno de los siguientes endpoint que tiene nuestro api. En mi caso es un get a un «health» que me permite obtener la fecha y hora del servidor y también de la base de datos, esto con la finalidad de determinar si todo esta funcionando correctamente. El endpoint a consumir sería el siguiente.

Respuesta del endpoint

Si quisieramos ver el log de nuestro lambda, tenemos que ir a la opción «Monitor», y luego ir a cloudwatch a ver los LogGroups que se crean cada ves que consumimos nuestro api.

Opción de monitor en nuestro lambda
Cloudwatch logs
Ejecución de nuestro lambda

Finalmente, sólo voy a comentar que a este endpoint que hemos generado en AWS API Gateway, podemos mapearlo a otros domains, para esto tenemos una opción que se llama «Custom Domain Names», en esta tenemos que indicar cual es el domain que queremos, además nos permite mapearlo a algún certificado que hayamos solicitado previamente para que nuestra api se pueda servir sobre https.

Por otro lado, como explico un poco en el video adjunto en este post, nuestro lambda es manejado enteramente por AWS, sin embargo, nosotros podemos agregar «layers» para poder realizar ajustes sobre nuestro ambiente, en tre los cuales se pueden manejar la instalación de otras librerías en nuestro ambiente de trabajo.

Finalmente, en un siguiente post, voy a mostrar como podemos integrar este flujo en un pipeline de devops con azure devops.

Espero que esten bien, cuidense.

Saludos.

Deja un comentario

Dockerizando una aplicación Net 5.0 – Part. II

Este post es una continuación de su versión anterior, en este nos enfocaremos en como configuramos nuestro ambiente de desarrollo con docker.

Retomemos en el código del proyecto.

Código del proyecto

Vamos a modificar nuestro «launchSetting.json», eliminemos los profiles que tenemos actualmente, creemos uno nuevo para manejar nuestra instancia en docker que tendrá la siguiente estructura.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:61567/",
      "sslPort": 44304
    }
  },
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "Docker": {
      "commandName": "Docker",
      "launchBrowser": true,
      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
      "publishAllPorts": true,
      "useSSL": true,
      "httpPort": 7550,
      "sslPort": 7551,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "SENTRY_CONNECTION": "",
        "DATABASE_CONNECTION": "",
        "JWT_KEY": "",
        "JWT_EXPIRATION": "",
        "URL_BASE_PATH": "",
        "AWS_ACCESS_KEY": "",
        "AWS_SECRET_KEY": "",
        "EMAIL_NO_REPLY": "",
        "ASPNETCORE_URLS": "http://+:8080;https://+:443"
      }
    }
  }
}
Nueva configuración en launchSettings.json

Lo que hemos hecho es crear un nuevo perfil, le indicamos el comando que va a ejecutar «docker», que debe abrir un navegador, la url que se va a abrir (esta como vemos se obtiene de variables, no valores en duro), un poco de dolor de cabeza me llevo descubrir el uso de «httpPort» y «sslPort», dado que estos dos nos permiten indicarle que puertos queremos que use. Esto ultimo parece algo menor, pero nos sirve cuando mapeamos en nuestro postman o en el front el puerto del backend, y que este sea consistente en distintos ambientes, de otro modo, nos asignarán un puerto aleatorio y esto nos generar tener que estar cambiando nuestra configuración en el front. Finalmente, acá también podemos indicarle las variables de entorno que vamos a usar dentro de nuestro dockerfile.

En nuestro ide, selecionamos «Docker» y ejecutamos el proyecto. Si es la primera ves que se ejecuta, se va a crear la imagen en base a nuestro dockerfile, luego de esto, el ide automaticamente va a crear una instancia en base a esta imagen. Veamos que comando es el que usa para ejecutar este comando.

Primero se hace el build:

docker build -f "C:\Users\walbe\Development\Simplicity\simplicity-api\dockerfile-dev" --force-rm -t simplicityserviceapi:dev --target base  --label "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=Simplicity.Service.Api" "C:\Users\walbe\Development\Simplicity"
Docker build

Luego realizamos se realiza el run de nuestra imagen recién creada:

docker run -dt -v "C:\Users\walbe\vsdbg\vs2017u5:/remote_debugger:rw" -v "C:\Users\walbe\Development\Simplicity\simplicity-api\Simplicity.Service.Api:/app" -v "C:\Users\walbe\Development\Simplicity:/src/" -v "C:\Users\walbe\AppData\Roaming\Microsoft\UserSecrets:/root/.microsoft/usersecrets:ro" -v "C:\Users\walbe\AppData\Roaming\ASP.NET\Https:/root/.aspnet/https:ro" -v "C:\Users\walbe\.nuget\packages\:/root/.nuget/fallbackpackages2" -v "C:\Program Files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages" -e "DOTNET_USE_POLLING_FILE_WATCHER=1" -e "ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS=true" -e "ASPNETCORE_ENVIRONMENT=Development" -e "ASPNETCORE_HTTPS_PORT=7551" -e "NUGET_PACKAGES=/root/.nuget/fallbackpackages2" -e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages;/root/.nuget/fallbackpackages2" -p 7550:8080 -p 7551:443 -P --name Simplicity.Service.Api_1 --entrypoint tail simplicityserviceapi:dev -f /dev/null
Docker run
docker run -dt 
-v "C:\Users\walbe\vsdbg\vs2017u5:/remote_debugger:rw" 
-v "C:\Users\walbe\Development\WebApplication1:/app" 
-v "C:\Users\walbe\Development\WebApplication1:/src/" 
-v "C:\Users\walbe\AppData\Roaming\Microsoft\UserSecrets:/root/.microsoft/usersecrets:ro" 
-v "C:\Users\walbe\AppData\Roaming\ASP.NET\Https:/root/.aspnet/https:ro" 
-v "C:\Users\walbe\.nuget\packages\:/root/.nuget/fallbackpackages2" 
-v "C:\Program Files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages" 
-e "DOTNET_USE_POLLING_FILE_WATCHER=1" 
-e "ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS=true" 
-e "ASPNETCORE_ENVIRONMENT=Development" 
-e "ASPNETCORE_URLS=https://+:443;http://+:80" 
-e "NUGET_PACKAGES=/root/.nuget/fallbackpackages2" 
-e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages;/root/.nuget/fallbackpackages2" 

Con esto, ya tendriamos todo funcionando correctamente, les dejo un video de todo este proceso, donde podemos ver mejor como nuestra aplicación corre desde nuestra imagen docker y los cambios que realizamos se actualizan directamente.

Espero que les sirva, en el siguiente post explicare como realizar el deploy de una aplicación en NetCore o Net 5.0 en AWS Lambda.

Deja un comentario

Dockerizando una aplicación Net 5.0 – Part. I

Hola, espero que los que me lean a esta fecha estén bien. Hace ya un tiempo que no publicaba nada, principalmente se debe a la falta de tiempo que he tenido por el trabajo y otras complicaciones que empezaron a surgir como consecuencia de la pandemia. En estos meses he aprendido algunas cosas nuevas relacionadas con AWS, Google Cloud y otras cosas que por ahí alguien puede encontrar interesante.

Retomo las publicaciones con este post, por que les comento que en general, en AWS y GCloud no es tan común que sus servicios soporten la última versión de net que nos ofrece microsoft, en mi caso tenía que migrar algunos servicios de AWS a Google Cloud esta semana que pasó y me di con la sorpresa que app engine no soporta Net 5.0. Sin embargo, soportan docker así que fue la oportunidad perfecta para practicar un poco mis skills en docker que hace un tiempito no he podido jugar con esta herramienta.

Quiero que este sea el primer post, por que en algunos de los que tengo pensado más adelante, quiero usar net 5.0, y de este modo sabrán como manejarse con esta versión del framework. Tomemos como ejemplo una aplicación que tiene especial cariño para mi porque yo la inicie como un proyecto personal hace ya un par de años y poco a poco he ido añadiendole funcionalidades y cosas. Sobre la aplicación, puedo decir que usa EF, Clean architecture y que no voy a poder compartirles el código. Sin embargo, este post es para centrarnos en docker y como implementarlo en un ambiente de desarrollo y de producción.

Dicho esto, empecemos, en nuestro proyecto base, vamos a crear un archivo que llamaremos «dockerfile-dev» y otro «dockerfile-prod». Veamos cual es la estructura de nuestro proyecto desde visual studio y desde el explorador de windows.

Explorador de windows
Visual studio

Para este ejemplo, hemos creado los archivos de correspondientes de docker en la raiz del proyecto. haciendo un build de nuestro proyecto para ver si todo esta funcionando correctamente.

Build del proyecto

Además, es importante revisar el archivo «launchSetting.json» dentro de la carpeta «Properties» como saben este archivo te permite configurar los distintos ambientes en los que quisieras levantar tu aplicación localmente, acá manejaremos las variables de entorno que necesitemos, entre otras configuraciones.

Archivo launchSetting.json

Notemos, que por defecto el visual estudio, nos esta proporcionando ya algunos perfiles. Para nuestro caso, queremos poder levantar nuestra aplicación localmente dentro de una imagen docker. Esto último es un salvavidas en algunos casos, al empezar algún proyecto, hay un tiempo que puede ser menor o mayor para poder levantar las aplicaciones que se tengan, si estuvieran dockerizadas, sólo tendrías que clonar el proyecto, instalar docker, correr tu «docker build .» dentro de la carpeta donde estar el dockerfile y listo, puede iniciar el trabajo. Tenganlo en cuenta por si más adelante les puede servir.

Empecemos trabajando con el archivo «dockerfile-prod». Añadamos las siguientes lineas y luego detallaremos un poco que es lo que esperamos conseguir con estas.

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 8080

Estamos tomando como base de nuestra futura imagen docker la imagen del repositorio oficial de microsoft para Net 5.0. el «AS base» es un alias que le ponemos en nuestro dockerfile a esta imagen. La sentencia «WORKDIR /app» le indica docker cual es nuestro directorio de trabajo y finalmente el «EXPOSE 8080» le dice que vamos a exponer el puerto 8080 dentro de nuestro container.

ENV ASPNETCORE_ENVIRONMENT=${_ASPNETCORE_ENVIRONMENT}
ENV AWS_ACCESS_KEY=${_AWS_ACCESS_KEY}
ENV AWS_SECRET_KEY=${_AWS_SECRET_KEY}
ENV DATABASE_CONNECTION=${_DATABASE_CONNECTION}
ENV EMAIL_NO_REPLY=${_EMAIL_NO_REPLY}
ENV JWT_EXPIRATION=${_JWT_EXPIRATION}
ENV JWT_KEY=${_JWT_KEY}
ENV SENTRY_CONNECTION=${_SENTRY_CONNECTION}
ENV ASPNETCORE_URLS=${_ASPNETCORE_URLS}
ENV URL_BASE_PATH=${_URL_BASE_PATH}

Esta sección es basicamente para declarar variables de entorno, podemos ver que en este caso las variables hacen referencia a otras variables, esto lo dejo asi por que si esto va a ir a producción, hay un pipeline en el que se reemplazarán estas variables por motivos de seguridad y buenas prácticas.

RUN apt-get update
RUN apt-get install -y apt-utils
RUN apt-get install -y libgdiplus
RUN apt-get install -y libc6-dev 
RUN ln -s /usr/lib/libgdiplus.so/usr/lib/gdiplus.dll

Esta parte la añado por que es necesario para esta aplicación, basicamente instala en nuestro entorno una librería necesaria para generar archivos excel que por defecto no están en linux. Sin embargo, nos sirve para ver como correr comandos dentro de una imagen docker.

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ./ ./Simplicity/
RUN dotnet restore Simplicity\\Simplicity.Service.Api\\Simplicity.Service.Api.csproj

Acá vamos a tomar como base para hacer el build el sdk de microsoft de 5.0, le indicarmos cual va a ser nuestro directorio de trabajo para hacer nuestro build, copiamos todo el codigo que tengamos en la carpeta en la que esta ubicada nuestro archivo dockerfile con el siguiente comando «COPY ./ ./Simplicity/» en una carpeta que vamos a crear «Simplicity». Finalmente, en esta sección vamos a ejecutar el comando «dotnet restore» que si recuerdan la imagen de los archivos esta dentro de «Simplicity.Service.Api\\Simplicity.Service.Api.csproj» en nuestro caso, como le estamos añadiendo una carpeta más, termina siendo «Simplicity\\Simplicity.Service.Api\\Simplicity.Service.Api.csproj».

COPY . .
WORKDIR /src/Simplicity
RUN dotnet build Simplicity.Service.Api\\Simplicity.Service.Api.csproj -c Release -o /app/build

En esta parte, nuevamente copiamos los archivos al directorio actual, le indicamos a docker cual va a ser nuestro nuevo directorio de trabajo con «/src/Simplicity» y finalmente realizamos nuestro build, recordemos en este punto que estamos haciendo un release y le añadimos que el directorio del output «-o /app/build», consideremos que ahora no es necesario ponerle «Simplicity» al inicio por que ya lo pusimos en el workdir.

FROM build AS publish
RUN dotnet publish Simplicity.Service.Api\\Simplicity.Service.Api.csproj -c Release -o /app/publish

Ya estamos cerca de terminar, en esta parte vamos a hacer el publish con el típico comando «dotnet publish», tambien tenemos que indicar el output. Y finalmente, ya llegamos a la última parte de la configuración de nuestro dockerfile.

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Simplicity.Service.Api.dll"]

En este punto, indicamos nuestro directorio de trabajo, copiamos lo que tenemos en nuestra carpeta publish en una carpeta publish que vamos a crear en la carpeta «app» y con el comando «ENTRYPOINT» le indicamos a docker que comando queremos que ejecute, para nuestro caso es «dotnet» al archivo «Simplicity.Service.Api.dll». Realicemos el build de nuestro dockerfile.

docker build -t simplicity-service --file dockerfile-prod .
Realización del build del dockerfile
Realización del build
Listado de imágenes de docker

Ahora ejecutemos esta imagen docker y probemos el resultado. Notemos en el siguiente comando que le indicamos que corra en modo deamon con el «-dt» y también es necesario que le indiquemos el mapeo de los puertos de nuestra imagen docker con la máquina en la que se está ejecutando, esto lo conseguimos con «-p 4750:8080».

docker run -dt -p 7551:8080 cb831
Ejecutamos nuestra imagen docker
Listado de imagenes docker ejecutandose

Finalmente, ejecutemos en nuestro browser el localhost para el puerto 7551.

Ejecución de nuestra aplicación dentro de la imagen docker

Les comparto el dockerfile que hemos desarrollado completo.

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 8080

ENV ASPNETCORE_ENVIRONMENT=${_ASPNETCORE_ENVIRONMENT}
ENV AWS_ACCESS_KEY=${_AWS_ACCESS_KEY}
ENV AWS_SECRET_KEY=${_AWS_SECRET_KEY}
ENV DATABASE_CONNECTION=${_DATABASE_CONNECTION}
ENV EMAIL_NO_REPLY=${_EMAIL_NO_REPLY}
ENV JWT_EXPIRATION=${_JWT_EXPIRATION}
ENV JWT_KEY=${_JWT_KEY}
ENV SENTRY_CONNECTION=${_SENTRY_CONNECTION}
ENV ASPNETCORE_URLS=${_ASPNETCORE_URLS}
ENV URL_BASE_PATH=${_URL_BASE_PATH}

RUN apt-get update
RUN apt-get install -y apt-utils
RUN apt-get install -y libgdiplus
RUN apt-get install -y libc6-dev 
RUN ln -s /usr/lib/libgdiplus.so/usr/lib/gdiplus.dll

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ./ ./Simplicity/
RUN dotnet restore Simplicity\\Simplicity.Service.Api\\Simplicity.Service.Api.csproj

COPY . .
WORKDIR /src/Simplicity
RUN dotnet build Simplicity.Service.Api\\Simplicity.Service.Api.csproj -c Release -o /app/build

FROM build AS publish
RUN dotnet publish Simplicity.Service.Api\\Simplicity.Service.Api.csproj -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Simplicity.Service.Api.dll"]

La parte del post, en el que explico como levantar la aplicación para nuestro ambiente de desarrollo, debuggear y realizar cambios de nuestro desarrollor que se actualicen directamente en nuestra imagen docker la realizare en un siguiente post. Espero que lo que he desarrollado hasta acá les sirva. En unos días publico la segunda parte.

Deja un comentario

Descargar Archivo en Angular

Bueno, con este post espero complementar el post anterior:

Lo hago con angular, por que cuando me toco revisarlo en el trabajo use angular para hacerlo. Pero, me parece que encontrarán algunas funciones que les pueden servir, dado que son javascript puro.

Basicamente, tenemos una aplicación creada usando el tipico:

ng new app-name

Le he instalado algunas librerias para poder tener algo más decente que un alert, otras que las podrán revisar en el código fuente que como siempre dejo disponible. La demás lógica la he realizado en el «app.component» que se crea por defecto. Revisemos que hemos creado en nuestro «app.component.html».

Adicion de botón de descarga

Al html inicial que se genera con nuestro nuevo proyecto ded angular, estoy adicionandole un botón de carga.

app.component.ts

Primer metodo, descarga el excel
Segundo método, le da formato como archivo y lo guarda en la pc

Acá, tenemos dos métodos, uno que nos permitirá descargar el array de bytes consumiendo el servicio, y el otro que obtendrá esta información, y lo descargará a nuestra máquina. Recordemos que si queremos que nuestro navegador abra una ventana para elegir donde guardar el archivo, se debe configurar en el mismo browser, de no ser así, el navegador lo descargará automáticamente en una carpeta (de descargas probablemente).

Finalmente, veamos que tenemos en nuestro servicio.

Consumo del servicio

Como se puede ver, es necesario en el header, indicar que esperamos un tipo «blob». Hagamos ahora una prueba.

Abrimos nuestra aplicación, podemos ver nuestro botón, lo presionamos

Recordemos que tenemos que tener el servicio corriendo en el mismo puerto en el que se está poniendo en el servicio.

Petición al servicio.

Sale la petición a nuestro servicio

La petición, llega al endpoint.

Llega nuestra petición al servicio

Podemos ver acá la respuesta que estamos recibiendo de nuestro servicio.

Se descarga el archivo.

Descarga final del archivo

Bueno, así finalizamos esta serie de post de generar un archivo excel y descargarlo desde una web en angular. Espero que les sirva, dejo el video con la explicación y el codigo fuente en github por si les interesa.

REPO: GITHUB

Saludos.

11 comentarios

Crear Excel con Apache POI y Spring Boot

Bueno, tuve que dejar un par de semanas de hacer post por que me acumule de trabajo y quería tocar este tema por que es algo que me parece muy útil para quien le toque ver temas referidos a la generación de excel con Spring boot o en general con java. Y para serles franco el código en este post no va a ser pequeño, por lo que hacer una demo que yo considero útil para varios posibles escenarios con los que se van a encontrar en algún trabajo y proyecto me tomo unas cuantas horas por lo que me demoro el desarrollo del post.

Espero que lo que revisemos y el código y video que deje les sea de provecho por si llegan a tener algún escenario como el que voy a plantear para iniciar con la explicación.

Escenario

Digamos que en su trabajo/freelo/proyecto personal, quieres realizar la generación de un excel. El modo más sencillo sería empezar a definir las columnas que va a tener y así ir dándole la forma al excel. Sin embargo, si queremos que este sea dinámico. Digamos que necesitamos un componente al cual le pasamos una estructura de datos y este nos genera un excel formateado y demás. Claro que el nivel de customización dependen de lo que se busque, pero un caso similiar tuve hace unos días en el trabajo y fue divertido por que me permitió aprender más sobre la librería en la cual me baso para esta demo, Apache POI, es de seguro ya conocida por mucho en el mundo de Java, teniendo en mente lo que acabo de plantear, veamos como esta distribuida nuestra demo.

Distribución del Proyecto

Distribución del Proyecto
Distribución del Proyecto

Como pueden ver en la imagen, tenemos las siguientes capas:

  • Controller: Exponemos nuestro endpoint
  • Model: Nos permitirá manejar de manera genérica la información de nuestro excel.
  • Service: Ejecutamos la lógica para la generación del excel.
  • Transversal: Son funciones que podremos usar a lo largo de la aplicación.
  • Util: He creado varias funciones que nos permitán manejar mejor ciertos casos que se pueden presentar en la generación de un excel (formato de decimales, fechas, etc).
  • Resources; Parte propia de spring, acá de ubica el yml de configuración. Sin embargo, también estoy poniendo el json del cual vamos a partir para generar el excel.

Revisemos ahora que tenemos en cada uno de los componentes:

Controller

End-point para obtener el reporte
End-point para obtener el reporte

Como pueden ver, estamos devolviendo a la petición GET un objeto del tipo Resource. Esto lo hacemos por que nuestro end-point retorna un array de bytes e indicamos en el header el formato del excel, en el key «Content-Disposition», además, de indicarlo en el content-type del response. En el siguiente post voy a explicar como se descargaría este excel desde una aplicación de angular sencilla.

Model

Información de las columnas
Información de las columnas
Información de las filas
Información de las filas

Sólo usaremos estas dos clases para manejar toda la información que queramos renderizar en nuestro excel, cuando revisemos las otras capas veremos como dinamicamente vamos generando el excel. Notese, que «Field» me permite mapear los campos que obtengo de base, por su nombre, descreipción y tipo de dato que como vemos es un enum. Estoy usando lombok para no llenarme de codigo de inicialización de las clases y demás.

Service

Método que se consume desde el controller
Método que se consume desde el controller

Este método es el que nos retorna el objeto Resource y por lo tanto es el se encarga de llamar a los otros métodos que nos permiten generar el excel. Antes de explicar que hace cada uno de estos métodos, comentar que «data» y «columns» básicamente están leyendo los json que tenemos en resources y los están parseando a un objeto con un método creado en utils que me permite hacer esto. Veamos que hacen los otros métodos:

generateFieldsReport:

Creación de la lista de Field
Creación de la lista de Field

Recorremos la lista de información de columnas para generar una lista de fields. Acá estamos usando el enum para determinar de que tipo de cada es cada columna de nuestro excel. Esto lo usaremos para el formato en un paso posterior.

buildCellTypes:

Obtenemos todos los tipos de datos que tendrá nuestro excel
Obtenemos todos los tipos de datos que tendrá nuestro excel

Obtenemos todos los tipos de datos que tendremos en nuestro excel.

buildDataLines:

Generamos un listado de array de string que es la información que ira en nuestro excel
Generamos un listado de array de string que es la información que ira en nuestro excel

En este método basicamente estamos obteniendo la información que va a llenar nuestro reporte de excel, en base al tipo de datos que ya obtuvimos estamos dándole el formato al cuerpo del excel en cada una de las columnas. Como se puede ver, estamos recibiendo la lista de maps que obtuvimos de la base de datos (nuestro json para nosotros).

createXlsxFileAsByteArray:

Generación del excel parte 1
Generación del excel parte 1
Generación del excel parte 2
Generación del excel parte 2

Finalmente, en esta capa, el método en el que ya trabajamos con Apache POI, acá generaremos el excel y lo convertiremos a un array de bytes. Empezamos creando un objeto del tipo «SXSSFWorkbook», dentro de este definimos un «Sheet» que es la hoja sobre la que trabajaremos, le ponemos el nombre que queramos. Las siguientes lineas me sirven para darle el formato de fecha a los campos que correspondan, lo defino a este nivel por que si se van creando conforme se recorren las filas de excel, se genera una excepción si nuestro excel tiene mas de 10 mil filas, dado que si definimos más de 10 mil objetos del tipo «CellStyle» en un mismo excel apache poi lanza una excepción. Empezamos a recorrer nuestro «dataLines» que si mal no recuerdan es donde se pusimos todo el contenido de nuestro excel y uno a uno vamos a hacer validaciones sobre cada fila, en la primera iteración no hacemos mayores validaciones por que es la cabecera solo seteamos esta cabecera y le damos cierto formato, a partir de aqui si nos vamos a valer de un switch por el tipo de datos para darle el formato de corresponda. Para finalizar, escribimos toda la información de nuestro workbook en nuestro objecto «ByteArrayOutputStream» y retornamos nuestro método con un «toByteArray()».

Ya en este punto, tenemos el bytes de array para devolver en el end-point. Las demás funciones utilitarias que se usan en el proyecto (leer el json, formatear decimales, fechas, etc) no las voy a explicar en el post por que sería extenderlo más y de por si ya a quedado muy amplio.

REPO GITHUB

Saludos

Deja un comentario

Spring Boot con Docker

Bueno, cada ves es más frecuente que los proyectos tengan todo un pipeline de devops para sus releases. He incluso, que en los lugares donde trabajan o en los proyectos que desarrollen tengan integrado ambientes con docker en los mismos proyectos. De manera tal, que puedan tener un ambiente de trabajo en el que se tenga la certeza que no se tiene algún factor adicional y que todos están trabajando en el mismo ambiente.

Tal ves resulte un poco dificil de comprender por que es esto importante. Pero creanme cuando les digo que puede ser un dolor de cabeza levantar algunas aplicaciones. En este post quería mostrar como podemos configurar nuestro ambiente de desarrollo con spring boot usando devtools.

Pensando en esto, es que me plantee la pregunta como integrar spring boot con docker. La meta de este post sería tener un proyecto web api con spring boot que que nos permita tener un ambiente configurado y desplegado en nuestro container. Y que al hacer cambios en nuestro proyecto, podamos ver los cambios en nuestra imagen docker sólo con la realización de un build.

Empezamos usando spring initializer para generar un proyecto base, sólo le pondremos las siguientes dependencias:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

Creamos un controlador en nuestro proyecto, sólo la demo.

Controlador para probar la actualización

Indicamos el puerto en el que queremos que nuestra aplicación funcione, esto lo hacemos poniendo en el «application.yml» la siguiente propiedad:

server:
  port: 8055

Sólo añadiendo la dependencia de devtools, tenemos acceso a ciertas funcionalidades en nuestra aplicación. Como por ejemplo, que nuestra aplicación mumestre los cambios que realizamos sin necesidad des volver a parar el debugging e iniciarlo, es algo parecido al live-realoading que tenemos con webpack. Provemos esto, y luego nos metemos de lleno a la configuración de con docker.

Probando el api inicialmente
Build al cambio que hemos realizado
Probamos el api nuevamente, visualizamos el cambio

Acá viene la magia de spring, para poder dockerizar nuestra aplicación sólo debemos de ejecutar un comando en la carpeta raiz de nuestro proyecto.

mvnw spring-boot:build-image

Esto, nos generará una imagen docker en nuestro equipo (obviamente antes debemos de tener instalado docker) con el nombre que hemos definido para nuestro proyecto. Para la demo sería del modo siguiente:

Nombre proyecto

De modo, que si ejecutamos el comando «docker ps», tendríamos un resultado similar a este:

Imagen creada

Es posible que la primera ves que ejecutan este proceso tome un poco de tiempo, dado que esta bajando la imagen base por primera ves.

Para poder correr nuestro proyecto en la imagen docker, tenemos que ejecutar el siguiente comando.

docker run --tty --publish 8055:8055 demo:0.0.1-SNAPSHOT

Hasta este punto, tenemos nuestro proyecto dockerizado. Pero que pasa con el live reloading? Bueno, nos toca realizar algunos pasos adicionales. Primero, en nuestro archivo de configuración yml debemos añadir la siguiente configuración.

spring:
  devtools:
    remote:
      secret: wgutierrez

Luego de esto, debemos de añadir una configuración adiciona en nuestro POM, veamos como estaba antes, y como debería de quedar.

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Tenemos que añadir una configuracion al plugin para que no excluya dev tools.

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <!--PARA PODER ACTUALIZAR EL PUBLICADO SOLO COMPILANDO-->
                <configuration>
                    <excludeDevtools>false</excludeDevtools>
                </configuration>
            </plugin>
        </plugins>
    </build>

Y finalmente, debemos añadir una nueva configuración para nuestro run/debug.

Configuración del run/debug

Es importante, mencionar que la clase:

org.springframework.boot.devtools.RemoteSpringApplication

La usamos por que nos permite tener un filewatcher, que detacta los cambios en nuestro proyecto y los replica en nuestro container. Además, tenemos también que indicar la url con el puerto que hemos configurado en nuestro proyecto.

De este modo, me parece que tenemos el panorama más claro sobre como podemos tener un proyecto spring boot con docker. Dejo el link y el repo del video.

REPO GITHUB

Saludos.

Deja un comentario

SQS en AWS con Spring Boot – Parte II

En el post pasado, revisamos que es una cola cuando hablamos de desarrollo, en que casos nos puede servir y que alternativas encontramos en el mercado para poder usarlas. Puntualmente, hicimos una demo de el uso de esta herramientas con SQS que es un servicio ofrecido por AWS. Sin embargo, buscando no extender el post y la demo demasiado conforme iba avanzando el post anterior, decidí que era mejor dividirlo en dos partes. En la primera, revisamos como crear desde AWS un usuario con los permisos necesarios para conectarse a una cola, en código con un proyecto hecho con spring boot conectarnos a esta y poder enviar mensajes.

En esta segunda parte, revisaremos como podemos suscribirnos a una cola, y estar a la escucha de nuevos mensajes.

Para poder conseguir esto, y no estar llenandonos de proyectos voy a usar el mismo proyecto en spring boot de la ves pasada, con la diferencia que creare algunos nuevos componentes, la idea basicamente es consumir una api desde un controlador, esta encola el mensaje y en un listener recibirlo e imprimirlo en consola.

Veamos que hemos añadido en nuestro proyecto inicial.

POM

Nuevas dependencias en el POM
Nuevas dependencias en el POM

JmsConfiguration

Clase de configuración del JMS
Clase de configuración del JMS

SqsListener

Clases del SqsListener
Clases del SqsListener

Con lo añadido en nuestro POM, tenemos acceso a clases de configuración que nos permitirán configurar un listener para las colas que ya tenemos definida en SQS. Si vemos a detalle la clase de configuración que hemos creado, tenemos lo siguiente:

Configuración en el JmsListener
Configuración en el JmsListener

En esta clase, como podemos ver, estamos creando un factory para las conexiones a AWS esto en el método «sqsConnectionFactory» y adicionalmente, configuramos nuestro listener factory para estar a la escucha de nuestra cola definida en AWS.

Como ven, tenemos que indicar que esta clase es de configuración con @Configuration, además de indicarse que habilite el JMS con @EnableJms. Sólo para aclarar por si hubiera alguna duda, JMS son las siglas de Java Message Service que es una clase definida en la librería JMS que nos permite realizar la creación y suscripción de mensajes a través de una cola.

Y finalmente, tenemos que crear nuestro listener, esto lo hacemos del siguiente modo:

SqsListener
SqsListener

Creamos una clase y dentro creamos un método void con las siguientes anotaciones «@JmsListeners» que tiene dentro un «@JmsListener». Es necesario dentro de esta última anotación indicarle cual es el nombre de la cola que vamos a estar a la escucha en nuestro listener.

Hagamos una prueba de todo junto.

Primero realizamos el consumo de nuestro controlador con una mensaje cualquiera.

Consumiendo el API
Consumiendo el API

Recibimos el mensaje en nuestro controlador.

Recibiendo el mensaje en el controlador
Recibiendo el mensaje en el controlador

El mensaje es enviado a nuestra cola previamente configurada en SQS.

Se genera el mensaje correctamente
Se genera el mensaje correctamente

Mensaje encolado en SQS.

Mensaje en SQS
Mensaje en SQS

Ahora, segundos despues de que se encoló el mensaje, debería llegar a nuestro listener que esta escuchando la misma cola «SqsTest».

Recibimos el mensaje
Recibimos el mensaje
Log del mensaje
Log del mensaje

Con esta prueba concluye este post. El código lo subire como una actualización al proyecto anterior. Espero que les sirva, como ven es sencillos tener un manejo de colas funcional con sólo un poco de código. Dejo el link del repo y el video.

REPO GITHUB

Saludos.

2 comentarios