Repository with projects especially focused on Computer Science and Natural Sciences.
📦jorge_cardona_project [project_directory]
┗ 📂deployment [package]
┗ 📂requirements [package]
┗ 📂test [package]
┗ 📜 README.md
┗ ⚠️ .gitignore
┗ 📂application [package]
┃ ┣ 📂configuration
┃ ┣ 📂log
┃ ┣ 📂htmlcov
┃ ┣ 📂utils
┃ ┣ 📂domain [package]
┃ ┃ ┣ 📂entities [package]
┃ ┃ ┣ 📂interfaces [package]
┃ ┃ ┃ ┗ 📂repositories [package]
┃ ┃ ┃ ┗ 📂business_logic [package]
┃ ┃ ┣ 📂usecases [package]
┃ ┃ ┣ 📂services [package]
┃ ┣ main.py [__main__]
📦jorge_cardona_project [project_directory]
┗ 📂application [package]
┃ ┣ 🎄 main.py [__main__]
┃ ┣ 📂configuration
┃ ┣ ┗ 📂 rest
┃ ┃ ┣ ┗ 🏩 app_configuration.py
┃ ┣ ┗ 📂 environment
┃ ┃ ┣ ┗ 📡 environment_configuration.py
┃ ┣ ┗ 📂 database
┃ ┃ ┣ ┗ 🔑 database_configuration.py
┃ ┣ ┗ 📂 log
┃ ┃ ┣ ┗ 📜 log_configuration.py
┃ ┣ ┗ 📂 cors
┃ ┃ ┣ ┗ 🚧 cors_configuration.py
┃ ┣ ┗ 📂 swagger
┃ ┃ ┣ ┗ 📪 swagger_configuration.py
┃ ┣ 📂log
┃ ┣ ┗ 💬 logging.log
┃ ┣ 📂htmlcov
┃ ┣ ┗ 📜 main_py.html
┃ ┣ 📂utils
┃ ┣ ┗ 🐍 script.py
┃ ┣ ┗ 🎰 file.yaml
┃ ┣ ┗ 📜 image.jpg
┃ ┣ 📂domain [package]
┃ ┃ ┣ 📂entities [package]
┃ ┃ ┃ ┣ 📂entity_one
┃ ┃ ┃ ┃ ┗ 🐍 model_for_entity_ONE.py
┃ ┃ ┃ ┃ ┗ 💦 schema_for_entity_ONE.py
┃ ┃ ┃ ┣ 📂entity_two
┃ ┃ ┃ ┃ ┗ 🐍 model_for_entity_TWO.py
┃ ┃ ┃ ┃ ┗ 💦 schema_for_entity_TWO.py
┃ ┃ ┃ ┣ 📂entity_n
┃ ┃ ┃ ┃ ┗ 🐍 model_for_entity_N.py
┃ ┃ ┃ ┃ ┗ 💦 schema_for_entity_N.py
┃ ┃ ┣ 📂interfaces [package]
┃ ┃ ┃ ┗ 📂repositories [package]
┃ ┃ ┃ ┃ ┃ ┗ 🐟 database_method_model_Entity_ONE.py
┃ ┃ ┃ ┃ ┃ ┗ 🐟 database_method_model_Entity_TWO.py
┃ ┃ ┃ ┃ ┃ ┗ 🐟 database_method_model_Entity_N.py
┃ ┃ ┃ ┗ 📂business_logic [package]
┃ ┃ ┃ ┃ ┃ ┗ 🐦 business_logic_method_model_Entity_ONE.py
┃ ┃ ┃ ┃ ┃ ┗ 🐦 business_logic_method_model_Entity_TWO.py
┃ ┃ ┃ ┃ ┃ ┗ 🐦 business_logic_method_model_Entity_N.py
┃ ┃ ┣ 📂usecases [package]
┃ ┃ ┃ ┃ ┗ 🎎 use_case_implementation_business_repository_logic_model_ONE.py
┃ ┃ ┃ ┃ ┗ 🎎 use_case_implementation_business_repository_logic_model_TWO.py
┃ ┃ ┃ ┃ ┗ 🎎 use_case_implementation_business_repository_logic_model_N.py
┃ ┃ ┣ 📂services [package]
┃ ┃ ┃ ┃ ┗ ✈️ services_use_case_implementation_model_ONE.py
┃ ┃ ┃ ┃ ┗ ✈️ services_use_case_implementation_model_TWO.py
┃ ┃ ┃ ┃ ┗ ✈️ services_use_case_implementation_model_N.py
┗ 📂deployment [package]
┃ ┗ 🐳 Dockerfile
┃ ┗ 🎰 Manifest.yaml
┗ 📂requirements [package]
┃ ┗ 📄 requirements.txt
┗ 📂test [package]
┃ ┃ ┣ 📂test_case_one
┃ ┃ ┃ ┗ 🍄 use_case_implementation_one.py
┃ ┃ ┃ ┗ 🍄 services_use_case_implementation_one.py
┃ ┃ ┣ 📂test_case_two
┃ ┃ ┃ ┗ 🍄 use_case_implementation_two.py
┃ ┃ ┃ ┗ 🍄 services_use_case_implementation_two.py
┃ ┃ ┣ 📂test_case_n
┃ ┃ ┃ ┗ 🍄 use_case_implementation_n.py
┃ ┃ ┃ ┗ 🍄 services_use_case_implementation_n.py
┗ 📜 README.md
┗ ⚠️ .gitignore
Repository with projects especially focused on Computer Science and Natural Sciences.
Este proyecto es una plantilla base para construir microservicios en Python siguiendo los principios de Arquitectura Limpia. Su objetivo es mantener la lógica de negocio aislada de tecnologías externas (frameworks, bases de datos, APIs), promoviendo un diseño desacoplado, testable y mantenible.
- Independencia de frameworks: el dominio y los casos de uso no dependen de frameworks web, librerías ORM ni herramientas externas.
- Separación de responsabilidades: cada carpeta y módulo cumple una función específica.
- Inversión de dependencias: las capas externas dependen del dominio, nunca al revés.
- Testabilidad: se pueden probar casos de uso y lógica de negocio sin necesidad de ejecutar la infraestructura.
📦jorge_cardona_project [project_directory]
┣ 📂 application [package]
┃ ┣ 📂 configuration [package] ← Configuración de entorno (puertos, URIs, logging, settings)
┃ ┣ 📂 utils [package] ← Funciones auxiliares generales
┃ ┣ 📂 domain [package] ← Núcleo de negocio (arquitectura limpia)
┃ ┃ ┣ 📂 entities [package] ← Modelos puros con @dataclass
┃ ┃ ┣ 📂 interfaces [package] ← Contratos abstractos
┃ ┃ ┃ ┣ 📂 repositories [package] ← Interfaces de persistencia (ej. UserRepository)
┃ ┃ ┃ ┗ 📂 business_logic [package] ← Interfaces de lógica externa (ej. EmailSender)
┃ ┃ ┣ 📂 usecases [package] ← Casos de uso del sistema
┃ ┃ ┣ 📂 services [package] ← Servicios del dominio (reglas reutilizables)
┃ ┗ 📄 main.py [executable] ← Punto de entrada (__main__)
┣ 📂 deployment [package] ← Docker, Kubernetes y scripts de infraestructura
┣ 📂 logs [directory] ← Archivos de log generados (no debe ser package)
┣ 📂 reports [directory] ← Reportes de cobertura, calidad u otros artefactos
┣ 📂 requirements [package] ← Organización de requirements por entorno (dev, prod, test)
┣ 📂 test [package] ← Pruebas unitarias y mocks
┃ ┣ 📂 usecases [package]
┃ ┣ 📂 mock [package]
┃ ┗ 📂 integration [package]
┣ 📄 README.md ← Documentación del proyecto
┣ ⚠️ .gitignore ← Exclusión de carpetas como `logs/`, `__pycache__/`, etc.
📦 jorge_cardona_project [project_directory]
┣ 📂 application [package] ← Paquete principal del microservicio
┃ ┣ 📄 __init__.py ← Permite importar todo `application` como módulo raíz
┃ ┣ 🎄 main.py [__main__] ← Punto de entrada. Orquesta la app.
┃ ┣ 📂 configuration [package] ← Configuración general separada por dominio
┃ ┃ ┣ 📄 __init__.py ← Expone todas las subconfiguraciones como módulo único
┃ ┃ ┣ 📂 rest [package] ← Config de servidor HTTP/API
┃ ┃ ┃ ┣ 📄 __init__.py ← Permite importar configuración del servidor REST
┃ ┃ ┃ ┗ 🏩 app_configuration.py
┃ ┃ ┣ 📂 environment [package] ← Config por entorno
┃ ┃ ┃ ┣ 📄 __init__.py ← Expone utilidades de entorno como módulo único
┃ ┃ ┃ ┗ 📡 environment_configuration.py
┃ ┃ ┣ 📂 database [package] ← Configuración de conexión a base de datos
┃ ┃ ┃ ┣ 📄 __init__.py ← Permite importar configuración DB como módulo
┃ ┃ ┃ ┗ 🔑 database_configuration.py
┃ ┃ ┣ 📂 log [package] ← Configuración del logger
┃ ┃ ┃ ┣ 📄 __init__.py ← Agrupa y expone la config de logs
┃ ┃ ┃ ┗ 📜 log_configuration.py
┃ ┃ ┣ 📂 cors [package] ← Configuración de CORS
┃ ┃ ┃ ┣ 📄 __init__.py ← Permite usar la configuración CORS desde `main.py`
┃ ┃ ┃ ┗ 🚧 cors_configuration.py
┃ ┃ ┣ 📂 swagger [package] ← Configuración de documentación interactiva
┃ ┃ ┃ ┣ 📄 __init__.py ← Habilita uso desde `configuration.swagger`
┃ ┃ ┃ ┗ 📪 swagger_configuration.py
┃ ┣ 📂 utils [package] ← Helpers generales (fechas, formatos, etc.)
┃ ┃ ┣ 📄 __init__.py ← Permite importar helpers directamente desde `utils`
┃ ┃ ┣ 🐍 script.py
┃ ┃ ┣ 🎰 file.yaml
┃ ┃ ┗ 📜 image.jpg ← ⚠️ Evitar binarios aquí
┃ ┣ 📂 domain [package] ← Núcleo de la lógica de negocio (arquitectura limpia)
┃ ┃ ┣ 📄 __init__.py ← Permite importar `entities`, `usecases`, `services`, etc.
┃ ┃ ┣ 📂 entities [package] ← 📌 Modelos puros del dominio
┃ ┃ ┃ ┣ 📄 __init__.py ← Expone entidades agrupadas
┃ ┃ ┃ ┣ 📂 entity_one [package]
┃ ┃ ┃ ┃ ┣ 📄 __init__.py ← Permite importar entidad y schema 1
┃ ┃ ┃ ┃ ┣ 🐍 model_for_entity_ONE.py
┃ ┃ ┃ ┃ ┗ 💦 schema_for_entity_ONE.py
┃ ┃ ┃ ┣ 📂 entity_two [package]
┃ ┃ ┃ ┃ ┣ 📄 __init__.py ← Permite importar entidad y schema 2
┃ ┃ ┃ ┃ ┣ 🐍 model_for_entity_TWO.py
┃ ┃ ┃ ┃ ┗ 💦 schema_for_entity_TWO.py
┃ ┃ ┃ ┣ 📂 entity_n [package]
┃ ┃ ┃ ┃ ┣ 📄 __init__.py ← Permite importar entidad y schema N
┃ ┃ ┃ ┃ ┣ 🐍 model_for_entity_N.py
┃ ┃ ┃ ┃ ┗ 💦 schema_for_entity_N.py
┃ ┃ ┣ 📂 interfaces [package] ← 📌 Contratos abstractos que el dominio necesita
┃ ┃ ┃ ┣ 📄 __init__.py ← Exposición conjunta de `repositories` y `business_logic`
┃ ┃ ┃ ┣ 📂 repositories [package] ← Interfaces de persistencia
┃ ┃ ┃ ┃ ┣ 📄 __init__.py ← Expone métodos CRUD abstractos
┃ ┃ ┃ ┃ ┣ 🐟 database_method_model_Entity_ONE.py
┃ ┃ ┃ ┃ ┣ 🐟 database_method_model_Entity_TWO.py
┃ ┃ ┃ ┃ ┗ 🐟 database_method_model_Entity_N.py
┃ ┃ ┃ ┣ 📂 business_logic [package] ← Interfaces para servicios externos
┃ ┃ ┃ ┃ ┣ 📄 __init__.py ← Permite importar lógica externa desacoplada
┃ ┃ ┃ ┃ ┣ 🐦 business_logic_method_model_Entity_ONE.py
┃ ┃ ┃ ┃ ┣ 🐦 business_logic_method_model_Entity_TWO.py
┃ ┃ ┃ ┃ ┗ 🐦 business_logic_method_model_Entity_N.py
┃ ┃ ┣ 📂 usecases [package] ← 📌 Casos de uso que orquestan el negocio
┃ ┃ ┃ ┣ 📄 __init__.py ← Expone todos los casos de uso desde un punto común
┃ ┃ ┃ ┣ 🎎 use_case_implementation_business_repository_logic_model_ONE.py
┃ ┃ ┃ ┣ 🎎 use_case_implementation_business_repository_logic_model_TWO.py
┃ ┃ ┃ ┗ 🎎 use_case_implementation_business_repository_logic_model_N.py
┃ ┃ ┣ 📂 services [package] ← 📌 Lógica compartida entre casos de uso
┃ ┃ ┃ ┣ 📄 __init__.py ← Importación de helpers reutilizables
┃ ┃ ┃ ┣ ✈️ services_use_case_implementation_model_ONE.py
┃ ┃ ┃ ┣ ✈️ services_use_case_implementation_model_TWO.py
┃ ┃ ┃ ┗ ✈️ services_use_case_implementation_model_N.py
┣ 📂 deployment [package] ← Despliegue y empaquetado
┃ ┣ 📄 __init__.py ← Permite importar scripts de despliegue como módulo
┃ ┣ 🐳 Dockerfile
┃ ┗ 🎰 Manifest.yaml
┣ 📂 logs [directory] ← Carpeta para logs generados
┃ ┣ 📝 app.log
┃ ┣ 📝 db.log
┃ ┗ 📂 archive [directory]
┃ ┗ 📝 app-2024-01-01.log
┣ 📂 reports [directory] ← Reportes generados automáticamente
┃ ┣ 📄 coverage.html
┃ ┗ 📂 htmlcov [directory]
┃ ┗ 📜 main_py.html
┣ 📂 requirements [package] ← Manejo separado de dependencias
┃ ┣ 📄 __init__.py ← Permite importar requirements como módulo si se usa dinámicamente
┃ ┣ 📄 base.txt
┃ ┣ 📄 dev.txt
┃ ┣ 📄 test.txt
┃ ┗ 📄 requirements.txt
┣ 📂 test [package] ← Estructura de pruebas
┃ ┣ 📄 __init__.py ← Expone test como módulo para ejecución e importación
┃ ┣ 📂 test_case_one [package]
┃ ┃ ┣ 📄 __init__.py ← Inicializa caso de prueba 1 como módulo
┃ ┃ ┣ 🍄 use_case_implementation_one.py
┃ ┃ ┗ 🍄 services_use_case_implementation_one.py
┃ ┣ 📂 test_case_two [package]
┃ ┃ ┣ 📄 __init__.py
┃ ┃ ┣ 🍄 use_case_implementation_two.py
┃ ┃ ┗ 🍄 services_use_case_implementation_two.py
┃ ┣ 📂 test_case_n [package]
┃ ┃ ┣ 📄 __init__.py
┃ ┃ ┣ 🍄 use_case_implementation_n.py
┃ ┃ ┗ 🍄 services_use_case_implementation_n.py
┣ 📜 README.md ← Documentación general del proyecto
┣ ⚠️ .gitignore ← Ignora `/logs/`, `__pycache__/`, `htmlcov/`, etc.
Este directorio encapsula todo el código fuente del microservicio. Implementa la arquitectura limpia dividiendo la aplicación en capas bien definidas: configuration, logging, utils, domain, y el punto de entrada (main.py).
| Parte del sistema | ¿Qué hace? |
|---|---|
entities/model_for_entity_*.py |
✅ Define la estructura interna de los datos. Son clases con @dataclass que representan tablas reales de la base de datos. Sin lógica ni validaciones. |
entities/schema_for_entity_*.py |
✅ Define los esquemas de entrada y salida que usa la API. Escritos con pydantic, validan datos de entrada y formatean la salida al cliente. |
interfaces/ |
✅ Declara contratos abstractos que el dominio necesita para acceder a datos (repositories/) o servicios externos (business_logic/). No contienen lógica. |
interfaces/repositories/ |
✅ Define interfaces para persistencia: guardar(), buscar_por_id(), actualizar(), etc. Sólo firmas de métodos. Sin implementación. |
interfaces/business_logic/ |
✅ Define interfaces para lógica externa (email, autenticación, notificaciones, pagos, etc.). Declaración de métodos que otros deben implementar. |
usecases/ |
✅ Contiene la lógica del negocio puro. Cada clase representa un flujo funcional (crear, editar, eliminar) y usa las interfaces del dominio. |
| 🧠 Aquí también se implementan los métodos definidos por las interfaces y se escribe la lógica propia del negocio: | |
| filtros, transformaciones, decisiones, validaciones del dominio, cálculos, restricciones de uso. No contiene lógica técnica ni externa. | |
services/ |
✅ Define los endpoints HTTP reales. Aquí se reciben las peticiones, se validan usando schemas, se llama a los usecases, y se retorna la respuesta al cliente. |
main.py |
✅ Punto de entrada. Registra los endpoints desde services/, configura la aplicación y conecta los usecases con sus dependencias reales. |
configuration/ |
✅ Agrupa todas las configuraciones del sistema. Conexión a base de datos, variables de entorno, logging, CORS, Swagger, etc. Organizado en submódulos por contexto. |
utils/ |
✅ Contiene funciones auxiliares reutilizables. Ej: manejo de fechas, normalización de strings, generadores de IDs, helpers sin lógica de negocio ni infraestructura. |
deployment/ |
✅ Archivos necesarios para empaquetar y desplegar el microservicio. Incluye Dockerfile, manifest de Kubernetes, scripts, etc. |
requirements/ |
✅ Lista de dependencias del proyecto divididas por entorno (base.txt, dev.txt, test.txt, requirements.txt). Para entornos reproducibles y mantenibles. |
test/ |
✅ Contiene pruebas unitarias y de integración. Organizado por usecases y services. Usa mocks de interfaces para evitar conectarse a la base real en pruebas. |
logs/ |
✅ Carpeta donde se escriben los archivos .log generados por la ejecución de la app. No contiene código ni se versiona. Ignorada por .gitignore. |
reports/ |
✅ Reportes generados automáticamente (ej. cobertura de código con coverage.html, análisis de calidad, performance, etc.). Carpeta ignorada del control de versiones. |
Contiene todo el conocimiento del negocio y debe permanecer libre de dependencias externas.
¿Qué representan los modelos dentro de entities/?
Los modelos en entities/ representan exactamente los campos de la base de datos, con los mismos nombres y los mismos tipos de datos.
📌 Los modelos (model_for_entity_*.py) son clases que representan exactamente las tablas de la base de datos.
- Cada clase representa una tabla específica.
- Cada atributo de la clase corresponde a una columna de esa tabla, con el mismo nombre y tipo de dato.
- Se pueden implementar con @dataclass, lo que permite definir estructuras simples y ordenadas, sin métodos ni lógica adicional.
- No usan pydantic ni contienen validaciones, reglas de negocio o funciones.
- Su propósito es ser una descripción inmutable de los datos que existen en el dominio, usada por casos de uso, repositorios y servicios.
🧾 Ejemplo real
🐍 Modelo en model_for_entity_producto.py
from dataclasses import dataclass
@dataclass
class Producto:
id: int
nombre: str
precio: float🔁 Resultado: es una representación exacta de la tabla producto, pero escrita como clase en Python.
🎯 Tabla en base de datos:
CREATE TABLE producto (
id SERIAL PRIMARY KEY,
nombre TEXT NOT NULL,
precio DECIMAL NOT NULL
);- Modelos para entrada/salida de datos, usualmente desde/hacia la API.
- Usan
pydanticpara validación, conversión de tipos y documentación. - Cada
entitysuele tener unschemacorrespondiente.
📄 user_schema.py:
from pydantic import BaseModel, EmailStr
class UserCreateSchema(BaseModel):
name: str
email: EmailStr
class UserResponseSchema(BaseModel):
user_id: int
name: str
email: EmailStrLos
schemasNO forman parte del núcleo del negocio, sino de la interfaz con el exterior.
¿Qué representa un schema?
Un
schema(ubicado enschema_for_entity_*.py) define la forma, validación y estructura de los datos que se reciben y devuelven por una API.
-
Un schema es una plantilla que define cómo deben ser los datos que entran o salen del sistema cuando alguien se conecta a tu API (por ejemplo, desde una aplicación web o móvil).
-
Se usa para validar la entrada del usuario (por ejemplo, en un
POSToPUT) -
Y para formatear la salida que se entrega como respuesta al cliente (por ejemplo, en un
GEToDELETE) -
Están escritos con una librería llamada pydantic, que permite definir campos, tipos de datos, restricciones (como mínimo de caracteres, o que algo sea obligatorio).
-
Se escribe con
pydantic.BaseModel, y es el puente entre el mundo externo (API) y el dominio interno (entity) -
Es como qua la API diga:
- 🔒 "Solo acepto datos si me los mandas con el nombre correcto, el tipo correcto y cumpliendo mis reglas."
- 📦 "Y siempre responderé en este formato claro para que tú sepas cómo leerlo."
CREATE TABLE producto (
id SERIAL PRIMARY KEY,
nombre TEXT NOT NULL,
precio DECIMAL NOT NULL
);from pydantic import BaseModel, Field
class ProductoCreateSchema(BaseModel):
nombre: str = Field(..., min_length=2)
precio: floatEste esquema:
- Exige que venga un campo
nombrecon al menos 2 caracteres - Espera que el
preciosea un número (float) - Valida automáticamente que esos datos sean correctos
✅ Si falta un campo, el schema rechaza la petición antes de que llegue al negocio.
{
"nombre": "Laptop",
"precio": 1299.99
}{
"precio": "barato"
}❗ Porque "barato" no es un número, y nombre está ausente.
class ProductoResponseSchema(BaseModel):
id: int
nombre: str
precio: floatCuando un cliente hace GET /productos/1, la API usará este esquema para garantizar que responde así:
{
"id": 1,
"nombre": "Laptop",
"precio": 1299.99
}Incluso si la base de datos tiene otros campos, el schema controla lo que se muestra.
- 🔐 Seguridad: no entra basura ni datos incompletos
- 🧼 Claridad: las respuestas son limpias, ordenadas y fáciles de usar
- 🤝 Compatibilidad: otros equipos o sistemas pueden conectarse con confianza a tu API
Perfecto, Jorge. Aquí te dejo la descripción completa, precisa y fácil de entender para documentar correctamente la estructura de:
┃ ┃ ┣ 📂 interfaces [package] ← Contratos abstractos
┃ ┃ ┃ ┣ 📂 repositories [package] ← Interfaces de persistencia (ej. UserRepository)
┃ ┃ ┃ ┗ 📂 business_logic [package] ← Interfaces de lógica externa (ej. EmailSender)
Incluye propósito, ejemplos y cómo se conectan al resto del sistema (usecases, adapters, etc.).
Es un paquete que contiene contratos abstractos, también llamados interfaces. Define qué necesita el dominio, pero no cómo se implementa.
📌 En arquitectura limpia, el dominio (los usecases) puede depender de interfaces, pero nunca de implementaciones concretas.
Define cómo espera el sistema interactuar con la base de datos, pero sin acoplarse a SQL, Mongo, ORM, etc. Los repositories en el paquete interfaces/ son clases abstractas que definen métodos obligatorios, pero sin implementación.
Su objetivo es establecer un contrato claro sobre cómo el sistema debe interactuar con la base de datos, sin acoplarse a ninguna tecnología específica.
- Los repositories en el paquete interfaces/ son solo clases abstractas con métodos definidos pero no implementados.
- Aquí se escriben clases abstractas como
ProductoRepository,UsuarioRepository, etc. - Estas clases definen métodos obligatorios pero solo relacionado a interacciones con bases de datos, como
guardar(),buscar_por_id(),listar_todos(), pero no tienen implementación. - Se usan en los
usecases, donde se inyecta luego una implementación real (por ejemplo, un repositorio SQL o Mongo).
# database_method_model_producto.py
from abc import ABC, abstractmethod
from domain.entities.entity_producto.model_for_entity_producto import Producto
from typing import List, Optional
class ProductoRepository(ABC):
"""Interfaz abstracta que define las operaciones permitidas sobre productos."""
@abstractmethod
def guardar(self, producto: Producto) -> Producto:
"""Guarda un nuevo producto y devuelve el producto con su ID asignado."""
...
@abstractmethod
def buscar_por_id(self, producto_id: int) -> Optional[Producto]:
"""Busca un producto por su ID."""
...
@abstractmethod
def listar(self) -> List[Producto]:
"""Devuelve una lista de todos los productos."""
...
@abstractmethod
def actualizar(self, producto: Producto) -> Producto:
"""Actualiza un producto existente."""
...
@abstractmethod
def eliminar(self, producto_id: int) -> None:
"""Elimina un producto por su ID."""
...Define cómo el sistema espera comunicarse con servicios externos son clases abstractas que definen cómo el sistema espera comunicarse con servicios externos, como proveedores de email, SMS, autenticación, pagos, notificaciones, etc.
-
Estas clases definen métodos obligatorios, pero no contienen implementación real.
-
Sirven para describir el comportamiento esperado de un servicio externo, sin acoplar el sistema a una librería o proveedor específico.
-
Se definen con abstractmethod, y se implementan más adelante en clases concretas como SendGridEmailSender, TwilioSMSSender, StripePaymentProcessor, etc.
-
Los usecases dependen únicamente de estas interfaces, lo que permite sustituir o cambiar proveedores sin modificar la lógica del negocio.
# business_logic_method_model_usuario.py
from abc import ABC, abstractmethod
class EmailSender(ABC):
@abstractmethod
def enviar(self, destinatario: str, asunto: str, contenido: str) -> None:
passLuego puedes tener una implementación real como:
class SendGridEmailSender(EmailSender):
def enviar(self, destinatario, asunto, contenido):
# Llama a la API de SendGrid aquí
...- El
usecasesolo conoce la interfaz (ProductoRepository,EmailSender) - El
main.pyo el contenedor inyecta la implementación real (ProductoRepositorySQL,SendGridEmailSender) - El dominio sigue siendo limpio, desacoplado y fácil de testear
| Ventaja | Descripción |
|---|---|
| 🔄 Desacoplamiento total | Puedes cambiar de base de datos o proveedor sin tocar la lógica del negocio |
| ✅ Testeabilidad total | Puedes simular la interfaz fácilmente con un mock en pruebas |
| 🔒 Independencia tecnológica | El dominio no depende de frameworks, librerías externas ni protocolos |
Contiene la lógica de negocio central del sistema. Cada clase representa una acción completa que responde a un requerimiento funcional (crear, editar, eliminar, notificar, listar, etc.).
Los usecases son los núcleos ejecutables del dominio, que usan las interfaces declaradas en repositories/ o business_logic/, y orquestan entidades y validaciones del negocio.
Implementan la lógica de negocio completa para una acción del sistema (como crear, editar, eliminar, listar, notificar, etc.). Cada clase en usecases/ representa un flujo funcional bien definido, que responde a una necesidad del negocio y usa las interfaces declaradas en interfaces/ para acceder a datos o servicios externos, pero implementa directamente en su interior toda la lógica que es propia del dominio. Esto incluye, por ejemplo:
-
Validaciones propias del negocio (que no son validaciones de tipo como en los schemas)
-
Transformaciones de datos (ej. convertir estructuras, unir campos, aplicar formatos internos)
-
Filtros, reglas, restricciones (ej. no permitir duplicados, no permitir ciertas combinaciones)
-
Control del orden y condiciones de ejecución de acciones (ej. si se guarda, luego se notifica)
-
Aunque los métodos que usa provienen de interfaces, la decisión de cuándo y cómo se llaman, en qué condiciones, con qué datos y en qué orden —esa es la responsabilidad exclusiva del usecase.
-
Convertir estructuras.
-
Validar consistencia lógica (ej. que no haya duplicados)
-
Rechazar operaciones según reglas internas.
-
No se conectan a base de datos ni a servicios externos directamente.
-
Son independientes de frameworks, APIs, librerías y detalles de infraestructura.
-
Son totalmente testeables en aislamiento, usando mocks.
-
Usan las interfaces (
ProductoRepository,EmailSender, etc.) sin conocer la implementación.
# usecases/crear_producto.py
from domain.entities.entity_producto.model_for_entity_producto import Producto
from domain.interfaces.repositories.database_method_model_producto import ProductoRepository
class CrearProducto:
def __init__(self, repo: ProductoRepository):
self.repo = repo # Interface inyectada
def execute(self, nombre: str, precio: float) -> Producto:
# ✅ Lógica de negocio: validación del dominio
if len(nombre.strip()) < 3:
raise ValueError("El nombre del producto es demasiado corto")
if precio <= 0:
raise ValueError("El precio debe ser mayor a cero")
producto = Producto(id=0, nombre=nombre, precio=precio)
# ✅ Llama a la interfaz (no conoce la implementación)
return self.repo.guardar(producto)📂 services/ — Capa de Presentación (Endpoints HTTP reales)
La carpeta services/ define los endpoints reales del sistema, es decir, las funciones que procesan solicitudes HTTP (GET, POST, PUT, DELETE, etc.). Esta capa actúa como el puente entre el mundo externo (cliente/API) y el núcleo del negocio (usecases/).
✅ ¿Qué hace un archivo en services/?
- administra las peticiones recibidas
- Valida la entrada usando un schema (pydantic)
- Llama al usecase correspondiente
- Recibe el resultado, lo formatea si es necesario
- Devuelve una respuesta estructurada al cliente, con el schema de salida
Ejemplo
from fastapi import status
from configuration.rest.rest_api_configuration import clean_architecture
from usecases.usecase_products import ProductsUseCases as use_case
from usecases.usecase_products import Session, get_session, Depends, List
from usecases.usecase_products import get_schema, post_schema, put_schema, patch_schema
from configuration.end_points.products import SEARCH_ALL_OBJECTS, SEARCH_SPECIFIC_OBJECT, ADD_NEW_OBJECT, ADD_NEW_OBJECT_LIST
from configuration.end_points.products import BASE_PATH, UPDATE_OBJECT, PATCH_OBJECT, REMOVE_OBJECT
class ProductsServices:
# retorna UNA LISTA DE OBJETOS con los campos que tenga el ESQUEMA DEL RESPONSE MODEL en este caso TODOS LOS CAMPOS
@clean_architecture.get(BASE_PATH + "/", name=SEARCH_ALL_OBJECTS)
def get_object_list(session: Session = Depends(get_session), offset: int = 0, limit: int = 100, page_number : int = 1, page_size : int = 10):
"""
DESCRIPTION \\
this service allows to RECOVERY A LIST of records from database
"""
data = use_case().get_object_list(session=session, offset=offset, limit=limit)
response = use_case().get_response_format(data, offset=offset, limit=limit, page_number=page_number, page_size=page_size)
return response
# retorna UN OBJETO con los campos que tenga el ESQUEMA DEL RESPONSE MODEL en este caso todos los campos EXCEPTO EL CAMPO ID
@clean_architecture.get(BASE_PATH + "/{id}", name=SEARCH_SPECIFIC_OBJECT, tags=['products'],
responses={
404: {"model": get_schema, "description": "The item was not found"},
200: {
"description": "User requested by ID",
"content": {
"application/json": {
"example": {"id": "bar", "value": "The bar tenders"}
}
},
},
}
)
def get_object(id:int, session: Session = Depends(get_session)):
"""
DESCRIPTION \\
this service allows to RECOVERY specific the record from database
"""
data = use_case().get_object(id=id, session=session)
response = use_case().get_response_format(data=data)
return response
@clean_architecture.post(BASE_PATH + "/", name=ADD_NEW_OBJECT, status_code=status.HTTP_201_CREATED, tags=['products'])
def add_object(entity:post_schema, session: Session = Depends(get_session)):
"""
DESCRIPTION \\
this service allows to CREATE a new the record of database
"""
data = use_case().add_object(entity=entity, session=session)
response = use_case().get_response_format(data=data)
return response
@clean_architecture.post(BASE_PATH + "/all", name=ADD_NEW_OBJECT_LIST, status_code=status.HTTP_201_CREATED, tags=['products'])
def add_object_list(entity:List[post_schema], session: Session = Depends(get_session)):
"""
DESCRIPTION \\
this service allows to CREATE a LIST of new the record of database
"""
data = use_case().add_object_list(entity=entity, session=session)
response = use_case().get_response_format(data=data)
return response
@clean_architecture.put(BASE_PATH + "/{id}", name=UPDATE_OBJECT, tags=['products'])
def update_object(id:int, entity:put_schema, session: Session = Depends(get_session)):
"""
DESCRIPTION \\
this service allows to UPDATE the record of database
"""
return use_case().update_object(id=id, entity=entity, session=session)
@clean_architecture.patch(BASE_PATH + "/{id}", name=PATCH_OBJECT, tags=['products'])
def patch_object(id:int, entity:patch_schema, session: Session = Depends(get_session)):
"""
DESCRIPTION \\
this service allows to PATCH the record of database
"""
return use_case().patch_object(id=id, entity=entity, session=session)
# status_code=status.HTTP_204_NO_CONTENT, genera excepcion porque retorna mensaje
@clean_architecture.delete(BASE_PATH + "/{id}", name=REMOVE_OBJECT, tags=['products'])
def delete_object(id:int, session: Session = Depends(get_session)):
"""
DESCRIPTION \\
this service allows to REMOVE the record of database
"""
return use_case().delete_object(id=id, session=session)Contiene todo lo relacionado con configuración, entrada al sistema, y conectores con tecnologías externas.
main.py: punto de entrada del microservicio.config.py: define variables globales como rutas, puertos y claves.logger.py: inicializa logs con formato.utils/: funciones genéricas de utilidad para formateo, transformación, etc.
Diseñado para facilitar pruebas unitarias puras del dominio.
use_cases/: pruebas de casos de uso con mocks de interfaces.mock/: implementaciones ficticias de interfaces para testeo.
📄 mock_user_repository.py:
from domain.interfaces.user_repository import UserRepository
from domain.entities.user import User
class MockUserRepository(UserRepository):
def save(self, user: User) -> User:
return User(user_id=1, name=user.name, email=user.email)- El
usecasedecide cuándo y cómo se crea un producto. - No le importa si se guarda en PostgreSQL, Mongo o un archivo.
- Si mañana cambias el motor de base de datos o el proveedor de email, el
usecaseno se toca.
Contains all configuration files for the Application
Save information about the application log.
Save unit testing coverage information about the application code.
Contains transversal resources for the application, that is not possible to include in other layers.
is a package that contains modules, like Models and Schemas, the module it's a plain script, it contains only the class definition and no logic in the classes.
- Models: are modules that contain the fields from a table on the database and it is represented by a class.
- Schemas: these are modules that contain the fields Used to validate data we receive as well as to reformat the data that we want to send to the client/browser, so just contains the fields that we want to return in the request.
are interfaces that contain only the methods that need to be implemented in the use cases one directory by class.
- Repository: Contains modules with methods that are needed to communicate with the database by class.
- Business: Contains modules with methods that are needed to process the information by class.
is a package that contains modules, the module is a class that implements the methods from the package interfaces(Repository, Business) and defines the business logic by module.
is a package that contains modules, the module is a class that contains the API services by functionality or by class and use the UseCases classes.
layer that communicates internal API services with API consumers.
Directory that contains the Dockerfile, k8s Manifest, and every file needed for the deployment.
Directory containing the requirements.txt with the definition of project dependencies.
Directory that contains the unit testing from the Use Cases and Services files.
Dockerfile: define cómo construir la imagen Docker del microservicio.manifest.yaml: manifiesto de Kubernetes para orquestación.
git clone https://github.com/JorgeCardona/python_microservice_scaffolding_clean_architecture.git
cd python_microservice_scaffolding_clean_architecture
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python application/main.pypytest test/📄 .github/workflows/ci.yml:
name: CI - Validación y Testeo
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: pip install -r requirements.txt
- run: pytest test/Jorge Cardona Gallego
https://github.com/JorgeCardonamat the data that we want to send to the client/browser, so just contains the fields that we want to return in the request.
are interfaces that contain only the methods that need to be implemented in the use cases one directory by class.
- Repository: Contains modules with methods that are needed to communicate with the database by class.
- Business: Contains modules with methods that are needed to process the information by class.
is a package that contains modules, the module is a class that implements the methods from the package interfaces(Repository, Business) and defines the business logic by module.
is a package that contains modules, the module is a class that contains the API services by functionality or by class and use the UseCases classes.
layer that communicates internal API services with API consumers.
Directory that contains the Dockerfile, k8s Manifest, and every file needed for the deployment.
Directory containing the requirements.txt with the definition of project dependencies.
Directory that contains the unit testing from the Use Cases and Services files.
