Template FastAPI
Propósito y Alcance
Este proyecto sirve como un template para desarrollar aplicaciones FastAPI (Python) siguiendo los principios de la Arquitectura Hexagonal (Puertos y Adaptadores). El objetivo es promover una estructura de código desacoplada, modular y mantenible, facilitando la evolución y prueba de las aplicaciones.
Problema que Resuelve: Proporciona una base estructurada para construir servicios backend en Python, separando claramente la lógica de negocio de las preocupaciones de infraestructura (bases de datos, mensajería, APIs externas).
Alcance:
- Implementación de API REST con FastAPI.
- Estructura de proyecto basada en Arquitectura Hexagonal (Dominio, Aplicación, Infraestructura).
- Integración opcional con:
- Base de datos PostgreSQL (con alternativa en memoria).
- Mensajería con Apache Kafka.
- Mensajería con Google Cloud Pub/Sub.
- Configuración para ejecución local y despliegue en GKE y Cloud Run.
- Ejemplos de pruebas unitarias.
- Mecanismo básico de autenticación.
Arquitectura
El template sigue una Arquitectura Hexagonal (o de Puertos y Adaptadores), que organiza el código en tres capas principales:
-
Dominio (
<modulo>/domain):- Contiene los modelos de dominio puros, lógica de negocio central y las interfaces (puertos) para los repositorios y servicios.
- No depende de ninguna otra capa.
- Ejemplos:
app/main/user/domain/model/user.py,app/main/user/domain/repositories/user_repository.py(interface).
-
Aplicación (
<modulo>/application):- Orquesta los casos de uso de la aplicación.
- Implementa las interfaces de servicio definidas en el dominio.
- Utiliza los puertos (interfaces de repositorio) para interactuar con la infraestructura.
- Contiene DTOs (Data Transfer Objects) y lógica de aplicación específica.
- Ejemplos:
app/main/user/application/user_service_impl.py,app/main/user/application/dtos/request_create_dto.py.
-
Infraestructura (
<modulo>/infrastructure):- Contiene las implementaciones concretas de los puertos (adaptadores) y toda la lógica relacionada con tecnologías externas.
- Adaptadores de Entrada (Input Adapters): Exponen la aplicación al mundo exterior (p.ej., controladores API,
consumidores de Kafka/PubSub).
- Ejemplos:
app/main/user/infrastructure/adapter/input/user_controller.py,app/main/user/infrastructure/adapter/input/kafka_receiver.py.
- Ejemplos:
- Adaptadores de Salida (Output Adapters): Implementan la interacción con servicios externos (p.ej.,
repositorios de base de datos, publicadores de Kafka/PubSub).
- Ejemplos:
app/main/user/infrastructure/adapter/output/pg_user_storage.py,app/main/message/infrastructure/kafka.py.
- Ejemplos:
Estructura de Directorios (Ejemplo para módulo user):
main/
└── user/
├── application/
│ ├── config/ # Configuración específica del módulo (ej. conexión DB)
│ ├── dtos/ # Data Transfer Objects
│ ├── entities/ # Entidades de persistencia (si aplica)
│ └── user_service_impl.py # Implementación del caso de uso
├── domain/
│ ├── model/ # Modelos de dominio
│ ├── repositories/ # Interfaces de repositorio (puertos)
│ └── services/ # Interfaces de servicio (puertos)
└── infrastructure/
└── adapter/
├── input/
│ ├── api/ # Controladores API (FastAPI endpoints)
│ └── kafka_receiver.py # Consumidor Kafka
│ └── pubsub_receiver.py# Consumidor PubSub
└── output/
├── pg_user_storage.py # Implementación repositorio PostgreSQL
└── in_memory_user_repository.py # Implementación repositorio en memoria
Tecnologías y Dependencias
- Lenguaje: Python (>=3.11)
- Framework API: FastAPI
- Servidor ASGI: Uvicorn
- Servidor WSGI/ASGI (Producción): Gunicorn
- Gestión de Dependencias: Pipenv, pip
- Base de Datos (Opcional): PostgreSQL (con SQLAlchemy como ORM)
- Mensajería (Opcional):
- Apache Kafka (con
confluent-kafka) - Google Cloud Pub/Sub (con
google-cloud-pubsub)
- Apache Kafka (con
- Pruebas: Pytest, Coverage.py
- Autenticación: Google OAuth2 Client (para validación de tokens)
- Contenerización: Docker
- CI/CD: Jenkins, GitLab CI
- Orquestación (Despliegue): Google Kubernetes Engine (GKE), Google Cloud Run
Listado de dependencias principales (ver requirements.txt para la lista completa):
fastapi
uvicorn
gunicorn
pipenv
# Para PostgreSQL
sqlalchemy
psycopg2-binary
# Para Kafka
confluent-kafka
# Para Pub/Sub
google-cloud-pubsub
# Para Auth
google-auth
google-api-python-client
# Para Pruebas
pytest
coverage
Configuración Local
Prerrequisitos
- Python 3.11 o superior.
pipinstalado.- IDE recomendado: Visual Studio Code.
- (Opcional) Docker y Docker Compose para dependencias como PostgreSQL, Kafka.
- (Opcional) Acceso a servicios GCP si se usan Pub/Sub o Kafka en Confluent Cloud y se quiere probar contra ellos.
Pasos de Configuración
-
Clonar el repositorio: Sigue los siguientes pasos
-
Instalar Pipenv:
pip install pipenv -
Activar el entorno virtual:
pipenv shellAsegúrate de que tu IDE (VS Code) utilice este entorno virtual. Usualmente se encuentra en
~/.virtualenvs/template_fast_api-XXXXXX. -
Instalar dependencias del proyecto:
pipenv install -r requirements.txtEsto instalará las dependencias listadas en
requirements.txty creará/actualizaráPipfileyPipfile.lock. -
Crear archivo
.env: En la raíz del proyecto, crea un archivo.envcon la siguiente estructura básica. Descomenta y configura las secciones según los componentes que vayas a utilizar.ENVIRONMENT=devDEBUG=true # o false# Configuración Base de Datos (PostgreSQL) - OpcionalDATABASE_USERNAME=postgresDATABASE_PASSWORD=postgresDATABASE_NAME=hexaDATABASE_HOST=localhostDATABASE_PORT="5432"SCHEMA_NAME=public# Configuración Kafka (Confluent Cloud o Local) - Opcional# Ejemplo Confluent Cloud:BOOTSTRAP_SERVER=pkc-xxxx.xxxx.gcp.confluent.cloud:9092CLUSTER_API_KEY=TU_KAFKA_API_KEYCLUSTER_API_SECRET=TU_KAFKA_API_SECRET# Ejemplo Local:# BOOTSTRAP_SERVER=localhost:9092TOPIC_RECIEVER=tu_topic_kafka_consumidor # Topic que la app consumeTOPIC_PRODUCER=tu_topic_kafka_productor # Topic donde la app produce (configurado en kafka.py)RETRIES=10CONSUMER_GROUP_ID=tu_grupo_consumidor_kafka# Configuración Google Cloud Pub/Sub - Opcional# Usado por PubsubConsumerPROJECT_ID=tu-gcp-project-idSUBSCRIPTION_ID=tu-pubsub-subscription-id-para-consumir# Usado por PubsubNotifier (main.py) - Asegúrate que coincidan o ajusta según necesidadPUBSUB_PROJECT_ID=tu-gcp-project-id # Puede ser el mismo que PROJECT_IDPUBSUB_TOPIC_ID=tu-pubsub-topic-id-para-producir# GOOGLE_APPLICATION_CREDENTIALS=/ruta/a/tus/credenciales-gcp.json # Necesario si no usas ADC# Configuración Autenticación (Google OAuth2) - OpcionalGOOGLE_CLIENT_ID=tu-google-client-id.apps.googleusercontent.com# Configuración LoggingLOGGING_SEVERITY="DEBUG"Nota: El archivo
.envestá incluido en.gitignorey no debe ser versionado. Para Kafka y Pub/Sub, si no tienes instancias locales, puedes usar emuladores o servicios en la nube. -
Ejecutar la aplicación localmente:
uvicorn main:app --reload --port 8080O usando el script principal si está configurado para
uvicorn.run:python main.pyLa aplicación estará disponible en
http://localhost:8080. La documentación Swagger UI enhttp://localhost:8080/api/hexa/docs.
Nuevas Dependencias
Para instalar nuevas dependencias:
pipenv install <nombre_dependencia>
Para actualizar requirements.txt a partir de Pipfile.lock (para mantener consistencia con Dockerfile u otros
setups):
pipenv run pip freeze > requirements.txt
Despliegue
El proyecto está configurado para desplegarse en Google Kubernetes Engine (GKE) o Google Cloud Run.
Despliegue en GKE
- Utiliza el
Jenkinsfilecon el pipelinegke-krane-python:pipelineLatam("gke-krane-python") - La configuración del despliegue (Deployment, Service, etc.) se encuentra en
deploy/gke/deployment.yaml.erb. - Las variables de entorno específicas para cada ambiente (dev, intg, prod) se gestionan a través de ConfigMaps y
Secrets en Kubernetes, populadas desde
deploy/env/<ambiente>.yaml.
Despliegue en Cloud Run
- Utiliza el
.gitlab-ci.ymlque incluye el componente de pipeline para Cloud Run:include:- component: $LATAM_PIPELINE_CLOUD_RUN_DEPLOY - La configuración del servicio de Cloud Run se encuentra en
deploy/run/runtime-config.yaml.erb. - Las variables de entorno se definen en
deploy/run/environment.yaml.erby se basan en los archivosdeploy/env/<ambiente>.yaml.
Variables de Entorno Comunes para Despliegue
Consultar los archivos en deploy/env/ para las variables específicas de cada entorno, como:
PROJECT_IDCLOUD_RUN_REGION- Credenciales de base de datos (usualmente inyectadas como secrets)
- Configuración de Kafka/PubSub (endpoints, topics, credenciales como secrets)
GOOGLE_CLIENT_IDpara autenticación.
Endpoints de la API
La API base se encuentra en /api/hexa. Todos los endpoints del módulo user requieren un header uuid.
| Método | Endpoint | Autenticación | Descripción | Ejemplo Cuerpo Solicitud (JSON) | Ejemplo Respuesta (Éxito) |
|---|---|---|---|---|---|
GET | /api/hexa/how-use-it | Ninguna | Devuelve enlace a la documentación del template | N/A | "https://docsrepo.appslatam.com/display/E2EDEV/Python+Template+FastAPI" (texto plano) |
POST | /api/hexa/user/registration | Header uuid | Registra un nuevo usuario. | {"username": "newuser", "email": "new@example.com"} | {"status_code": 200, "data": "user created"} |
POST | /api/hexa/user/get_user | Header uuid | Obtiene un usuario por nombre de usuario. | {"username": "testuser", "email": "ignored@example.com"} | {"status_code": 200, "data": "{'username': 'testuser', 'email': 'test@example.com'}"} |
Autenticación General (Opcional):
El sistema incluye un validador de tokens Google OAuth2 en app/main/auth/infrastructure/auth.py. Si se activa en los
endpoints (usando Depends(validate_token)), se requerirá un header Authorization: Bearer <token_id_google>.
Actualmente, los endpoints de user_controller solo dependen del header uuid.
Ejemplos de Respuestas de Error para /registration y /get_user:
- Usuario ya existe (
/registration):{"status_code": 201, "data": "username already exists"} - Validación fallida (
/registration):{"status_code": 202, "data": "username must not contain digits"} - Usuario no encontrado (
/get_user):{"status_code": 404, "data": "user not found"} - Header
uuidfaltante/inválido:400 Bad Requestcon detalle.
Componentes Específicos
Base de Datos (PostgreSQL / En Memoria)
- Descripción: El template permite usar una base de datos PostgreSQL o una implementación en memoria para persistencia.
-
Selección: En
main.py, se selecciona el repositorio a usar:# Para PostgreSQL# from app.main.user.infrastructure.adapter.output.pg_user_storage import PgUserStorage# repository = PgUserStorage()# Para En Memoria (por defecto en el template base)from app.main.user.infrastructure.adapter.output.in_memory_user_repository import InMemoryUserRepositoryrepository = InMemoryUserRepository()Cambiar las líneas comentadas para elegir la implementación.
-
Configuración (PostgreSQL - Local
/.env):DATABASE_USERNAME=postgresDATABASE_PASSWORD=postgresDATABASE_NAME=hexaDATABASE_HOST=localhostDATABASE_PORT="5432"SCHEMA_NAME=public -
Implementación:
- PostgreSQL:
app/main/user/infrastructure/adapter/output/pg_user_storage.py,app/main/user/application/config/pg_connection.py,app/main/user/application/entities/user_entity.py. - En Memoria:
app/main/user/infrastructure/adapter/output/in_memory_user_repository.py.
- PostgreSQL:
-
Deshabilitar PostgreSQL: Comentar la inicialización de
PgUserStoragey asegurarse que se usaInMemoryUserRepository. No se necesitarían las dependenciassqlalchemyypsycopg2-binarysi no se usa.
Kafka
-
Descripción: Integración con Apache Kafka para producción y consumo de mensajes.
-
Configuración (Local
/.env):# Ejemplo Confluent Cloud:BOOTSTRAP_SERVER=pkc-xxxx.xxxx.gcp.confluent.cloud:9092CLUSTER_API_KEY=TU_KAFKA_API_KEYCLUSTER_API_SECRET=TU_KAFKA_API_SECRET# Ejemplo Local:# BOOTSTRAP_SERVER=localhost:9092TOPIC_RECIEVER=tu_topic_kafka_consumidor # Para KafkaConsumer# Para KafkaNotifier (hardcodeado en kafka.py, ajustar si es necesario)TOPIC_PRODUCER=tmpl_arq-dev-topic-test_topic_producerRETRIES=10 # Para productorCONSUMER_GROUP_ID=tu_grupo_consumidor_kafka # Para KafkaConsumerLas configuraciones se leen en
app/main/message/application/configurations.py. -
Implementación:
- Productor:
app/main/message/infrastructure/kafka.py(claseKafkaNotifier). - Consumidor:
app/main/user/infrastructure/adapter/input/kafka_receiver.py(claseKafkaConsumer). - El consumidor se inicia en
main.pydurante el ciclo de vida de la aplicación. El notificador se inyecta enUserServiceUserImpl.
- Productor:
-
Deshabilitar:
-
Comentar o eliminar las siguientes secciones en
main.py:# {{ KAFKA }}# from app.main.message.infrastructure.kafka import KafkaNotifier# from app.main.user.infrastructure.adapter.input.kafka_receiver import KafkaConsumer# {{! KAFKA }}# ...# {{ KAFKA }}# notifier = KafkaNotifier() # Si Kafka es el único notificador activo# {{! KAFKA }}# ...# {{ KAFKA }}# kafka_consumer = KafkaConsumer()# {{! KAFKA }}# En app_lifespan:# {{ KAFKA }}# await asyncio.create_task(KafkaConsumer.receive_user(user_service))# {{! KAFKA }} -
Si
KafkaNotifieres el notificador asignado auser_service, asignarNoneo otro notificador. -
Eliminar la dependencia
confluent-kafkaderequirements.txtyPipfile.
-
Google Cloud Pub/Sub
-
Descripción: Integración con Google Cloud Pub/Sub para producción y consumo de mensajes.
-
Configuración (Local
/.env):# Usado por PubsubConsumer (configurations.py)PROJECT_ID=tu-gcp-project-idSUBSCRIPTION_ID=tu-pubsub-subscription-id-para-consumir# Usado por PubsubNotifier (main.py)PUBSUB_PROJECT_ID=tu-gcp-project-id # Puede ser el mismo que PROJECT_IDPUBSUB_TOPIC_ID=tu-pubsub-topic-id-para-producir# Autenticación GCP (localmente)GOOGLE_APPLICATION_CREDENTIALS=/ruta/a/tus/credenciales-gcp.jsonLa configuración del consumidor se lee en
app/main/message/application/configurations.py. El productor se configura directamente enmain.py. Nota:PUBSUB_PROJECT_IDyPUBSUB_TOPIC_IDenmain.pyestán como"${{...}}". Para ejecución local, deben ser reemplazados porenviron.get("PUBSUB_PROJECT_ID")o proporcionar valores directamente enmain.pysi no se usa templating.
- Implementación:
- Productor:
app/main/message/infrastructure/pubsub.py(clasePubsubNotifier). - Consumidor:
app/main/user/infrastructure/adapter/input/pubsub_receiver.py(clasePubsubConsumer). - El consumidor se inicia en
main.pydurante el ciclo de vida de la aplicación. El notificador se inyecta enUserServiceUserImpl.
- Productor:
-
Deshabilitar:
-
Comentar o eliminar las siguientes secciones en
main.py:# {{ PUBSUB }}# from app.main.message.infrastructure.pubsub import PubsubNotifier# from app.main.user.infrastructure.adapter.input.pubsub_receiver import PubsubConsumer# {{! PUBSUB }}# ...# {{ PUBSUB }}# notifier = PubsubNotifier(environ.get("PUBSUB_PROJECT_ID"), environ.get("PUBSUB_TOPIC_ID")) # Si Pub/Sub es el único notificador activo# {{! PUBSUB }}# ...# {{ PUBSUB }}# pubsub_consumer = PubsubConsumer()# {{! PUBSUB }}# En app_lifespan:# {{ PUBSUB }}# await asyncio.create_task(PubsubConsumer.receive_user(user_service))# {{! PUBSUB }} -
Si
PubsubNotifieres el notificador asignado auser_service, asignarNoneo otro notificador. -
Eliminar la dependencia
google-cloud-pubsubderequirements.txtyPipfile.
-
Pruebas
El proyecto utiliza pytest para pruebas unitarias y coverage para medir la cobertura de código.
Ejecutar Pruebas Unitarias
Desde la raíz del proyecto, dentro del entorno virtual (pipenv shell):
python -m pytest -v --junitxml=reports/result.xml
-v: Modo verbose.--junitxml=reports/result.xml: Genera un reporte en formato JUnit XML en la carpetareports.
Validar Cobertura (Coverage)
-
Ejecutar pruebas con coverage: Se puede integrar
coverageconpytesto ejecutarlo por separado. Para ejecutar conpytest-cov(si está instalado):pipenv run pytest --cov=app --cov-report=xml --cov-report=htmlO usando
coveragedirectamente:pipenv run coverage run -m pytest -
Ver reporte de cobertura en consola:
pipenv run coverage report -
Generar reporte HTML de cobertura:
pipenv run coverage htmlEsto creará una carpeta
htmlcov/con el reporte navegable.
Los archivos de prueba se encuentran en el directorio app/tests/.
Consideraciones de Seguridad
- Gestión de Secretos:
- Localmente: Utilizar el archivo
.envpara almacenar credenciales y claves sensibles. Este archivo está incluido en.gitignorey NO debe ser subido al repositorio. - Despliegue (GKE/Cloud Run): Los secretos (como contraseñas de base de datos, API keys de Kafka/PubSub,
GOOGLE_CLIENT_ID) deben gestionarse a través de los mecanismos de secretos del entorno (ej. Kubernetes Secrets, Google Secret Manager) y ser inyectados como variables de entorno en los contenedores. Verdeploy/gke/deployment.yaml.erbpara ejemplos de cómo se referencian secrets.
- Localmente: Utilizar el archivo
- Autenticación de API:
- Header
uuid: Los endpoints del módulouseractualmente requieren un headeruuidno vacío. Su propósito y validación más allá de la existencia no están especificados y deberían ser definidos según los requisitos de seguridad. - Token Google OAuth2: El archivo
app/main/auth/infrastructure/auth.pycontiene lógica para validar tokens ID de Google. Requiere la variable de entornoGOOGLE_CLIENT_ID. Esta validación puede ser agregada como una dependencia (Depends(validate_token)) a los endpoints de FastAPI que necesiten ser protegidos.
- Header
- Archivos No Versionados: Asegúrate de no versionar los siguientes archivos y directorios que pueden contener
información sensible o específica del entorno:
htmlcov/reports/.envPipfile(si contiene información sensible, aunque usualmente no lo hace)Pipfile.lock(por las mismas razones que Pipfile).coverage