¿Dónde se pueden ver las librerías y partes juntas?

Construyendo Microservicios en Python: Guía Esencial

18/05/2022

Valoración: 4.7 (4323 votos)

La arquitectura de microservicios ha revolucionado la forma en que construimos y desplegamos aplicaciones, prometiendo escalabilidad, resiliencia y flexibilidad. Sin embargo, más allá de la omnipresencia del "Stack de Netflix" y Java, muchos desarrolladores se preguntan cómo abordar este paradigma en otros lenguajes. Si eres de los que buscan construir microservicios potentes y eficientes en Python, te has topado con el lugar correcto. En este artículo, nos sumergiremos en las herramientas, librerías y mejores prácticas para estructurar tus proyectos de microservicios en Python, dejando de lado la necesidad de "reinventar la rueda" y enfocándonos en lo que realmente funciona.

¿Dónde se pueden ver las librerías y partes juntas?
Para ver todas estas librerías y partes juntas, podéis verlo en el siguiente repositorio el arquetipo u otros ejemplos en el grupo Python Microservicios. Inclusive el mismo arquetipo montado con Django si a pesar de todo lo explicado no os he arrastrado al lado oscuro de Flask.

No profundizaremos en el diseño de microservicios en sí mismo, ya que existe una vasta literatura al respecto. Nuestro objetivo es práctico: guiarte a través de los frameworks y librerías clave que te permitirán poner tus manos a la obra y construir un ecosistema de microservicios robusto y mantenible. Prepárate para descubrir cómo componentes aparentemente dispares pueden unirse para formar una solución coherente y de alto rendimiento.

Índice de Contenido

El Corazón de Nuestro Microservicio: Flask vs. Django

Cuando se trata de desarrollar aplicaciones web en Python, Flask y Django son los dos gigantes que dominan el panorama. Para el propósito específico de construir microservicios, hemos optado por Flask, y hay razones de peso para ello. La filosofía de "responsabilidad única" que subyace a la arquitectura de microservicios se alinea perfectamente con la naturaleza ligera y "menos opinionated" de Flask. Esto lo convierte en una opción increíblemente versátil, ideal para construir API Management, integrar bases de datos no relacionales, o trabajar con sistemas como GraphQL, sin la sobrecarga que un framework más completo como Django podría implicar.

Es importante considerar que los microservicios, por su propia naturaleza distribuida, a menudo conllevan un mayor consumo de memoria y recursos. Para abordar esta preocupación, realizamos comparativas de rendimiento exhaustivas entre ambos frameworks. Las pruebas involucraron levantar aplicaciones en contenedores Docker con Gunicorn (8 procesos) y someterlas a escenarios de carga intensos: 1.000 clientes realizando 10.000 peticiones y, posteriormente, 100.000 peticiones a un endpoint que consultaba una base de datos y recuperaba múltiples registros. Los resultados fueron reveladores:

Comparativa de Rendimiento: Flask vs. Django

MétricaFlaskDjangoVentaja de Flask
Consumo de MemoriaMenorMayor (8.3% más)8.3%
Tiempos de RespuestaMás RápidosMás Lentos (5.9% más)5.9%

Estas cifras, aunque pueden variar según el caso de uso específico, demuestran claramente las ventajas de Flask gracias a su código más ligero. Una aplicación básica en Flask puede ser ejecutada con tan solo cinco líneas de código, lo que ilustra su simplicidad y eficiencia:

from flask import Flask app = Flask(__name__) @app.route('/') def index(): return 'Hello World'

Y se ejecuta fácilmente desde la consola:

$ FLASK_APP=app.py flask run

Si la ligereza y el rendimiento son cruciales para tus microservicios, Flask se posiciona como una elección superior por su capacidad de mantener las aplicaciones ágiles y con un menor consumo de recursos.

Gestión de Datos: El Poder de SQLAlchemy

En el ámbito de los Object-Relational Mappers (ORMs) en Python, SQLAlchemy ha sido durante mucho tiempo el estándar de oro por su flexibilidad y potencia, especialmente cuando se enfrentan a consultas complejas. Aunque las últimas actualizaciones de Django han mejorado significativamente su ORM, SQLAlchemy sigue siendo, desde nuestra perspectiva, insuperable para escenarios donde la complejidad de las consultas es una prioridad.

