Proyectos
Backend · Clean Architecture2025·Dificultad

Servicio de jobs de IA · Clean Architecture

Backend en FastAPI para crear, ejecutar, consultar y cancelar jobs de IA, construido con Clean Architecture (dominio aislado, casos de uso, puertos y adapters). Demuestra criterio de arquitectura, no escala distribuida.

HE
Harold Eustaquio
AI & Data Engineer
Código fuente
Stack
PythonuvFastAPIPydanticasyncpgPostgreSQLDockerpytest
Arquitectura
Mini Map
El cliente entra por app/api, que delega en los casos de uso; estos aplican las reglas del dominio y dependen de puertos que la infraestructura implementa (memoria o Postgres). Las dependencias apuntan siempre hacia adentro.
  1. Cliente HTTP — curl, Swagger UI o cualquier cliente contra los endpoints REST.
  2. app/api — Routes y schemas FastAPI + dependencias; traduce HTTP a casos de uso.
  3. Casos de uso — JobService, Runner y Worker: orquestan el flujo contra los puertos.
  4. Dominio — Job + JobStatus y las transiciones válidas; sin dependencias externas.
  5. Puertos — JobRepository y JobExecutor como Protocols; la frontera de inversión de dependencias.
  6. Infraestructura — Adapters: repositorio en memoria, repositorio Postgres y DemoJobExecutor.
  7. Postgres — Persistencia opcional vía asyncpg con migraciones SQL idempotentes.
Contexto

Un portafolio suele acumular notebooks y scripts sueltos; falta una pieza que muestre cómo se estructura un backend mantenible. Quería un proyecto deliberadamente pequeño cuyo valor estuviera en los límites entre capas, no en la cantidad de features.

Problema

Modelar el ciclo de vida de un job de IA (encolar, ejecutar, consultar, cancelar) con reglas de dominio claras, manteniendo la lógica de negocio independiente de FastAPI, SQL y de cualquier SDK externo.

Solución

Arquitectura por capas con dependencias hacia adentro: el dominio define el Job y sus transiciones de estado; la capa de aplicación expone casos de uso contra puertos (JobRepository, JobExecutor); la infraestructura provee dos adapters de persistencia (memoria y Postgres) y un executor demo. Un runtime central compone las dependencias y un worker procesa los jobs pendientes una vez.

Decisiones de diseño
  • Mantener app/core/domain libre de FastAPI, Pydantic, SQL y SDKs: la regla de negocio no conoce a sus consumidores.
  • Definir puertos (JobRepository, JobExecutor) como Protocols para desacoplar los casos de uso de Postgres y de los executors.
  • Implementar dos adapters de persistencia (memoria y Postgres) para correr la API sin infraestructura y probar el contrato del repositorio.
  • Modelar el job como máquina de estados (QUEUED → RUNNING → SUCCEEDED/FAILED/CANCELED) con invariantes en el dominio.
  • Componer dependencias en un runtime central y usar migraciones SQL idempotentes en vez de un ORM.
  • Worker demo de una sola pasada (sin broker ni loop infinito): demuestra la separación API / casos de uso / puertos sin montar una plataforma distribuida.
Resultados
  • Dominio totalmente aislado: app/core/domain no importa FastAPI, Pydantic, SQL ni Docker.
  • API ejecutable sin infraestructura (repositorio en memoria) y con Postgres en Docker mediante un solo runtime.
  • Suite de pruebas (unitarias e integración) con ruff y basedpyright sobre todo el código.
  • Ciclo de vida de jobs completo: crear, ejecutar, consultar y cancelar, con un worker demo de una pasada.
Lecciones aprendidas

Un proyecto chico es suficiente para mostrar criterio de arquitectura si los límites están bien puestos: el valor está en qué no depende de qué. El siguiente paso natural es reemplazar el executor demo por una llamada real a un modelo y el worker de una pasada por un consumidor de cola.