Saltar al contenido

Categoría: NetCore

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

CI and CD con VSTS

Hola, aún en cuarentena aprovecho para hacer un breve post sobre integración y deploy continuo. Como deben de haber notado, luego de todo el proceso de desarrollo que hemos cubierto en algunos post que he realizado antes y con distintas tecnologías, siempre terminamos la demo realizando una prueba de nuestra aplicación mientras esta en debugging. Sin embargo, en un escenario real sabemos que esto es inviable. Dado, que necesitamos que nuestra aplicación este desplegada en un ambiente productivo. Cuando uno inicia en el mundo del desarrollo, probablemente no es quien se encargue de hacer los pases a producción y es muy probable dependiendo en que empresas estés trabajando, que este pase sea un simple «copiar y pegar» de una carpeta a otra manualmente, o que tengan una solución automatizada para llevar esto a cabo.

Esta última es la que me encargaré de desarrollar en este post. Yo he podido vivir ambos tipos de despliegue, siendo el más gratificante los últimos por que puedes ver que si bien es cierto la configuración inicial requiere más trabajo, además de tener ciertos conceptos claros, te permite automatizar los despliegues futuros eliminando el factor humado.

Primero, tengamos claro que para conseguir un proceso de CI y CD tenemos distintos stacks de tecnologías, entre las más grandes podríamos mencionar a azure, aws, jenkins, etc. Y también podemos encontrar variantes entre las que son libres y las que te ofrecen una serie de servicios adicionales como escalamiento y demás. Para la demo que voy a realizar, he decidido optar por hacerla usando VSTS (Visual Studio Team Services) que es basicamente un servicio ofrecido por microsoft que nos permite mapear todos los procesos del desarrollo de software, desde la gestión que se debe realizar hasta el despliegue. La estoy usando por sobre otras, por que es gratuita con sus limitaciones, y es accesible sólo con tener una cuenta de microsoft.

Mi idea, es generar un proyecto MVC con net core de 0, mapearlo a un repositorio como puede ser github, luego vincular nuestro repositorio a nuestro pipeline en VSTS y desde ahí configurar los archivos YAML indicandole los pasos que debe seguir. Para el hosting de nuestra aplicación demo, no estoy usando ninguna de las alternativas que ofrece Azure, por que quise hacerlo en un hosting propio en el que tengo una cuenta así que por medio de una tarea FTP, paso los archivos.

Primero, debo comentar que cuando queremos generar un proyecto con netcore desde la consola y no queremos usar la última versión que tengamos instalada, en cambio queremos indicarle una es necesario crear en la carpeta una archivo «global.json» indicandole esto al cli. Empecemos poniendo los comando que debemos usar para crear nuestra aplicación demo:

$ mkdir demo-mvc-ci-cd # creamos la carpeta donde alojaremos nuestra aplicación
$ cd demo-mvc-ci-cd # ingresamos a la carpeta
$ echo > global.json

En el archivo que hemos creado en necesario ingresar un json con la siguiente estructura.

{
    "sdk": {
      "version": "2.2.401"
    }
}

En el cual, como ven indicamos la versión del sdk queremos que se use. Una ves creado esto, podemos crear nuestra aplicación netcore.

$ dotnet new mvc # nuestra aplicación tendrá el mismo nombre de la carpeta
$ dotnet restore

Hasta aquí, tenemos nuestra aplicación en netcore creada, ahora inicializemos nuestra repositorio con git, previamente en mi caso he creado un repositorio en github para almacenar ahí nuestra aplicación.

$ git init
$ git add .
$ git commit -m "hola"
$ git remote add origin https://github.com/walberth/demo-mvc-ci-cd.git
$ git remote -v
$ git push origin master
$ git checkout -B "master" "origin/master"

Ya con nuestro código en github, es momento de ir a VSTS, les comento que con sólo tener una cuenta en microsoft (outlook, live, etc) ya tienes acceso a este servicio. Al inciar debemos crear un proyecto si es que aún no lo tenemos, en mi caso es un proyecto que he llamado demo.

Proyectos VSTS

En nuestro proyecto demo, podemos hacerle seguimiento a todos los procesos de desarrollo de software de nuestra aplicación. Sin embargo, nosotros vamos a centrarnos sólo en la integración y el deploy continuo. Vamos a la siguiente opción.

Opción del menú

