Cómo desarrollar una aplicación open source en Ruby

Lucas Hourquebie
Unagi
Published in
8 min readNov 10, 2021

En este artículo voy a contarte cómo adaptamos una aplicación Ruby on Rails para poder compartirlo como código libre a la comunidad, junto a los cambios y decisiones que tomamos y que pueden servirte para tu proyecto, sea de código abierto o no.

Illustration by Victoria Chepkasova from Ouch!

Sobre el proyecto

AFIP es el ente nacional de recaudación de Argentina. Todos los negocios/empresas que quieran tener un sistema de facturación interno pueden hacer uso de sus web services. El inconveniente es que dichos WS suelen resultar confusos porque no están muy bien documentados. Por eso, creamos afip-invoices, una API en Ruby on Rails para que cualquier dev pueda integrarse con el ente de una forma más amigable.

Rubocop: una cuestión de estilo

A la hora de compartir código a la comunidad, e incluso dentro de una organización o empresa, es importante utilizar un lenguaje común al programar.

Pero si todos pensamos y programamos diferente, ¿por qué tenemos que escribir de la misma forma? Bueno, la idea no es que todos escribamos de la misma manera, sino más bien seguir un cierto conjunto de reglas para mantener una armonía en la forma en la que escribimos nuestro código, buscando siempre expresividad y mantenibilidad.

Aquí es donde entran las guías de estilo. Particularmente para Ruby, tenemos una guía de estilos muy completa, y que es ampliamente la más utilizada en la comunidad. Una guía de estilo es el nombre que se le da al conjunto de normas usadas para escribir código.

Afortunadamente para el mundo de Ruby, existe RuboCop. RuboCop es una gema que analiza nuestro código y nos marca ofensas en función de que la guía de estilos determina que es correcto, como por ejemplo el largo de los métodos, el uso de condicionales, la complejidad de las ramas y demás. Como no todos trabajamos de la misma manera, RuboCop nos permite ajustar las reglas a través de un simple archivo de configuración, especificando qué tan estrictas pueden ser, o incluso si queremos apagarlas para algunos archivos.

A modo de ejemplo, éste es el archivo de configuración que escribimos para nuestra API. Lo que hace es simplemente sobreescribir aquellas reglas que queremos modificar.

Secretos vs variables de entorno

Un aspecto importante en un proyecto es la organización de valores confidenciales o de entorno. Particularmente para el caso de esta API, la cual cuenta con un acceso administrador mediante token, era importante almacenar dicho valor encriptado. A su vez, varios valores dependen del entorno, de forma de que en un ambiente local o de staging se consumen los web services de test de AFIP, pero en entorno productivo se utiliza el propio de AFIP.

Originalmente, este proyecto contaba con la administración de credenciales por entorno utilizando las funcionalidades que vienen implementadas por defecto en Rails (antes llamadas secretos, y ahora credenciales). El problema de este enfoque es que íbamos a tener que compartir el archivo maestro para desencriptar, tal y como indica Rails en su documentación, o bien indicar qué credenciales almacenar luego de crear tal archivo.

Si bien esto último no es incorrecto, preferimos mudarnos a una solución basada en variables de entorno. Gracias a este enfoque y utilizando la gema Figaro, definimos un archivo de muestra indicando qué variables de entorno deben ser configuradas. De esta forma, cada desarrollador necesita únicamente definir su propio archivo de configuración en base a su entorno.

Deployment con Capistrano

En Unagi usamos muy frecuentemente Capistrano, una gema que automatiza todos los procesos de deploy y conexión con diferentes entornos de servidor. A su vez, es una de las gemas más usadas en lo que respecta a este tipo de tareas para Ruby/Rails, por lo que decidimos incluirla como parte del repositorio.

Para lograr esto, conectamos Capistrano con Figaro, por lo que la configuración necesaria para la conexión con el servidor queda a cargo del desarrollador a través de variables de entorno, tal y como se describe en el README del proyecto.