Es cierto que la curva de aprendizaje de SQLAlchemy es considerablemente más pronunciada que la del ORM de Django. Sin embargo, para simplificar su integración y uso dentro de un entorno Flask, recurrimos a la librería Flask-SQLAlchemy. Esta extensión implementa de base scoped_session, lo que nos permite interactuar con SQLAlchemy de una manera más "Django friendly". Por ejemplo, una consulta simple se vería así:

Con SQLAlchemy puro:

Colors.query.all()

Con Flask-SQLAlchemy:

session.query(Color).all()

Esta abstracción facilita la gestión de sesiones de base de datos y la ejecución de consultas, haciendo que el potente SQLAlchemy sea más accesible para los desarrolladores acostumbrados a la simplicidad de otros ORMs.

Rastreo Distribuido: Opentracing y Jaeger

En un ecosistema de microservicios, donde las peticiones pueden atravesar múltiples servicios y lenguajes, la trazabilidad se convierte en un desafío crítico. Cada lenguaje solía tener su propio método preferido, como Sleuth en Java, que "hace magia negra" para generar trazas automáticamente. Pero, ¿qué sucede cuando tu stack tecnológico es heterogéneo y combina Python, Java, Node.js y otros?

Para abordar esta complejidad, ha surgido la iniciativa Opentracing. Este estándar permite estandarizar el uso de trazas entre los lenguajes y frameworks más populares, proporcionando una API agnóstica al proveedor. Esto significa que puedes instrumentar tus servicios una vez y luego elegir entre una multitud de clientes de rastreo, como Jaeger (desarrollado por Uber y compatible con la propagación B3 y Zipkin), LightStep, Apache SkyWalking, entre otros.

Opentracing es fundamental para entender el flujo de una petición a través de tu arquitectura, identificar cuellos de botella y depurar problemas en sistemas distribuidos. La única "pega" notable es que actualmente existen algunas incidencias con Python 3, pero se espera que se resuelvan a medida que la adopción y el desarrollo maduren.

Organización y Dependencias: Flask Injector

Las aplicaciones en Flask son conocidas por su ligereza, a menudo comenzando como proyectos de un solo fichero. Sin embargo, a medida que un microservicio crece, incorporando configuración para múltiples entornos, modelos de datos, y una docena de endpoints, la organización del código se vuelve crucial. Los blueprints de Flask ofrecen una solución excelente para modularizar tu aplicación.

Pero, incluso con los blueprints, un problema común emerge cuando comienzas a integrar múltiples librerías: para bases de datos, seguimiento de trazas, autenticación JWT, etc. Esto puede llevar fácilmente a "importaciones cíclicas", donde un fichero necesita una librería que está en otro, que a su vez la necesita en el primero. Para evitar este entramado de dependencias, Flask Injector se convierte en tu mejor amigo.

Flask Injector es una librería de inyección de dependencias que te permite gestionar cómo se crean y proporcionan los objetos y servicios a tu aplicación. Esto no solo resuelve los problemas de importaciones cíclicas, sino que también facilita la escritura de código más modular, testeable y mantenible. Aquí un ejemplo de cómo se utiliza:

# Route with injection @app.route("/foo") def foo(db: sqlite3.Connection): users = db.execute('SELECT * FROM users').all() return render("foo.html") def configure(binder): binder.bind( sqlite3.Connection, to=sqlite3.Connection(':memory:'), scope=request, ) # Initialize Flask-Injector. This needs to be run *after* you attached all # views, handlers, context processors and template globals. FlaskInjector(app=app, modules=[configure]) # All that remains is to run the application app.run()

Esta aproximación te permite definir cómo se resuelven las dependencias (como una conexión a la base de datos) y luego "inyectarlas" automáticamente donde se necesiten, promoviendo un diseño de código limpio y desacoplado.

Asegurando la Compatibilidad: Tox para Testing

Desarrollar software ad hoc rara vez presenta problemas de versiones. Sin embargo, cuando construyes una librería o un módulo que será reutilizado por múltiples microservicios, los "problemas de versiones" se vuelven una pesadilla recurrente: "En mi Django 1.8 no funciona", "En Python 3.3 falla pero en Python 3.6 OK"... Esta fragmentación es aún más crítica en un entorno de microservicios donde la duplicidad de código es un riesgo constante.

Para desacoplar funcionalidades comunes, es natural crear librerías compartidas. Pero, ¿cómo garantizas que estas librerías sean compatibles con todas las permutaciones de versiones de Python y frameworks que tus microservicios podrían utilizar? Aquí es donde entra en escena Tox.