Creamos un nuevo pipeline, y nos preguntará donde está nuestro código, en mi caso mi repositorio está en github así que le indicaré que lo obtenga de ahí señalándole el nombre del repositorio. Luego de esto nos pide que elijamos el tipo de tecnología que deseamos compilar, en nuestro caso elegimos net core y nos carga la siguiente plantilla:

Plantilla YAML

Acá debemos realizar varios cambios, veamos uno a los lo que debemos poner.

steps:
- task: DotNetCoreCLI@2
  inputs:
    command: 'restore'
    projects: '**/*.csproj'
    feedsToUse: 'select'

- task: DotNetCoreCLI@2
  inputs:
    command: 'build'
    projects: '**/*.csproj'
    arguments: '--configuration $(buildConfiguration)'

- task: DotNetCoreCLI@2
  inputs:
    command: 'publish'
    publishWebProjects: true
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: false

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'
    publishLocation: 'Container'

En steps, debemos añadir tareas de netcore, la primera es para restaurar los nuggets que necesitemos, la segunda es realizar un build de nuestro código indicando que es en release, la tercera es realizar la publicación de nuestro compilado con netcore, dejándolo en «$(Build.ArtifactStagingDirectory)» que es una variable de entorno de VSTS y finalmente, la última tarea es para copiar ese compilado a la carpeta final en la que la tendremos lista para poder obtenerla desde nuestra tarea de despliegue continuo. Grabamos todos los cambios realizados.

Grabamos cambios

Cuando empieza a correr, tenemos la siguiente vista.

Actividad en la cola
Realiza las actividades

Como podemos ver en la última imagen, el proceso se a llevado a cabo de-acuerdo a lo que le hemos indicado, ha realizado todos los pasos y al final a desplegado el artefacto donde le indicamos que lo haga. Ahora vamos a ver como realizar el despliegue continuo.

Para esto, vamos a la opción de releases y creamos uno nuevo.

Creando un nuevo release

Seleccionamos el artefacto que vamos a usar y se lo indicamos al release.

Luego de añadirlo, es necesario que le indiquemos los pasos que vamos a usar acá tenemos un sin fin de opciones y de las más variadas, en mi caso simplemente realizaré una tarea FTP para que se conecte al site donde debe dejar el compilado. Si intentamos entrar a este site ahora tendremos la siguiente respuesta.

Respuesta del site antes de publicar

Agregamos nuestra tarea FTP.

Tarea FTP
Información FTP
Grabamos nuestro release
Realizamos el deploy
Realiza el proceso de despliegue

Si en este punto, validamos nuestra web, debería de tener nuestra aplicación ya desplegada.

Aplicación

Finalmente, hagamos un cambio en la aplicación, luego realizar un commit y push a master y luego de unos minutos deberíamos de tener nuestra despliegue realizado.

Cambio en la app

Luego de unos minutos que todo nuestro proceso automatizado se lleva a cabo, vemos nuestra web nuevamente y el cambio esta reflejado.

Cambio reflejado

Bueno, con esto concluyo el post que al final no salio tan chico. Espero que les sea de utilidad, igual pueden dejarme algún comentario o comunicarse conmigo por alguna de las redes sociales. No dejo link de github en este caso por que esta demo no iba enfocada en código.

Saludos.

1 comentario

Web API NetCore 3.0 con EF

Esta semana me toco estar en una reunión donde entre otras cosas se discutía el desarrollo de un nuevo microservicio que permita suplir alguna necesidad del negocio, y mientras se exponían puntos de vista sobre que deberíamos considerar, se tuvo un debate ameno sobre el uso de un ORM para tener acceso a datos en una base de datos relacional. Esto me llevo a querer hacer una demo sobre como funciona entity framework con NetCore 3.0, como se debería configurar para conectar a una base de datos y poder obtener información.

Así nace este post, tenemos un proyecto básico que nos permite conectarnos a una base de datos en MySQL, y que con EF podemos tener acceso a su información. Aproveche para configurar algunas cosas adicionales en el proyecto que también voy a explicar.