Por lo tanto, decidimos configurar dos instancias de forma de que fuera lo suficientemente flexible para que el sistema pueda ser puesto en marcha tanto en un ambiente de staging como en uno de producción. La configuración del deployment de Capistrano puede encontrarse en este archivo, mientras que los parámetros de cada entorno se localizan en este directorio. Por supuesto que no es necesario utilizarlos a ambos, pero si así se quisiera, se podrían agregar tantos ambientes como se deseara.

En cuanto al app server, optamos la versión más reciente de Puma al momento de la implementación, la cual es la 5.4.0. A diferencias de versiones anteriores que veníamos utilizando, esta utiliza systemd como soporte para la gestión del demonio del servidor, por lo que tuvimos que adaptar nuestros procesos de deploy para dar soporte a este cambio.

Postman y la wiki como fuentes de documentación

El alojamiento de la documentación fue un motivo de debate durante la etapa de planificación antes del lanzamiento de la versión open source de nuestra API. Luego de analizar algunas alternativas, nos decantamos por el uso de Postman.

Postman es una herramienta que permite registrar, coleccionar y ejecutar diferentes tipos de solicitudes HTTP. Es ampliamente utilizado por la comunidad, y permite fácilmente guardar y compartir colecciones. A su vez, tiene una gestión de ambientes que permiten cambiar variables de entorno, lo cual es muy útil para parametrizar información como el host o el token de acceso de la API, usando los mismos endpoints que tenemos almacenados.

Ejecución de solicitud HTTP perteneciente a una colección en Postman.

A su vez, Postman ofrece una interfaz gráfica llamada Documenter que organiza los diferentes endpoints en categorías, y por cada uno de ellos permite almacenar respuestas. Esto es útil para mostrar a quien la use los diferentes tipos de respuesta que puede obtener. En nuestro caso, compartimos en este enlace una versión web de la colección, a la vez que la anexamos como parte del repositorio en este directorio (junto a un ejemplo de las variables de entorno para usar en Postman).

A modo complementario, también decidimos documentar ciertos los flujos de funcionamiento más importantes de la API utilizando la misma wiki del repositorio. Creemos que la combinación entre Postman y la wiki de GitHub son suficientes para explicar cómo usar la API.

El que avisa no traiciona: guía de contribución y código de conducta

Como todo proyecto de software libre, dejamos abierta la puerta para que cualquier persona pueda colaborar. Pero como todo proceso colectivo, son necesarias reglas para que toda contribución se maneje dentro de un contexto de orden, respeto y compromiso.

Es por eso que decidimos redactar una guía de contribución y seguir un código de conducta, el cual adoptamos de Contributor Covenant para instrumentar el comportamiento esperado de las personas que colaboren con el proyecto.

A su vez, creamos algunas plantillas de issues para cubrir los casos más comunes:

  • Solicitud de nueva funcionalidad.
  • Reporte de bug.
  • Mejora de documentación.

Democratizando la puesta en marcha con Docker

Decidimos dockerizar la aplicación entendiendo que es la forma más sencilla para los desarrolladores de poder deployar la aplicación localmente sin tener que instalar las dependencias, el motor de base datos y evitar configuraciones. Además de definir el Dockerfile, también definimos el docker-compse.yml para levantar la aplicación, la base de datos y Redis para manejar la caché.

El proceso solo requiere la creación de un archivo para las variables de entorno, el cual es muy similar al que realizamos con Figaro, todo el proceso está explicado en el README.

Utilizamos la imagen ruby:2.7.4-slim-buster de los repositorios oficiales de Ruby. Elegimos la versión slim porque contiene los paquetes mínimos y necesarios para correr Ruby, lo cual la hace muy liviana. En caso de querer usar Docker en un ambiente de producción tal vez sea necesario usar un versión completa. A su vez, optamos por buster porque a la fecha es la release de Debian estable.