Tox es una herramienta de automatización de pruebas que te permite definir y ejecutar tus tests en entornos virtuales aislados para cada una de las versiones de Python y dependencias que especifiques. Por ejemplo, puedes configurar Tox para probar tu librería con Python 2.7 y Django 1.8, Python 2.7 y Django 1.11, Python 3.6 y Django 1.8, y así sucesivamente.

Además, con cada ejecución, Tox instala un entorno virtual desde cero. Esto es crucial porque te obliga a declarar explícitamente todas las dependencias en tu archivo requirements.txt o setup.py. Así, ese "pip install" que hiciste a toda prisa y olvidaste añadir a tus requisitos, te avisará inmediatamente durante las pruebas locales, evitando el clásico "En mi local funciona" que tanto dolor de cabeza causa.

Diseño API-First: La Magia de Connexion y Swagger

En el mundo de los microservicios, la documentación de la API es tan importante como el código mismo. Una de las mejores herramientas para esto es Swagger (ahora conocido como OpenAPI Specification), que incluso proporciona editores online para facilitar su creación. El problema es que, con demasiada frecuencia, los endpoints se programan primero y la documentación se actualiza después (o nunca).

En un ecosistema con decenas o cientos de microservicios, tener la documentación desactualizada es una receta para el caos y los problemas de integración. Aquí es donde Connexion brilla con luz propia, elevando a Flask a la categoría de "Señor de los Microservicios".

La "magia" de Connexion reside en que, leyendo tu documentación en Swagger (en formato YAML o JSON), genera automáticamente los endpoints, las rutas y las validaciones de las definiciones de tus objetos. En esencia, te "obliga" a seguir un diseño de API First. ¿Cómo es esto posible?

Puedes crear el esquema de tu API utilizando el editor online de Swagger, exportar el fichero en formato YAML y añadirlo a tu proyecto Flask. Connexion tomará este archivo y, "mágicamente", creará el esqueleto de tu aplicación. Incluso puedes ir más allá, ya que Swagger Editor te puede generar el proyecto en código Python, dejando solo el trabajo de añadir la lógica de negocio y el origen de los datos.

Veamos un ejemplo. Si tienes un fichero YAML con tu definición Swagger como esta:

paths: /colors/: get: tags: - "colors" summary: "Example endpoint return a list of colors by palette" description: "" operationId: "list_view" consumes: - "application/json" produces: - "application/json" responses: 200: description: "A list of colors" schema: $ref: '#/definitions/Color' 204: description: "Color not found" 405: description: "Validation exception" x-swagger-router-controller: "project.views.views"

Con Connexion, al ejecutar tu aplicación y acceder a http://localhost:5000/colors/, automáticamente buscará el path/colors/ en tu definición del endpoint. Luego, utilizando x-swagger-router-controller: "project.views.views" y operationId: "list_view", Connexion ejecutará la función list_view definida en /project/views/views.py. Además, si incluyes parámetros en tu definición Swagger, Connexion validará que los campos obligatorios se envíen y que el tipado sea correcto, ahorrándote una enorme cantidad de trabajo innecesario en validaciones manuales.

Preparando el Despliegue: Docker y Gunicorn con Gevent

Una vez que tu código está listo, el siguiente paso es prepararlo para el despliegue. En el mundo de los microservicios, Docker se ha convertido en la herramienta indispensable para empaquetar tus aplicaciones en imágenes portables y consistentes, que luego se pueden promocionar a través de diferentes entornos (desarrollo, preproducción, producción). Si aún no estás familiarizado con Docker y los contenedores, este es el momento de ponerte al día, ya que son fundamentales para la orquestación de microservicios.

A diferencia de los despliegues en servidores "tradicionales" donde se suelen usar Nginx o Apache como proxies inversos, dentro de una imagen Docker para Python, utilizaremos Gunicorn. Gunicorn es un servidor WSGI (Web Server Gateway Interface) que se encarga de ejecutar tu aplicación Python con workers, permitiendo levantar múltiples procesos para soportar una mayor carga de peticiones.

Para mejorar aún más el rendimiento y la concurrencia, combinamos Gunicorn con Gevent. Gevent es una librería que permite a tus workers funcionar de manera asíncrona, lo que significa que pueden manejar muchas más conexiones concurrentes sin bloquearse. Con esta combinación, el comando dentro de tu Dockerfile para ejecutar tu aplicación quedaría así:

CMD ["gunicorn", "--worker-class", "gevent", "--workers", "4", "--log-level", "INFO", "--bind", "0.0.0.0:5000", "manage:app"]

Este comando asegura que tu microservicio se ejecute de manera eficiente y escalable dentro de su contenedor Docker.