Primero quisiera comentar que mi historia con EF, no siempre fue la mejor. Cuando inicie en mi primer trabajo como practicante recuerdo que el sistema que tenían usaba ADO.Net con la ejecución de procedimientos almacenados, y en ese tiempo el lider técnico era un evangelizador de los beneficios que brindaba poder modificar los procedimientos sin necesidad de tener que realizar un deploy de la aplicación. Y asi es como yo inicialmente no tuve mucho contacto con EF, en trabajos venideros empece a aprender más sobre bases de datos, sobre distintos tipos de ORM, y poco a poco me fui interesando sobre el potencial que tenía EF, hasta que llego un proyecto en el que pude verlo en extenso y ser testigo de todo el potencial que esconde, claro con los problemas que también puede acarrear.

Digo problemas, por que si es mal usado puede devenir en un ineficiente manejo de la base de datos, todo depende de como lo usen los desarrolladores y que prácticas se sigan. Ahora si, veamos el proyecto.

Partimos ejecutando el siguiente script de la base de datos, que pueden encontrar en la carpeta scripts del repositorio.

Scripts

Veamos un poco el diagrama entidad relación de esta base de datos de demo.

Diagrama E-R

Como podemos ver, tenemos un diagrama con 12 tablas, entre las cuales de modo básico tenemos el manejo de las direcciones de una persona, y los estudiantes. Veremos más a detalle como se vincula la información en el video. Revisemos, ahora el proyecto.

Configuración del proyecto

Repasemos cada una de las carpetas y como se han dividido.

  • Configuration: Acá he puesto algunos archivos de para el correcto funcionamiento del proyecto, el archivo «ContainerProvider.cs» es para la inyección de dependencia, «ExceptionMiddleware.cs» nos permite capturar alguna excepción no controlada que se pueda generar en la ejecución de la aplicación y centralizar las acciones que tomen y finalmente «SwaggerServiceExtensions.cs» que nos permite realizar la configuración de swagger para generar documentación en nuestra API.
  • Context: En esta carpeta, se tiene el archivo «EFCoreContext.cs» que nos permite configurar entity framework, las relaciones entre las distintas tablas entre otras cosas.
  • Controller: Acá irán todos los controladores que necesitemos exponer en nuestra API.
  • Entity: La entidad que obtendremos de la base de datos.
  • Mapper: Estamos usando automapper en el proyecto, en esta carpeta pondremos los distintos profiles que requiramos crear para realizar el mapeo de nuestra información.
  • Model: En esta carpeta pondremos todas los DTO, que se expondrán como información por nuestro API.
  • Repository: Esta carpeta será nuestro repository, para el cual tenemos la interfaz, y su implementación.
  • Script: Acá estoy dejando ciertos archivos importantes para poder levantar la demo, el script de base de datos, diagrama E-R, etc.
  • Service: Acá pondremos la lógica de negocio que consideremos necesaria. También tiene la interfaz y su implementación.
  • Transversal: Pondremos acá distintas utilidades que podemos usar en el proyecto.

Para no entrar en detalle de la configuración en EF de todas las entidades, tomemos como modelo a la entidad «person».

Configuración de la entidad person

Como podemos ver, nos valemos de las «DataAnnotations» que nos permitan indicar el nombre de la tabla que se va a mapear a una entidad, del mismo modo con las columnas, en caso posean un nombre distinto en la base de datos. Mediante estas anotaciones es posible especificar más información, como las relaciones entre las tablas o las llaves primarias y foraneas que se tienen. Sin embargo, particularmente prefiero indicar estas relaciones en el context.

Veamos que configuración se tiene en este archivo.

Configuración contexto

Como podemos ver, es necesario realizar un override al método «OnModelCreating» y ahí especificar las relaciones entre las distintas tablas, así como también las llaves foraneas. Entre otro tipo de configuración con la base de datos que sea necesario realizar. Revisemos ahora el repositorio y como estamos obteniendo la información.

Llamada al contexto

Tenemos una interfaz que declara un método asyncrono que permite obtener el listado de estudiantes y en su implementación hacemos uso de EF para obtener esta información con los joins que son necesarios para nuestra demo. En la capa de servicio, tenemos los siguiente.

Servicio

Tenemos la implementación de la interfaz para la capa de servicio, como se puede ver, se obtiene del repositorio la información y luego se realiza un mapping de estos campos para el DTO que terminaremos devolviendo en el API. Notemos que no tenemos ningún try-catch hasta ahora en nuestro código, esto dado que se a implementado un middleware que nos permita centralizar los errores. Veamos como se implemento esto.

ExceptionMiddleware

