hexcore 2.1.1__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hexcore-2.1.1 → hexcore-2.3.0}/LICENSE +1 -5
- hexcore-2.3.0/MANIFEST.in +1 -0
- hexcore-2.3.0/PKG-INFO +584 -0
- hexcore-2.3.0/README.md +558 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/__init__.py +6 -10
- hexcore-2.3.0/hexcore/__main__.py +4 -0
- hexcore-2.3.0/hexcore/application/cqrs/__init__.py +24 -0
- hexcore-2.3.0/hexcore/application/cqrs/adapters.py +33 -0
- hexcore-2.3.0/hexcore/application/cqrs/config.py +69 -0
- hexcore-2.3.0/hexcore/application/cqrs/factory.py +131 -0
- hexcore-2.3.0/hexcore/application/cqrs/in_memory_buses.py +164 -0
- hexcore-2.3.0/hexcore/application/cqrs/pipeline.py +58 -0
- hexcore-2.3.0/hexcore/application/cqrs/registry.py +115 -0
- hexcore-2.3.0/hexcore/application/dtos/__init__.py +23 -0
- hexcore-2.3.0/hexcore/application/dtos/query.py +66 -0
- hexcore-2.3.0/hexcore/application/use_cases/__init__.py +13 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/application/use_cases/base.py +4 -4
- hexcore-2.3.0/hexcore/application/use_cases/query.py +37 -0
- hexcore-2.3.0/hexcore/config.py +185 -0
- hexcore-2.3.0/hexcore/domain/auth/__init__.py +13 -0
- hexcore-2.3.0/hexcore/domain/auth/permissions.py +71 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/domain/auth/value_objects.py +2 -3
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/domain/base.py +3 -3
- hexcore-2.3.0/hexcore/domain/cqrs/__init__.py +50 -0
- hexcore-2.3.0/hexcore/domain/cqrs/buses.py +80 -0
- hexcore-2.3.0/hexcore/domain/cqrs/commands.py +30 -0
- hexcore-2.3.0/hexcore/domain/cqrs/decorators.py +78 -0
- hexcore-2.3.0/hexcore/domain/cqrs/exceptions.py +34 -0
- hexcore-2.3.0/hexcore/domain/cqrs/handlers.py +52 -0
- hexcore-2.3.0/hexcore/domain/cqrs/middleware.py +51 -0
- hexcore-2.3.0/hexcore/domain/cqrs/queries.py +27 -0
- hexcore-2.3.0/hexcore/domain/cqrs/serializer.py +39 -0
- hexcore-2.3.0/hexcore/domain/cqrs/task_queues.py +64 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/domain/events.py +36 -4
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/domain/repositories.py +6 -11
- hexcore-2.3.0/hexcore/domain/services.py +232 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/domain/uow.py +7 -10
- hexcore-2.3.0/hexcore/infrastructure/api/__init__.py +3 -0
- hexcore-2.3.0/hexcore/infrastructure/api/utils.py +174 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/cache/cache_backends/memory.py +3 -3
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/cache/cache_backends/redis.py +2 -2
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/cli.py +184 -145
- hexcore-2.3.0/hexcore/infrastructure/cqrs/__init__.py +24 -0
- hexcore-2.3.0/hexcore/infrastructure/cqrs/middlewares.py +136 -0
- hexcore-2.3.0/hexcore/infrastructure/cqrs/procrastinate.py +99 -0
- hexcore-2.3.0/hexcore/infrastructure/cqrs/pydantic_serializer.py +56 -0
- hexcore-2.3.0/hexcore/infrastructure/cqrs/rabbitmq.py +182 -0
- hexcore-2.3.0/hexcore/infrastructure/events/events_backends/memory.py +32 -0
- hexcore-2.3.0/hexcore/infrastructure/repositories/base.py +28 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/repositories/implementations.py +55 -35
- hexcore-2.3.0/hexcore/infrastructure/repositories/orms/beanie/utils.py +283 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/alchemy.py → hexcore-2.3.0/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.py +1 -1
- hexcore-2.3.0/hexcore/infrastructure/repositories/orms/sqlalchemy/session.py +66 -0
- {hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql → hexcore-2.3.0/hexcore/infrastructure/repositories/orms/sqlalchemy}/utils.py +132 -12
- hexcore-2.3.0/hexcore/infrastructure/repositories/utils.py +352 -0
- hexcore-2.3.0/hexcore/infrastructure/uow/__init__.py +181 -0
- hexcore-2.3.0/hexcore/infrastructure/uow/helpers.py +21 -0
- hexcore-2.3.0/hexcore/infrastructure/workers/__init__.py +1 -0
- hexcore-2.3.0/hexcore/infrastructure/workers/consumer.py +123 -0
- hexcore-2.3.0/hexcore/infrastructure/workers/rabbitmq_worker.py +93 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/types.py +5 -1
- hexcore-2.3.0/hexcore.egg-info/PKG-INFO +584 -0
- hexcore-2.3.0/hexcore.egg-info/SOURCES.txt +97 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore.egg-info/requires.txt +7 -3
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore.egg-info/top_level.txt +2 -1
- {hexcore-2.1.1 → hexcore-2.3.0}/pyproject.toml +18 -28
- hexcore-2.3.0/scripts/main.py +10 -0
- hexcore-2.3.0/tests/conftest.py +21 -0
- hexcore-2.3.0/tests/test_beanie_query_utils.py +138 -0
- hexcore-2.3.0/tests/test_config_loading.py +187 -0
- hexcore-2.3.0/tests/test_cqrs.py +602 -0
- hexcore-2.3.0/tests/test_domain_service_query.py +153 -0
- hexcore-2.3.0/tests/test_infrastructure_query_path.py +150 -0
- hexcore-2.3.0/tests/test_query_field_validation.py +218 -0
- hexcore-2.3.0/tests/test_rabbitmq_bus.py +95 -0
- hexcore-2.3.0/tests/test_repositories_utils.py +335 -0
- hexcore-2.3.0/tests/test_smart_routing.py +218 -0
- hexcore-2.3.0/tests/test_uow_session_regression.py +187 -0
- hexcore-2.3.0/tests/test_use_cases_query.py +224 -0
- hexcore-2.1.1/PKG-INFO +0 -38
- hexcore-2.1.1/README.md +0 -14
- hexcore-2.1.1/hexcore/__init__.pyi +0 -10
- hexcore-2.1.1/hexcore/application/__init__.pyi +0 -4
- hexcore-2.1.1/hexcore/application/dtos/__init__.py +0 -7
- hexcore-2.1.1/hexcore/application/dtos/__init__.pyi +0 -3
- hexcore-2.1.1/hexcore/application/dtos/base.pyi +0 -4
- hexcore-2.1.1/hexcore/application/use_cases/__init__.py +0 -7
- hexcore-2.1.1/hexcore/application/use_cases/__init__.pyi +0 -3
- hexcore-2.1.1/hexcore/application/use_cases/base.pyi +0 -11
- hexcore-2.1.1/hexcore/config.py +0 -99
- hexcore-2.1.1/hexcore/config.pyi +0 -35
- hexcore-2.1.1/hexcore/domain/auth/__init__.py +0 -17
- hexcore-2.1.1/hexcore/domain/auth/__init__.pyi +0 -4
- hexcore-2.1.1/hexcore/domain/auth/permissions.py +0 -73
- hexcore-2.1.1/hexcore/domain/auth/permissions.pyi +0 -25
- hexcore-2.1.1/hexcore/domain/auth/value_objects.pyi +0 -12
- hexcore-2.1.1/hexcore/domain/base.pyi +0 -19
- hexcore-2.1.1/hexcore/domain/events.pyi +0 -35
- hexcore-2.1.1/hexcore/domain/exceptions.pyi +0 -2
- hexcore-2.1.1/hexcore/domain/repositories.pyi +0 -25
- hexcore-2.1.1/hexcore/domain/services.py +0 -11
- hexcore-2.1.1/hexcore/domain/services.pyi +0 -8
- hexcore-2.1.1/hexcore/domain/uow.pyi +0 -18
- hexcore-2.1.1/hexcore/infrastructure/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/api/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/api/utils.py +0 -20
- hexcore-2.1.1/hexcore/infrastructure/api/utils.pyi +0 -9
- hexcore-2.1.1/hexcore/infrastructure/cache/__init__.pyi +0 -10
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/memory.pyi +0 -10
- hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/redis.pyi +0 -12
- hexcore-2.1.1/hexcore/infrastructure/cli.pyi +0 -21
- hexcore-2.1.1/hexcore/infrastructure/events/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/events/events_backends/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/events/events_backends/memory.py +0 -20
- hexcore-2.1.1/hexcore/infrastructure/events/events_backends/memory.pyi +0 -8
- hexcore-2.1.1/hexcore/infrastructure/repositories/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/base.py +0 -24
- hexcore-2.1.1/hexcore/infrastructure/repositories/base.pyi +0 -13
- hexcore-2.1.1/hexcore/infrastructure/repositories/decorators.pyi +0 -5
- hexcore-2.1.1/hexcore/infrastructure/repositories/implementations.pyi +0 -39
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/beanie.pyi +0 -14
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/utils.py +0 -134
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/utils.pyi +0 -18
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/__init__.py +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/__init__.pyi +0 -0
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/alchemy.pyi +0 -19
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/session.py +0 -31
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/session.pyi +0 -9
- hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/utils.pyi +0 -22
- hexcore-2.1.1/hexcore/infrastructure/repositories/utils.py +0 -119
- hexcore-2.1.1/hexcore/infrastructure/repositories/utils.pyi +0 -13
- hexcore-2.1.1/hexcore/infrastructure/uow/__init__.py +0 -121
- hexcore-2.1.1/hexcore/infrastructure/uow/__init__.pyi +0 -22
- hexcore-2.1.1/hexcore/infrastructure/uow/decorators.pyi +0 -4
- hexcore-2.1.1/hexcore/infrastructure/uow/helpers.py +0 -9
- hexcore-2.1.1/hexcore/infrastructure/uow/helpers.pyi +0 -7
- hexcore-2.1.1/hexcore/types.pyi +0 -21
- hexcore-2.1.1/hexcore.egg-info/PKG-INFO +0 -38
- hexcore-2.1.1/hexcore.egg-info/SOURCES.txt +0 -100
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/application/__init__.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/application/dtos/base.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/domain/__init__.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/domain/exceptions.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/__init__.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/cache/__init__.py +0 -0
- {hexcore-2.1.1/hexcore/infrastructure/api → hexcore-2.3.0/hexcore/infrastructure/cache/cache_backends}/__init__.py +0 -0
- {hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends → hexcore-2.3.0/hexcore/infrastructure/events}/__init__.py +0 -0
- {hexcore-2.1.1/hexcore/infrastructure/events → hexcore-2.3.0/hexcore/infrastructure/events/events_backends}/__init__.py +0 -0
- {hexcore-2.1.1/hexcore/infrastructure/events/events_backends → hexcore-2.3.0/hexcore/infrastructure/repositories}/__init__.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/repositories/decorators.py +0 -0
- {hexcore-2.1.1/hexcore/infrastructure/repositories → hexcore-2.3.0/hexcore/infrastructure/repositories/orms}/__init__.py +0 -0
- /hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/beanie.py → /hexcore-2.3.0/hexcore/infrastructure/repositories/orms/beanie/__init__.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore/infrastructure/uow/decorators.py +0 -0
- /hexcore-2.1.1/hexcore/domain/__init__.pyi → /hexcore-2.3.0/hexcore/py.typed +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore.egg-info/dependency_links.txt +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/hexcore.egg-info/entry_points.txt +0 -0
- {hexcore-2.1.1/hexcore/infrastructure/repositories/orms → hexcore-2.3.0/scripts}/__init__.py +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/setup.cfg +0 -0
- {hexcore-2.1.1 → hexcore-2.3.0}/tests/test_basic.py +0 -0
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
2
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
3
|
-
furnished to do so, subject to the following conditions:
|
|
4
|
-
|
|
5
1
|
MIT License
|
|
6
2
|
|
|
7
|
-
Copyright (c) 2025 David Latosefki
|
|
3
|
+
Copyright (c) 2025 David Latosefki
|
|
8
4
|
|
|
9
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include hexcore *.pyi py.typed
|
hexcore-2.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hexcore
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: Núcleo reutilizable para proyectos Python con arquitectura hexagonal y event handling. Provee abstracciones, utilidades y contratos para DDD, eventos y desacoplamiento de infraestructura.
|
|
5
|
+
Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: aiosqlite>=0.21.0
|
|
11
|
+
Requires-Dist: alembic>=1.16.5
|
|
12
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
13
|
+
Requires-Dist: beanie>=2.0.0
|
|
14
|
+
Requires-Dist: fastapi>=0.116.1
|
|
15
|
+
Requires-Dist: pika>=1.3.2
|
|
16
|
+
Requires-Dist: pydantic>=2.11.7
|
|
17
|
+
Requires-Dist: redis>=6.4.0
|
|
18
|
+
Requires-Dist: sqlalchemy>=2.0.43
|
|
19
|
+
Requires-Dist: typer>=0.17.3
|
|
20
|
+
Requires-Dist: ruff>=0.12.11
|
|
21
|
+
Provides-Extra: procrastinate
|
|
22
|
+
Requires-Dist: procrastinate>=3.0.0; extra == "procrastinate"
|
|
23
|
+
Provides-Extra: rabbitmq
|
|
24
|
+
Requires-Dist: aio-pika>=9.4.0; extra == "rabbitmq"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# HexCore [](https://pepy.tech/projects/hexcore)
|
|
28
|
+
HexCore es un módulo base reutilizable para proyectos Python que implementan arquitectura hexagonal y event handling.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Skills del Proyecto
|
|
33
|
+
|
|
34
|
+
Este repositorio cuenta con un conjunto de skills adicionales para extender y personalizar funcionalidades en VS Code y otros entornos compatibles. Puedes encontrarlas en:
|
|
35
|
+
|
|
36
|
+
- [Repositorio de Skills de HexCore](https://github.com/Indroic/hexcore-skill)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ¿Qué provee HexCore?
|
|
41
|
+
|
|
42
|
+
- **Clases base y abstracciones** para entidades, repositorios, servicios y unidad de trabajo (UoW), siguiendo los principios de DDD y arquitectura hexagonal.
|
|
43
|
+
- **Interfaces y contratos** para caché, eventos y manejo de dependencias, desacoplando la lógica de negocio de la infraestructura.
|
|
44
|
+
- **Utilidades para event sourcing y event dispatching** listas para usar en cualquier proyecto.
|
|
45
|
+
- **Estructura flexible** para que puedas construir microservicios o aplicaciones monolíticas desacopladas y testeables.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Instalación
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pip install hexcore
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Templates de Proyecto (CLI)
|
|
56
|
+
|
|
57
|
+
HexCore incluye templates base para bootstrap de proyectos:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
hexcore init mi_proyecto --template hexagonal
|
|
61
|
+
hexcore init mi_proyecto --template vertical-slice
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- `hexagonal`: crea `src/domain`, `src/application`, `src/infrastructure`.
|
|
65
|
+
- `vertical-slice`: crea `src/features`, `src/shared/domain`, `src/shared/application`, `src/shared/infrastructure`.
|
|
66
|
+
|
|
67
|
+
En ambos templates se generan:
|
|
68
|
+
- `config.py` en raíz con `repository_discovery_paths` de ejemplo.
|
|
69
|
+
- estructura de migraciones con Alembic.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Configuración v2 (Folder-Agnostic)
|
|
74
|
+
|
|
75
|
+
Desde v2, HexCore usa configuración explícita y no aplica fallback implícito para descubrir repositorios.
|
|
76
|
+
|
|
77
|
+
### 1. Configuración visible en raíz
|
|
78
|
+
|
|
79
|
+
Define un archivo `config.py` en la raíz del proyecto:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from hexcore.config import ServerConfig
|
|
83
|
+
|
|
84
|
+
config = ServerConfig(
|
|
85
|
+
repository_discovery_paths={
|
|
86
|
+
"myapp.features.users.infrastructure.repositories",
|
|
87
|
+
"myapp.features.billing.infrastructure.repositories",
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Prioridad para cargar configuración
|
|
93
|
+
|
|
94
|
+
`LazyConfig` resuelve módulos en este orden:
|
|
95
|
+
|
|
96
|
+
1. `HEXCORE_CONFIG_MODULE`
|
|
97
|
+
2. `HEXCORE_CONFIG_MODULES` (lista separada por comas)
|
|
98
|
+
3. módulos configurados por `LazyConfig.set_config_modules(...)`
|
|
99
|
+
4. `config` por defecto (raíz del proyecto)
|
|
100
|
+
|
|
101
|
+
### 3. Regla de discovery en v2
|
|
102
|
+
|
|
103
|
+
- Si `repository_discovery_paths` está vacío, no se cargan módulos de repositorios.
|
|
104
|
+
- UoW falla con error explícito para evitar comportamiento ambiguo.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Pautas de Colaboración
|
|
109
|
+
|
|
110
|
+
¡Gracias por tu interés en contribuir a HexCore! Para mantener una colaboración organizada y eficiente, sigue estas pautas:
|
|
111
|
+
|
|
112
|
+
### 1. Código de Conducta
|
|
113
|
+
Mantén siempre una comunicación respetuosa y profesional. Revisa el [Código de Conducta](CODE_OF_CONDUCT.md) antes de interactuar.
|
|
114
|
+
|
|
115
|
+
### 2. Cómo Contribuir
|
|
116
|
+
- **Forkea** el repositorio y crea una rama para tu contribución (`feature/nombre`, `fix/nombre`, etc.).
|
|
117
|
+
- Realiza tus cambios en la rama y asegúrate de que el código funcione correctamente.
|
|
118
|
+
- Escribe una descripción clara y detallada en tu pull request (PR).
|
|
119
|
+
- Relaciona los issues relevantes en tu PR si aplica.
|
|
120
|
+
|
|
121
|
+
### 3. Estilo y Formato de Código
|
|
122
|
+
- Sigue la guía de estilos de Python ([PEP8](https://pep8.org/)).
|
|
123
|
+
- Usa comentarios cuando sea necesario para clarificar el propósito del código.
|
|
124
|
+
- Idealmente, incluye pruebas unitarias para nuevas funciones y arreglos.
|
|
125
|
+
|
|
126
|
+
### 4. Revisión de Pull Requests
|
|
127
|
+
- Todos los PR serán revisados antes de ser aceptados. Se pueden solicitar cambios o aclaraciones.
|
|
128
|
+
- Responde a los comentarios de los revisores para facilitar el proceso.
|
|
129
|
+
|
|
130
|
+
### 5. Issues
|
|
131
|
+
- Describe claramente los problemas que encuentres.
|
|
132
|
+
- Proporciona información relevante (logs, versiones, pasos para reproducir, etc.).
|
|
133
|
+
|
|
134
|
+
### 6. Comunicación
|
|
135
|
+
- Usa los issues y las discusiones para preguntas, sugerencias o propuestas.
|
|
136
|
+
- Si tienes dudas sobre cómo empezar, puedes abrir un issue para orientación.
|
|
137
|
+
|
|
138
|
+
### 7. Licencia
|
|
139
|
+
Al contribuir, aceptas que tu código será distribuido bajo la licencia del repositorio.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Documentación Básica
|
|
144
|
+
|
|
145
|
+
### Estructura principal
|
|
146
|
+
|
|
147
|
+
HexCore se organiza con los siguientes submódulos y carpetas:
|
|
148
|
+
|
|
149
|
+
- **src/domain/**: Módulos de dominio, entidades, repositorios, servicios, objetos de valor, eventos, enums y excepciones.
|
|
150
|
+
```
|
|
151
|
+
src/domain/{modulo}/
|
|
152
|
+
├─ __init__.py
|
|
153
|
+
├─ entities.py
|
|
154
|
+
├─ repositories.py
|
|
155
|
+
├─ services.py
|
|
156
|
+
├─ value_objects.py
|
|
157
|
+
├─ events.py
|
|
158
|
+
├─ enums.py
|
|
159
|
+
└─ exceptions.py
|
|
160
|
+
```
|
|
161
|
+
- **src/application/**: Casos de uso (UseCase) y DTOs para orquestar la lógica de negocio.
|
|
162
|
+
- **src/infrastructure/**: Implementaciones técnicas (ORM/ODM, CLI, caché, base de datos, repositorios, unit of work).
|
|
163
|
+
- **src/infrastructure/database/models/**: Modelos SQLAlchemy para base de datos relacional.
|
|
164
|
+
- **src/infrastructure/database/documents/**: Documentos Beanie para MongoDB.
|
|
165
|
+
- **tests/**: Pruebas para módulos de dominio e infraestructura.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### Abstracciones de Entidades y Eventos
|
|
170
|
+
|
|
171
|
+
#### BaseEntity
|
|
172
|
+
|
|
173
|
+
Clase base para entidades de dominio. Provee atributos comunes y gestión de eventos.
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from hexcore.domain.base import BaseEntity
|
|
177
|
+
|
|
178
|
+
class User(BaseEntity):
|
|
179
|
+
id: UUID
|
|
180
|
+
name: str
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### DomainEvent y eventos de entidad
|
|
184
|
+
|
|
185
|
+
Abstracciones para eventos de dominio y para ciclo de vida de entidades.
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from hexcore.domain.events import DomainEvent, EntityCreatedEvent
|
|
189
|
+
|
|
190
|
+
class UserCreatedEvent(EntityCreatedEvent[User]):
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
user = User(...)
|
|
194
|
+
event = UserCreatedEvent(entity_id=user.id, payload={"name": user.name})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### Implementaciones de Repositorios
|
|
200
|
+
|
|
201
|
+
#### SQLAlchemyCommonImplementationsRepo
|
|
202
|
+
|
|
203
|
+
Repositorio genérico para modelos SQLAlchemy con métodos CRUD reutilizables.
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
class SQLAlchemyCommonImplementationsRepo(BaseSQLAlchemyRepository[T], HasBasicArgs[T, M], t.Generic[T, M]):
|
|
207
|
+
# Métodos principales: get_by_id, list_all, save, delete
|
|
208
|
+
...
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Ejemplo:**
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
class UserRepository(SQLAlchemyCommonImplementationsRepo[UserEntity, UserModel]):
|
|
215
|
+
def __init__(self, uow):
|
|
216
|
+
super().__init__(
|
|
217
|
+
entity_cls=UserEntity,
|
|
218
|
+
model_cls=UserModel,
|
|
219
|
+
not_found_exception=UserNotFoundException,
|
|
220
|
+
fields_resolvers=None,
|
|
221
|
+
fields_serializers=None,
|
|
222
|
+
uow=uow
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### BeanieODMCommonImplementationsRepo
|
|
227
|
+
|
|
228
|
+
Repositorio genérico para documentos Beanie ODM (MongoDB) con métodos CRUD reutilizables.
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
class BeanieODMCommonImplementationsRepo(IBaseRepository[T], HasBasicArgs[T, D], t.Generic[T, D]):
|
|
232
|
+
# Métodos principales: get_by_id, list_all, save, delete
|
|
233
|
+
...
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Ejemplo:**
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
class UserRepository(BeanieODMCommonImplementationsRepo[UserEntity, UserDocument]):
|
|
240
|
+
def __init__(self, uow):
|
|
241
|
+
super().__init__(
|
|
242
|
+
entity_cls=UserEntity,
|
|
243
|
+
document_cls=UserDocument,
|
|
244
|
+
not_found_exception=UserNotFoundException,
|
|
245
|
+
fields_resolvers=None,
|
|
246
|
+
fields_serializers=None,
|
|
247
|
+
uow=uow
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### Inicialización y Descubrimiento de Documentos Beanie
|
|
254
|
+
|
|
255
|
+
Para inicializar y registrar automáticamente todos los documentos Beanie:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from hexcore.infrastructure.repositories.orms.beanie.utils import init_beanie_documents
|
|
259
|
+
|
|
260
|
+
await init_beanie_documents()
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### Conversión entre modelos/documentos y entidades
|
|
266
|
+
|
|
267
|
+
Ambos repositorios utilizan `to_entity_from_model_or_document` para convertir modelos ORM/ODM en entidades del dominio, aplicando resolvers para atributos complejos.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Arquitectura CQRS en HexCore
|
|
274
|
+
|
|
275
|
+
HexCore v2 integra de forma nativa soporte para el patrón **CQRS (Command Query Responsibility Segregation)**, permitiendo separar conceptual y técnicamente las operaciones de escritura (Commands) de las de lectura (Queries).
|
|
276
|
+
|
|
277
|
+
### ¿Cómo funciona el CQRS en HexCore?
|
|
278
|
+
|
|
279
|
+
El sistema se basa en 3 buses principales, configurables e independientes:
|
|
280
|
+
|
|
281
|
+
1. **`AbstractCommandBus`**: Despacha inteniones de mutación (`Command`) a un único `AbstractCommandHandler`. Los commands modifican el estado del sistema y se ejecutan (por defecto) dentro de una transacción de base de datos (Unit of Work).
|
|
282
|
+
2. **`AbstractQueryBus`**: Despacha intenciones de lectura (`Query`) a un único `AbstractQueryHandler`. Retornan un resultado sin mutar el estado.
|
|
283
|
+
3. **`EventBus`**: Distribuye eventos de dominio (`DomainEvent`) a múltiples suscriptores asíncronamente (vía `subscribe`/`publish`).
|
|
284
|
+
|
|
285
|
+
La configuración de CQRS se activa mediante el `CQRSConfig` en tu `ServerConfig`:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from hexcore.config import ServerConfig
|
|
289
|
+
from hexcore.application.cqrs.config import CQRSConfig, BusConfig
|
|
290
|
+
|
|
291
|
+
config = ServerConfig(
|
|
292
|
+
cqrs=CQRSConfig(
|
|
293
|
+
command_bus=BusConfig(
|
|
294
|
+
# Por defecto incluye TransactionMiddleware
|
|
295
|
+
middlewares=["hexcore.infrastructure.cqrs.middlewares.TransactionMiddleware"]
|
|
296
|
+
),
|
|
297
|
+
# Puedes sustituir el backend en memoria por uno distribuido (Ej: Celery, Procrastinate)
|
|
298
|
+
# backend="mi_app.infrastructure.ProcrastinateCommandBus"
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
### Guía de Migración: De Casos de Uso Clásicos a CQRS
|
|
306
|
+
|
|
307
|
+
Si ya tienes una aplicación escrita con la abstracción `UseCase` de HexCore, puedes migrar progresivamente a CQRS sin reescribir todo tu código, utilizando los adaptadores incluidos.
|
|
308
|
+
|
|
309
|
+
#### Paso 1: Usar el adaptador `UseCaseCommandHandler`
|
|
310
|
+
|
|
311
|
+
En lugar de instanciar un UseCase directamente en tu endpoint, envuélvelo en un comando:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
from hexcore.application.cqrs.adapters import UseCaseCommandHandler
|
|
315
|
+
from hexcore.application.cqrs.registry import HandlerRegistry
|
|
316
|
+
|
|
317
|
+
# 1. Tienes tu UseCase legado
|
|
318
|
+
class CreateUserUseCase(UseCase[CreateUserCommand, UserDTO]):
|
|
319
|
+
async def execute(self, request: CreateUserCommand) -> UserDTO:
|
|
320
|
+
# logica legacy
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
# 2. Lo registras en el registry de CQRS utilizando el adaptador
|
|
324
|
+
registry = HandlerRegistry()
|
|
325
|
+
registry.register_command(
|
|
326
|
+
CreateUserCommand,
|
|
327
|
+
UseCaseCommandHandler(CreateUserUseCase())
|
|
328
|
+
)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### Paso 2: Consumirlo desde el endpoint usando el CommandBus
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
@router.post("/users")
|
|
335
|
+
async def create_user(
|
|
336
|
+
cmd: CreateUserCommand,
|
|
337
|
+
# factory inyectado por dependencias
|
|
338
|
+
bus: AbstractCommandBus = Depends(get_command_bus)
|
|
339
|
+
):
|
|
340
|
+
# El bus despacha el comando al UseCase legacy de forma transparente
|
|
341
|
+
result = await bus.dispatch(cmd)
|
|
342
|
+
return result
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Paso 3 (Final): Refactor a Handler Puro
|
|
346
|
+
|
|
347
|
+
Cuando estés listo, convierte tu UseCase directamente en un `AbstractCommandHandler`:
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
from hexcore.domain.cqrs import AbstractCommandHandler
|
|
351
|
+
|
|
352
|
+
class CreateUserHandler(AbstractCommandHandler[CreateUserCommand, UserDTO]):
|
|
353
|
+
def __init__(self, uow: IUnitOfWork):
|
|
354
|
+
self.uow = uow
|
|
355
|
+
|
|
356
|
+
async def handle(self, command: CreateUserCommand) -> UserDTO:
|
|
357
|
+
# Lógica refactorizada
|
|
358
|
+
return dto
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
### Guía: Almacenamiento Híbrido para Queries (Mongo, Redis, SQL)
|
|
364
|
+
|
|
365
|
+
La mayor ventaja de CQRS es optimizar las lecturas. HexCore permite que tus **Commands** escriban en una base de datos relacional (SQLAlchemy) fuertemente normalizada, mientras que los **Queries** leen de vistas desnormalizadas súper rápidas en MongoDB o Redis.
|
|
366
|
+
|
|
367
|
+
#### 1. Sincronización a través del EventBus (La Proyección)
|
|
368
|
+
|
|
369
|
+
Cuando un Command modifica SQL, dispara un Evento de Dominio. Un handler de eventos intercepta este evento y actualiza el "Read Model" en MongoDB o Redis.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
from hexcore.domain.events import EventBus, DomainEvent
|
|
373
|
+
|
|
374
|
+
class UserCreatedEvent(DomainEvent):
|
|
375
|
+
user_id: str
|
|
376
|
+
full_name: str
|
|
377
|
+
email: str
|
|
378
|
+
|
|
379
|
+
async def project_user_to_mongodb(event: UserCreatedEvent):
|
|
380
|
+
"""Proyecta el evento en la BD de lectura (MongoDB)"""
|
|
381
|
+
doc = UserReadDocument(
|
|
382
|
+
id=event.user_id,
|
|
383
|
+
name=event.full_name,
|
|
384
|
+
email=event.email
|
|
385
|
+
)
|
|
386
|
+
await doc.insert() # usando Beanie (Mongo)
|
|
387
|
+
|
|
388
|
+
# Registrar la proyección
|
|
389
|
+
event_bus.subscribe(UserCreatedEvent, project_user_to_mongodb)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### 2. Query Handler leyendo del Read Model
|
|
393
|
+
|
|
394
|
+
Tu QueryHandler nunca toca SQL, simplemente ataca directamente a Mongo o Redis para máxima velocidad.
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
from hexcore.domain.cqrs import AbstractQueryHandler, Query
|
|
398
|
+
|
|
399
|
+
class GetUserQuery(Query[UserReadDTO]):
|
|
400
|
+
user_id: str
|
|
401
|
+
|
|
402
|
+
class GetUserQueryHandler(AbstractQueryHandler[GetUserQuery, UserReadDTO]):
|
|
403
|
+
async def handle(self, query: GetUserQuery) -> UserReadDTO:
|
|
404
|
+
# Consulta ultra rápida a la colección de lectura en MongoDB
|
|
405
|
+
doc = await UserReadDocument.get(query.user_id)
|
|
406
|
+
|
|
407
|
+
# O desde Redis:
|
|
408
|
+
# data = await redis_client.get(f"user:{query.user_id}")
|
|
409
|
+
|
|
410
|
+
if not doc:
|
|
411
|
+
raise UserNotFoundException()
|
|
412
|
+
return UserReadDTO(**doc.dict())
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Con este esquema, alcanzas una alta escalabilidad: tus endpoints GET son despachados por el `QueryBus` respondiendo en milisegundos desde Mongo/Redis, y tus operaciones POST/PUT/DELETE van por el `CommandBus` transaccionando con ACID en SQL.
|
|
416
|
+
|
|
417
|
+
#### 3. Definición de Modelos de Lectura (Proyecciones)
|
|
418
|
+
|
|
419
|
+
Una pregunta frecuente es: **¿HexCore genera automáticamente estos modelos de lectura?**
|
|
420
|
+
La respuesta es **No**. El patrón CQRS sugiere que tus modelos de lectura estén diseñados *específicamente* para lo que tus interfaces visuales (UI) o APIs van a consultar. Por lo tanto, debes definir estos modelos manualmente.
|
|
421
|
+
|
|
422
|
+
**Si usas MongoDB (Beanie) para lecturas:**
|
|
423
|
+
Debes crear un documento manual optimizado. Por ejemplo, en lugar de tener joins, puedes embeber datos:
|
|
424
|
+
```python
|
|
425
|
+
from beanie import Document
|
|
426
|
+
|
|
427
|
+
# Modelo desnormalizado optimizado para la lectura
|
|
428
|
+
class UserReadDocument(Document):
|
|
429
|
+
id: str # ID referenciado de la tabla SQL
|
|
430
|
+
name: str
|
|
431
|
+
email: str
|
|
432
|
+
total_purchases_cache: int = 0 # Dato pre-calculado por eventos
|
|
433
|
+
|
|
434
|
+
class Settings:
|
|
435
|
+
name = "users_read_projections"
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Si usas PostgreSQL/MySQL (SQLAlchemy) para lecturas:**
|
|
439
|
+
Si prefieres mantenerte 100% en SQL pero aislando lecturas, puedes crear tablas específicas para proyecciones (Materialized Views o tablas planas):
|
|
440
|
+
```python
|
|
441
|
+
from sqlalchemy.orm import declarative_base
|
|
442
|
+
from sqlalchemy import Column, String, Integer
|
|
443
|
+
|
|
444
|
+
Base = declarative_base()
|
|
445
|
+
|
|
446
|
+
class UserReadProjection(Base):
|
|
447
|
+
__tablename__ = 'users_read_projection'
|
|
448
|
+
|
|
449
|
+
# Modelo totalmente plano sin relaciones ForeignKey complejas
|
|
450
|
+
id = Column(String, primary_key=True)
|
|
451
|
+
full_name = Column(String)
|
|
452
|
+
email = Column(String)
|
|
453
|
+
total_purchases_cache = Column(Integer, default=0)
|
|
454
|
+
```
|
|
455
|
+
En ambos casos, es tu **EventBus** (o un consumidor como Procrastinate) el encargado de instanciar estos modelos manuales y persistirlos cada vez que se detecte un cambio en los modelos de escritura.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
### Integración con Task Queues (Smart Routing)
|
|
460
|
+
|
|
461
|
+
HexCore v2 hace que la delegación de tareas a **Celery**, **Procrastinate** o **ARQ** sea increíblemente sencilla y mágica a través del patrón de **Smart Routing**.
|
|
462
|
+
|
|
463
|
+
Ya no necesitas instanciar buses separados para código síncrono y asíncrono. HexCore enruta automáticamente tus comandos y eventos hacia las colas de background usando simples decoradores.
|
|
464
|
+
|
|
465
|
+
#### 1. Decoradores de Background
|
|
466
|
+
|
|
467
|
+
HexCore ofrece 3 decoradores esenciales en `hexcore.domain.cqrs.decorators` para cubrir todos los casos de uso:
|
|
468
|
+
|
|
469
|
+
1. **`@background_command(queue="...")`**: Aplícalo sobre una clase `Command`. Todo el comando y su handler se ejecutarán asíncronamente en el Worker. Ideal para operaciones pesadas iniciadas por el usuario (ej. Generar un reporte PDF masivo).
|
|
470
|
+
2. **`@background_handler(queue="...")`**: Aplícalo sobre una función que maneje un evento (`DomainEvent`). Permite que un solo evento dispare algunas acciones síncronas rápidas y otras asíncronas lentas (ej. Enviar emails).
|
|
471
|
+
3. **`@background_task(queue="...")`**: Aplícalo sobre funciones o utilidades genéricas que no pertenecen al modelo estricto de CQRS (ej. Limpiar base de datos, tareas tipo CRON).
|
|
472
|
+
|
|
473
|
+
**Ejemplos de uso:**
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
from hexcore.domain.cqrs.decorators import background_command, background_handler, background_task
|
|
477
|
+
from hexcore.domain.cqrs.commands import Command
|
|
478
|
+
|
|
479
|
+
# 1. Comando de ejecución asíncrona obligatoria
|
|
480
|
+
@background_command(queue="high_priority")
|
|
481
|
+
class SendEmailCommand(Command):
|
|
482
|
+
user_id: str
|
|
483
|
+
template: str
|
|
484
|
+
|
|
485
|
+
# 2. Handler de evento asíncrono
|
|
486
|
+
@background_handler(queue="analytics")
|
|
487
|
+
async def send_analytics_on_user_created(event: UserCreatedEvent):
|
|
488
|
+
# Lógica costosa...
|
|
489
|
+
pass
|
|
490
|
+
|
|
491
|
+
# 3. Tarea genérica (Non-CQRS)
|
|
492
|
+
@background_task(queue="maintenance")
|
|
493
|
+
async def clean_old_records_task(days_retention: int):
|
|
494
|
+
# Limpieza de base de datos...
|
|
495
|
+
pass
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
#### 2. Crear el Enqueuer (Adaptador)
|
|
499
|
+
|
|
500
|
+
Implementa la interfaz genérica `ITaskEnqueuer` usando la SDK de tu Task Queue favorito:
|
|
501
|
+
|
|
502
|
+
```python
|
|
503
|
+
from hexcore.domain.cqrs.task_queues import ITaskEnqueuer
|
|
504
|
+
|
|
505
|
+
class ProcrastinateEnqueuer(ITaskEnqueuer):
|
|
506
|
+
async def enqueue_command(self, command_name: str, payload: dict, queue: str) -> None:
|
|
507
|
+
await process_cqrs_command.defer_async(payload=payload)
|
|
508
|
+
|
|
509
|
+
async def enqueue_handler(self, handler_name: str, payload: dict, queue: str) -> None:
|
|
510
|
+
await process_cqrs_handler.defer_async(handler_name=handler_name, payload=payload)
|
|
511
|
+
|
|
512
|
+
async def enqueue_event(self, event_name: str, payload: dict, queue: str) -> None:
|
|
513
|
+
pass # Usualmente no se usa directamente si utilizas @background_handler
|
|
514
|
+
|
|
515
|
+
async def enqueue_task(self, task_name: str, payload: dict, queue: str) -> None:
|
|
516
|
+
await process_generic_task.defer_async(task_name=task_name, payload=payload)
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### 3. Configurar tus Buses (Enrutamiento Automático)
|
|
520
|
+
|
|
521
|
+
Al inyectar tu enqueuer en los buses estándar de memoria en tu API, estos adquieren la habilidad de enrutamiento inteligente. (Si usas un comando decorado pero no inyectas un enqueuer, HexCore levantará una excepción tempranamente).
|
|
522
|
+
|
|
523
|
+
```python
|
|
524
|
+
from hexcore.application.cqrs.in_memory_buses import InMemoryCommandBus, InMemoryEventBus
|
|
525
|
+
|
|
526
|
+
enqueuer = ProcrastinateEnqueuer()
|
|
527
|
+
serializer = PydanticSerializer()
|
|
528
|
+
|
|
529
|
+
# El bus evalúa: ¿Tiene el comando @background_command? Si es así, usa el enqueuer.
|
|
530
|
+
command_bus = InMemoryCommandBus(registry=registry, enqueuer=enqueuer, serializer=serializer)
|
|
531
|
+
|
|
532
|
+
# El bus evalúa suscriptores: ¿Tienen @background_handler? Si es así, usa el enqueuer.
|
|
533
|
+
event_bus = InMemoryEventBus(enqueuer=enqueuer, serializer=serializer)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
#### 4. Ejecutar tareas genéricas
|
|
537
|
+
|
|
538
|
+
Para encolar la tarea genérica (`@background_task`), la llamas indirectamente pasándola por el enqueuer:
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
# Así se encola una tarea genérica sin CQRS:
|
|
542
|
+
await enqueuer.enqueue_task(
|
|
543
|
+
task_name=clean_old_records_task.__cqrs_task_name__,
|
|
544
|
+
payload={"days_retention": 30},
|
|
545
|
+
queue=clean_old_records_task.__cqrs_queue__
|
|
546
|
+
)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
#### 5. Levantar el Worker (Consumidor Universal)
|
|
550
|
+
|
|
551
|
+
En el entrypoint de tu worker (ej. Celery o Procrastinate), usa el `CQRSConsumer` de HexCore para deserializar y ejecutar los payloads interceptados. El consumidor usa resolución dinámica para invocar la función correcta automáticamente.
|
|
552
|
+
|
|
553
|
+
```python
|
|
554
|
+
from hexcore.infrastructure.workers.consumer import CQRSConsumer
|
|
555
|
+
|
|
556
|
+
consumer = CQRSConsumer(
|
|
557
|
+
command_bus=command_bus, # Tu CommandBus configurado
|
|
558
|
+
event_bus=event_bus,
|
|
559
|
+
serializer=serializer
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
@app.task(name="process_cqrs_command")
|
|
563
|
+
async def process_cqrs_command(payload: dict):
|
|
564
|
+
# HexCore deserializa y ejecuta el Command usando el CommandBus local
|
|
565
|
+
await consumer.process_command(payload)
|
|
566
|
+
|
|
567
|
+
@app.task(name="process_cqrs_handler")
|
|
568
|
+
async def process_cqrs_handler(handler_name: str, payload: dict):
|
|
569
|
+
# HexCore resuelve y ejecuta exclusivamente el Event Handler asíncrono
|
|
570
|
+
await consumer.process_handler(handler_name, payload)
|
|
571
|
+
|
|
572
|
+
@app.task(name="process_generic_task")
|
|
573
|
+
async def process_generic_task(task_name: str, payload: dict):
|
|
574
|
+
# HexCore resuelve e inyecta los kwargs a la función pura
|
|
575
|
+
await consumer.process_task(task_name, payload)
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Referencias
|
|
581
|
+
|
|
582
|
+
- [CONTRIBUTING.md](./CONTRIBUTING.md): Pautas de colaboración.
|
|
583
|
+
- [CHANGELOG.md](./CHANGELOG.md): Historial de cambios.
|
|
584
|
+
- [DOCS.md](./DOCS.md): Documentación básica de clases, funciones y ejemplos.
|