Orquestación de Microservicios: Kubernetes

Para llevar nuestros microservicios a un entorno de integración continua o producción, la elección recae en Kubernetes. Aunque el "Stack de Netflix" es una opción popular para microservicios, su implementación principal se ha realizado en Spring (Java), lo que obligaría a reinventar o implementar librerías similares para otros lenguajes.

Kubernetes, por otro lado, proporciona un conjunto robusto de funcionalidades esenciales para el manejo y control de una arquitectura basada en microservicios. Ofrece registro de servicios, resiliencia, balanceo de carga, autoescalado y muchas otras capacidades que son cruciales para la operación de sistemas distribuidos. Nos basaremos en Kubernetes para la orquestación de nuestros microservicios, complementando sus capacidades con otras herramientas cuando sea necesario.

Para familiarizarte con Kubernetes de manera local y sin riesgos, recomendamos encarecidamente practicar con Minikube. Este software te permite instalar y trabajar con Kubernetes en tu propia máquina, ideal para experimentar y aprender. Además, Google ha creado una gran cantidad de tutoriales interactivos que simplifican enormemente la introducción a estas potentes herramientas.

Conclusión: El Futuro de los Microservicios en Python

Como hemos podido ver, construir microservicios robustos y eficientes con Python no solo es posible, sino que es una realidad cada vez más viable gracias a un ecosistema de librerías y herramientas en constante evolución. A pesar de que la literatura sobre microservicios en Python puede ser menos abundante en comparación con otros lenguajes, las soluciones que hemos explorado demuestran que Python es una opción poderosa y flexible.

El campo de los microservicios y la contenerización sigue evolucionando rápidamente. Es probable que muchas de las soluciones actuales se perfeccionen o incluso queden obsoletas en pocos años, lo cual es una señal del dinamismo y la innovación en este sector. En futuros artículos, profundizaremos en cómo unir nuestro código con Kubernetes para obtener una visión global de un ecosistema de microservicios completo en Python, compatible a su vez con cualquier otro lenguaje de programación, brindándote las herramientas para un despliegue y gestión exitosos.

Preguntas Frecuentes (FAQ)

¿Por qué se recomienda Flask sobre Django para microservicios en Python?

Flask es preferido por su ligereza y su diseño "menos opinionated", lo que se alinea mejor con el principio de responsabilidad única de los microservicios. Las pruebas de rendimiento demuestran que Flask consume menos memoria y ofrece tiempos de respuesta más rápidos en comparación con Django para operaciones similares, lo cual es crucial para la eficiencia en un entorno de microservicios.

¿Cuál es la función de Flask-SQLAlchemy y cómo simplifica el uso de SQLAlchemy?

Flask-SQLAlchemy es una extensión que simplifica la integración de SQLAlchemy con Flask. Implementa scoped_session, lo que facilita la gestión de sesiones de base de datos y la ejecución de consultas de una manera más familiar para los desarrolladores, haciendo que el potente ORM SQLAlchemy sea más accesible.

¿Cómo ayuda Connexion a mantener la documentación de la API actualizada?

Connexion aplica un enfoque "API First" al generar automáticamente los endpoints, rutas y validaciones de tu microservicio directamente desde tu definición de API en Swagger/OpenAPI (YAML o JSON). Esto "obliga" a la documentación a estar siempre sincronizada con el código, eliminando el problema de la documentación desactualizada.

¿Por qué son importantes Docker, Gunicorn y Gevent para el despliegue de microservicios en Python?

Docker es esencial para empaquetar la aplicación en un contenedor portable y consistente. Gunicorn es un servidor WSGI que ejecuta la aplicación Python con múltiples workers. Gevent se usa con Gunicorn para que los workers funcionen de manera asíncrona, mejorando la concurrencia y el rendimiento del microservicio.

¿Qué papel juega Kubernetes en la orquestación de microservicios y por qué se elige sobre otros stacks?

Kubernetes es un orquestador de contenedores que proporciona funcionalidades clave como el registro de servicios, resiliencia, balanceo de carga y autoescalado, fundamentales para gestionar un ecosistema de microservicios. Se elige sobre stacks como el de Netflix (orientado a Java) porque ofrece una solución agnóstica al lenguaje, permitiendo una mayor flexibilidad y compatibilidad con diversos lenguajes de programación.

Si quieres conocer otros artículos parecidos a Construyendo Microservicios en Python: Guía Esencial puedes visitar la categoría Librerías.

Subir