Si ocurriera algún error en la ejecución de nuestra solicitud, se terminará entrando en nuestro método «HandleExceptionAsync» acá podríamos tomar las acciones necesarias, para la demo estoy devolviendo un error genérico que un GUID autogenerado por cada error.

El proyecto no es nada del otro mundo, con respecto a lo que ya hayamos visto con net core, pero me parecío interesante hacer una guía sobre como usar EF. Pueden revisar el video en donde seguro hablo a más detalle sobre algunas cosas de la implementación del proyecto, el proyecto demo también esta en github por si quieres revisarlo y si hubiera alguna duda adicional pueden dejarme una pregunta.

REPO: GITHUB

Saludos.

3 comentarios

Logging en Cloudwatch (Amazon Web Services)

Hace un tiempo en el trabajo tuve la oportunidad de ser parte de un proyecto que buscaba centralizar el logging de múltiples aplicaciones en una sola y persistir esta información no en una base de datos, sino en uno de los servició que ofrece Amazon Web Services (AWS) para este fin que es CloudWatch. Esto me llevo a todo un proceso de investigación y recabar información sobre la mejor manera de realizar este grabado de log. De ese proceso de investigación nace este post.

Primero, hay que decir que AWS es todo un mundo por si solo, se tienen muchos servicios orientados a necesidades especificas en el proceso de desarrollo de software, asi como también de cara al despliegue de aplicaciones, seguridad, networking, almacenamiento, entre otros. Esto me lleva a comentarles que en adelante intentaré realizar más post sobre los servicios que ofrecen y como se pueden usar, esto obviamente lo hago para compartir la información y también afianzar lo poco o mucho que puedo ir aprendiendo. Vamos al tema en específico, cuando me planteo realizar esta aplicación, que por lo demás tenía claro que era un API en net core, busque que alternativas tengo para comunicarme con AWS y específicamente con cloudwatch, encontrando las siguientes:

  • SDK de Amazon
  • Diversas librerias que se integran con aplicaciones de loggers (Nlog, Log4net, Serilog, etc).

Debido al tiempo que tenía para realizar la aplicación y a la experiencia que tenía con las herramientas que tenía a la mano, termine decantandome por usar la librería para Nlog. Esto, por que ya tenía el conocimiento de Nlog, te instala un módulo que le permite tener acceso a cierta información para grabar un log. Sin embargo, esto me trajo algunas complicaciones para el caso de net core que ya más adelante veremos.

Proyecto

La demo, consta de una aplicación web api generada de 0, con lo mímimo necesario apra que pueda funcionar nuestro log en AWS. Las librerías a usar, son las siguientes:

  • AWS.Logger.NLog (v1.5.1)
  • NLog (v4.6.8)

Me interesa explicar como se lleva a cabo esta configuración, dado que lo demás es lo típico con cualquier aplicación en net core. En nuestro archivo «Startup.cs» tenemos que realizar las siguientes configuraciones:

Configuración del target en nlog para AWS

Config target

Como se puede ver, se crea una nueva clase del target (que esta disponible luego de instalar la librería) y tenemos que proporciarle cierta información:

  • Credentials: Se le indica el tipo de autenticación que se va a usar, para la demo he creado en AWS un IAM (manejo de indentidad) que sólo tenga permisos para poder comunicarse con cloudwatch (en otro post voy a ahondar en como AWS maneja la seguridad), existen otras tipos de credenciales, por ejemplo en el trabajo dado que nuestro aplicativo está en un EC2 se puede obtener la autenticación de la máquina.
  • LogGroup: Cada uno de los log que se graben están asociados a un log group, que digamos que es la instancia que se crea para almacenar todos los logs que provengan de ese cliente.
  • Region: Es la región en la cual está nuestra cuenta de AWS. Entendamos que esta plataforma esta mundial, y por lo tanto tiene servidores en distintos lugares del mundo, esto debido a buscar que sus servicios sean escalables y redundantes ante cualquier eventualidad y a ti como usuario te permite elegir consumir los servicios que ofrecen en distintos lugares del mundo. De esto también se puede ahondar más adelante.
  • LogStreamNameSuffix y LogStreamNamePrefix : Cuando se crea una nueva instancia en un log group esta se crea con un identificador único la cual es un GUID más un timestamp, esto es así si no se le proporciona ningún parámetro. En cambio si le pasamos un sufijo o un prefijo ya no se genera el GUID, sino se le concatena a lo establecido (prefijo y sufijo) la marca de tiempo.
  • Layout: Este punto lo deje al final por que es donde probablemente me deba explayar más. como ven es un texto, pero me estoy valiendo de los layouts renders proporcionados por nlog para obtener cierta información y evitarme tener que obtenerla por otros medios.