Otras ventajas de el uso de de Docker son:

  • Portabilidad: evitamos problemas a la hora de correr nuestra aplicación en distintos sistemas operativos o plataformas.
  • Eficiencia: levantar una aplicación con Docker es muy rápido por cómo se manejan los contenedores.
  • Aislamiento: una aplicación que corre en Docker solo usar los recursos que se le asignaron.
  • Escalabilidad: con Docker es muy simple correr varias instancias de la aplicación.

Probando, probando

Nuestra aplicación tiene altos niveles de testing y de cobertura de código, y buscamos que así se mantenga con el correr del tiempo. Es por eso que vimos necesario automatizar el proceso de inspección de código en búsqueda de ofensas, así como también la ejecución de la suite de testing.

Es por ello que nos valimos de GitHub Actions, el servicio de GitHub que permite la descripción de un flujo de trabajo a ejecutar ante determinados eventos. En nuestro caso, automatizamos las siguientes tareas:

  • Ejecución de test suite de RSpec.
  • Inspección de código con Rubocop.
  • Generación de reporte de cobertura de código en función del resultado de la suite de testing.

El flujo de trabajo quedó configurado para ser ejecutado siempre que se abra un pull request, cuando se realicen revisiones de pull requests, y cuando se detecta un push en la rama principal del repositorio, que es main.

En cuanto al feedback que se agrega al repositorio, configuramos el README para leer estos valores y mostrar el estado y el porcentaje de cobertura.

Badges de estado en README de repositorio en GitHub.

Redactando un README a conciencia

Como último y no por ello menos importante, creamos un README teniendo en cuenta todas las consideraciones que un desarrollador podría llegar a necesitar.

Por un lado, especificamos las tecnologías principales y el propósito del proyecto, además de agregar los enlaces correspondientes a la wiki y a la información respecto a contribución.

Por otra parte, indicamos tanto para Docker como sin él, la guía de puesta en marcha del sistema, así como también las cuestiones a tener en cuenta durante la configuración, como las variables de entorno. Para configurar ciertos valores en ella, se agregaron tareas rake, las cuales también se encuentran documentadas en esta sección.

Siguiendo con el README, también describimos el proceso de deploy, junto a todas las variables de entorno que son necesarias configurar para utilizar hasta dos entornos de servidor: staging y producción. Junto a eso, también describimos los comandos de Capistrano para ejecutar un deploy inicial, y los consecuentes.

Finalmente, describimos brevemente cómo funciona y cómo importar las colecciones de Postman descritas anteriormente.

A modo de adicional, también adjuntamos algunos diagramas de flujo explicando algunos de los procesos más complejos del sistema, a fin de dar contexto a aquellas personas que quieran colaborar con el mantenimiento y la mejora de la lógica de funcionamiento.

En Unagi hemos estado haciendo pequeñas contribuciones a la comunidad open source a la vez que consumiendo muchos proyectos existentes. Sin embargo, no fue hasta hace poco que cumplimos nuestra meta de publicar nuestro primer desarrollo open source en Ruby.

Como equipo, estamos muy orgullosos de poder compartir este proyecto con la comunidad, ya que creemos que es un aporte del que muchas empresas se van a poder aprovechar. A su vez, esperamos que la comunidad se haga activamente partícipe del desarrollo del mismo, colaborando tanto en la implementación de nuevas funcionalidades, así como también reportando errores y mejoras en la documentación.

Creemos que este software cubre un hueco en lo que respecta a una aplicación completa que resuelve la integración con el sistema nacional de facturación, en este caso desarrollado en un lenguaje sumamente expresivo y fácilmente utilizable por cualquiera.

Este es simplemente el primero de muchos proyectos en los que Unagi colaborará para fomentar el uso libre y colaborativo del software al servicio de la comunidad🙌. ¡Que lo disfruten!

--

--

Lucas Hourquebie
Unagi
Writer for

Software engineer, Jedi Knight and Pokémon trainer. He/him.