Ampliando el último punto, pueden encontrar más información sobre esto en este link.

Una ves que se configura el target de AWS, es necesario indicarle a nlog que debe de usarlo, asociarle los eventos del log y la configuración a logmanager. Veamos como se hace esto en código.

Configuración de nlog

Como se puede ver, se crea el un nuevo «LoggingConfiguration», a este se le asocia el target a aws creado y además las reglas a las cuales estará vinculado nuestro target, finalmente también estoy indicando donde estará el log interno de nlog y le paso toda está configuración a logmanager.

Para cierta información que en teoría debería ser proporcionada por los layout render, pero que parece que en net core no funcionan correctamente, me valí de un Middleware, acá pueden ver que información de obtiene de ahí.

Configuración del middleware

Se está obteniendo la URL que se está consumiendo, el username que consume la aplicación, si se tuviera como header, el método http (PUT, POST, GET, etc). Finalmente, cuando un evento de log (para nuestra demo excepción) se lleva a cabo.

Evento de error disparándose
Disparando el log

Finalmente, veamos nuestro appsettings para ver que configuración tenemos ahí.

Configuración del appsetting

El accesskey y secretkey son las que cree para la demo, este usuario ya ha sido eliminado de mi cuenta de aws.

Realicemos la prueba de nuestra demo para ver si efectivamente tenemos nuestro log en AWS ahora. Primero tomemos algunas capturas del ambiente antes de ejecutar nuestra demo.

Log Groups en AWS

Listado de log groups

Postman

Get request

Levantamos la solución (con lo que nuestro log group ya debería haberse creado en AWS) y realizamos la solicitud al api.

Entro a la excepción
Esta por enviarse el log a AWS
Respuesta en postman
Validamos que se creo nuestro log group
Validamos nuestro log stream
El log grabado

Como podemos ver (si hacemos zoom) nuestro log se ha grabado correctamente en AWS.

Con esto llegamos al fin del post, dejo el link al repositorio donde está el código fuente por si quieres revisarlo y un video de la demo.


REPO: GITHUB

Saludos.

2 comentarios

Interface Segregation Principle

El penúltimo principio del que nos toca escribir hoy es el de la segregación de interfaces. El corolario de este principio nos indica lo siguiente:

Los clientes no deberían ser forzados a depender de métodos que no vayan a usar.

El problema que se tiene con las grandes interfaces podemos verlo claramente en la clase MembershipProvider que usaba microsoft hace algún tiempo.

MembershipProvider de Microsoft

Hace un tiempo, microsoft sólo soportaba SQL Server como provider, por lo que si se necesitaba usar algún otro provider, era necesario implementar todos los métodos que pueden ver en la imagen.

El resultado de violar este principio, nos lleva a no implementar completamente las interfaces, dado que no es necesario usar todos métodos, esto hace que poco a poco empecemos a adquirir en nuestro código más dependencias, lo cual es muy probable que nos termine ocasionando las siguientes complicaciones:

  • Más acoplamiento.
  • Código más frágil.
  • Más dificultas de probar el código.
  • Más dificultas en los pases a producción.

Básicamente, lo que nos indica es que debemos evitar crear interfaces con demasiados métodos. Esto lo conseguimos separando las responsabilidades de nuestra interfaces. Para poder determinar si en nuestros proyectos estamos violando este principio, es necesario buscar grandes interfaces, una muestra clara la podemos ver si en nuestro código encontramos algún método que implementa una interfaz y una de estas implementaciones tienen un «NotImplementedException».

Respetar Interface Segregation Principle

Si se diera el caso en que tenemos refactorizar un proyecto para que se respete este principio, podemos considerar las siguientes estrategias, sea cual sea el caso en el que nos encontremos.

  • Romper una interfaz grande en pequeñas (siempre y cuando nosotros tengamos control de las interfaces a refactorizar).
  • Si se diera el caso en el que dependemos de una gran interfaz (entiéndase un caso como la implementación del provider indicado lineas arriba), lo que se debe hacer es mediante el patrón adapter segregar las interfaces que necesitemos, y sólo este adapter conocerá todos los métodos de la interfaz que queremos disminuir.

La demo que desarrollare para demostrar lo mencionado en este post, consta de una clase que llamaremos IMachine, que es el simil de una impresora multifuncional.

Interfaz IMachine

Si queremos implementar esta interfaz en un método que sólo imprima, nos veremos en la siguiente situación.

Implementación de IMachine, sólo para imprimir

Con métodos que no necesitamos, por esto se crea una interfaz para cada una de las acciones «IFax», «IPrint» y «IScann». Del mismo modo, si se requiriera que nuestro equipo escanee y también imprima, podríamos crear una interfaz que herede a su ves de estas dos interfaces, del siguiente modo:

Herencia entre interfaces

Como ven, no se esta generando ningún método dentro, dado que el IScan y el IPrint, ya tiene sus implementaciones propias. Así mismo, cuando hacer la implementación de nuestra interfaz IMultiFunction, podemos valernos del patrón decorator para no implementar nuevamente los métodos de imprimir y de scan, sino usar los que ya existen en estas interfaces.

Aplicación del patrón decorator

Espero que con este post se tenga un poco más claro el concepto que nos brinda este principio. El último principio que compone SOLID es el ded la inyección de dependencia, y este lo estaremos revisando probablemente en el siguiente post. Como siempre, dejo el video del desarrollo del post y el link al repositorio en github.

REPO: GITHUB

Listado de POST sobre SOLID anteriores:

Deja un comentario

Liskov Substitution Principle

El tercer principio que compone SOLID es el Liskov Substitution Principle, y este nos plantea

Plantea una guía de cuando debemos usar herencia en POO.

Let Φ (x) be a property provable about objects x of type T. Then Φ (y) should be true for objects y of type S where S is a subtype of T.

Barbara Liskov, 1988

Este principio, podemos entenderlo en su modo más simple de la siguiente forma «Las clases derivadas, deben poder ser sustituidas por sus clases base».

Para ejemplificar este principio, usaremos el típico ejemplo del rectángulo y el cuadrado. Veamos el proyecto que tenemos definido:

Nuestra clase base es el rectángulo:

Clase base rectángulo

Queremos calcular el área de nuestro rectángulo o las clases que lo implementen, como un cuadrado. Para esto, creamos el siguiente método:

Método para hallar el área

Digamos que ahora necesitamos implementar un cuadrado, y con esta finalidad creamos la siguiente clase:

Clase del cuadrado

Como ven, el cuadrado implementa nuestro rectángulo y lo que hacemos es definir en sus propiedades que las propiedades base (las que esta implementado) son iguales en su ancho y altura, que como sabemos corresponden a los lados de un cuadrado. Si probamos obtener el cuadrado de ambas clases, tendremos el siguiente resultado:

Resultado al obtener el área sin probar el principio Liskov

Notese que se esta creando una nueva instancia del Square y por esto obtenemos los resultados correctos. Sin embargo, si buscamos poner en práctica lo definido por el principio liskov, veamos que resultado obtenemos.

Resultado poniendo a prueba el principio Liskov

Lo que sucede es que nuestra clase Rectangle, es la que es inicializada, por lo tanto sólo posee el Width igual a 4, y no el Height. Cuando se busca obtener el área realiza la multiplicación del ancho por la altura y tenemos como resultado 0. Para poder respetar este principio, es necesario que realicemos el siguiente cambio:

Cambios para respetar el principio Liskov

Lo que hacemos es declarar como virtuales las propiedades de nuestra clase base, por lo que cuando se incialice y el compilador determine que es virtual, va a buscar algún override que es justo lo que hacemos en la clase derivada. De este modo, sabe que debe usar la propiedad de Square e iguala los lados a 4 que es lo que definimos en el Width. Si probamos ahora nuestro método, tendremos el siguiente resultado:

Prueba final del cálculo del área

Donde como se ve, ya se respeta el principio que hemos tocado ampliamente en este post. Espero que les ayude a entender un poco más este principio, así mismo comentarles que este es el tercer post de los principios que componen SOLID. Como siempre, dejo el video del desarrollo de esta post y el link al repositorio en github.

REPO: GITHUB

Listado de POST sobre SOLID anteriores:

Deja un comentario

Open Close Principle

Siguiendo con la linea de post que presentan los principios que componen marco de buenas prácticas SOLID. El que vamos a desarrollar en este post es el «Open Close Principle», esto hace referencia a que el código que desarrollamos debería estar abierto a ser extendido y cerrado a las modificaciones.

Sin más, veamos el ejemplo que vamos a desarrollar en este post:

Supongamos que estamos desarrollando un sistema para un almacen, en el cual se manejan productos. Estos productos, poseen características, las cuales pueden ser:

Clase de Productos

En donde los «Enums» mostrados poseen los siguientes elementos:

Clase de Enums

Digamos que por necesidad del negocio, nos vemos obligados a filtrar los productos que vamos almacenando por su color, teniendo esto en mente, creamos una nueva clase que llamaremos «ProductFilter».

De este modo, si queremos filtrar un grupo de colores, por un color en particular deberíamos hacer algo muy similar a esto:

Filtro por color los productos

De este modo, tenemos 3 productos, los cuales se agregan a una lista y esta lista se proporciona al método de filtrado que hemos creado. Si ejecutamos esta aplicación, tenemos el siguiente resultado:

Filtrado por color a los productos
Filtrado Correcto

Como podemos ver, se ejecuta sin problemas. El problema viene cuando requerimos realizar más filtros. Digamos que ahora nos indican que el filtrado también sea por tamaño, o por color y tamaño, terminariamos llenando nuestra clase «ProductFilter» de métodos. Algo muy similar a esto:

Llenado de métodos para diversos tipos de filtros

Como ven, esto rompe claramente el principio de Open Close Principle. Deberíamos de poder extender nuestros filtros, sin afectar la clase que ya se tiene, esto se puede conseguir a través de interfaces. En este caso, para respetar este principio voy a usar el «specification pattern». Veamos como podemos implementarlo y respetar este principio.

Es necesario creas dos interfaces para poder llevar a cabo la implementación de este patrón de diseño, siendo la primera una que nos permitirá definir la especificación, cuando esta va a estar satisfecha y el tipo de elemento que recibe como tipo de entrada y la segunda espara implementar el filtro y como este se lleva a cabo.

Interfaz IFilter
Interfaz ISpecification

Ahora, implementemos el primero de los filtros, este es es el «ColorSpecification»:

ColorSpecification

Como pueden ver, implementamos la interfaz definida en el paso anterior, tenemos una variable que se inicializa en el constructor, que para el filtro es el color y en el método «IsSatisfied» solo se retorna true si el color del producto que nos proporcionan es igual al que se inicializa en el constructor.

Para poder usar este filtro es necesario implementar también la interfaz «IFilter», lo cual realizamos del siguiente modo:

Implementación del IFilter

Del mismo modo que en la implementación anterior, se se le indica el tipo de elemento con el cual se desea filtrar, iteramos sobre la lista de estos elementos y solo solo cuando la especificación haya sido satifecha retornamos, el «yield» lo usamos por la eficiencia en el recorrido de nuestra lista.

Veamos como usamos este filtro en nuestra clase principal y que resultado obtenemos:

Uso del filtro
Resultado del nuevo filtrado

Finalmente, mostrare como se puede realizar el más de un filtro a la vez, para ejemplificar esto, creare un filtro por tamaño, siguiendo el mismo esquema ejemplificado anteriormente. Para poder adicionar más de un filtro es necesario crear una nueva clase pero ahora su tipo también será genérico, del siguiente modo:

AndSpecification para realizar más de un filtro

Inicializamos dos variables en el constructor, ambas del tipo «ISpecification», y sólo se satisface cuando ambas variables lo están. Para consumir este método, debemos de hacerlo del siguiente modo:

Consumiendo más de un filtro a la vez

Se le tienen que indicar los filtros que se quieren realizar y al consumir este filtro en el cual buscamos un producto del color verde y de tamaño pequeño, nuestro resultado es el siguiente:

Resultado de ambos filtros

Espero que les ayude a entender un poco más este principio, así mismo comentarles que este es el segundo post de los principios que componen SOLID. Como siempre, dejo el video del desarrollo de esta post y el link al repositorio en github.

REPO: GITHUB

Listado de POST sobre SOLID anteriores:

Deja un comentario