Appearance
Arquitectura de Módulos — Modules/<Module>
Esta guía describe la arquitectura modular introducida en Sistema Bautista a partir de los módulos CRM y Membresía. Define la estructura de carpetas, la responsabilidad de cada capa y las convenciones que deben seguirse al crear un módulo nuevo con este patrón.
Por qué existe esta arquitectura
La arquitectura Slim Framework de 5 capas (Routes → Controller → Service → Model → DB) funciona bien para módulos CRUD simples. Sin embargo, cuando un módulo contiene subdominios complejos —facturación, eventos de dominio, repositorios con implementaciones intercambiables— la organización en carpetas raíz (service/, models/, controller/) dispersa el código de un mismo módulo en múltiples lugares del proyecto.
La estructura Modules/<Module> agrupa todo el código de un módulo en una sola carpeta, con separación interna por capas DDD. Esto facilita:
- Navegar y entender un módulo sin saltar entre carpetas raíz.
- Aplicar Domain-Driven Design con Domain, Application e Infrastructure diferenciados.
- Definir contratos públicos explícitos para la integración entre módulos.
- Escalar un módulo complejo sin impacto en el resto del sistema.
Módulos actuales
| Módulo | Ruta | Complejidad |
|---|---|---|
| CRM | Modules/Crm/ | Media — sin repositorios ni eventos de dominio |
| Membresía | Modules/Membresia/ | Alta — con repositorios, eventos, subdominio de facturación |
Árbol de directorios completo
La siguiente estructura toma como referencia el módulo Membresía, que es el más completo. Las carpetas marcadas con (*) están presentes sólo en Membresía.
Modules/<Module>/
│
├── Application/
│ ├── Services/ # Casos de uso que orquestan el dominio
│ │ ├── Cache/ # Servicios con caché integrado
│ │ ├── ExternalData/ # Datos desde sistemas externos (CRM)
│ │ ├── Facturacion/ # Subdominio de facturación (*)
│ │ ├── Grouping/ # Agrupaciones de entidades (*)
│ │ └── Validation/ # Validaciones de negocio complejas (*)
│ └── Validators/ # Validadores de estructura HTTP (middleware)
│
├── Contracts/ # Interfaces públicas del módulo
│
├── Domain/
│ ├── Contracts/ # Interfaces de repositorio del dominio (*)
│ ├── Events/ # Eventos de dominio
│ │ ├── Facturacion/ # Eventos del subdominio de facturación (*)
│ │ └── Listeners/ # Listeners de eventos
│ ├── Facturacion/ # Subdominio de facturación (*)
│ │ ├── Aggregates/ # Raíces de agregado
│ │ ├── Builder/ # Constructores de agregados
│ │ │ ├── Exceptions/
│ │ │ └── Traits/
│ │ ├── Entities/ # Entidades del subdominio
│ │ ├── Enums/ # Enumeraciones tipadas
│ │ ├── Exceptions/ # Excepciones de dominio
│ │ ├── Interface/ # Interfaces internas del subdominio
│ │ ├── Providers/ # Proveedores de datos de dominio
│ │ ├── Repositories/ # Interfaces de repositorio del subdominio
│ │ ├── Services/ # Servicios de dominio puros
│ │ └── ValueObjects/ # Objetos de valor inmutables
│ └── Shared/
│ └── DTO/ # DTOs compartidos entre capas del dominio
│
├── Infrastructure/
│ ├── Documentation/ # Clases de documentación OpenAPI
│ │ └── Resources/ # Recursos OpenAPI por entidad
│ ├── Http/
│ │ ├── Controllers/ # Controladores Slim (HTTP puro)
│ │ └── Routes/ # Definición de rutas Slim
│ └── Persistence/
│ ├── Models/ # Modelos de acceso a base de datos (PDO)
│ ├── Queries/ # Query builders especializados (*)
│ └── Repositories/ # Implementaciones concretas de repositorios (*)
│
└── Presentation/
└── DTOs/ # DTOs de request y response HTTP
├── Disciplina/ # DTOs por entidad (ejemplo Membresía)
├── Enums/
├── Facturacion/
└── ...Descripción de cada capa
Domain
Contiene la lógica de negocio pura del módulo. No tiene dependencias de frameworks, bases de datos, ni HTTP.
Qué va aquí:
- Entities: Objetos con identidad que encapsulan comportamiento de negocio.
- ValueObjects: Objetos inmutables que representan conceptos del dominio (ej.
Importe,FechaVencimiento). - Aggregates: Raíces de agregado que garantizan consistencia interna.
- Enums: Enumeraciones tipadas para estados y categorías del dominio.
- Exceptions: Excepciones que representan violaciones de reglas de negocio.
- Repositories (interfaces): Contratos de persistencia definidos por el dominio, no por la infraestructura.
- Services: Servicios de dominio puros para lógica que no pertenece a una sola entidad.
- Events: Eventos que ocurren como resultado de operaciones de dominio.
- Listeners: Manejadores de eventos de dominio.
- Shared/DTO: DTOs de transferencia interna entre componentes del dominio.
Regla fundamental: ninguna clase en Domain/ importa clases de Infrastructure/, Application/, ni frameworks externos.
Application
Orquesta los objetos de dominio para implementar casos de uso. Coordina transacciones, validaciones de negocio y flujos de trabajo compuestos.
Qué va aquí:
- Services: Implementan casos de uso concretos. Invocan entidades, repositorios y servicios de dominio. Gestionan transacciones.
- Services/Cache: Variantes de servicios con caché para operaciones de lectura frecuente.
- Services/Validation: Validadores de reglas de negocio complejas que requieren consultas a la base de datos.
- Validators: Validadores de estructura HTTP. Se aplican como middleware en las rutas. Solo verifican tipos, formatos y presencia de campos. Devuelven 422 si fallan.
Diferencia entre Validators y Services/Validation:
Application/Validators/ | Application/Services/Validation/ | |
|---|---|---|
| Cuándo se ejecuta | Antes del Controller (middleware) | Dentro del Service (tras el Controller) |
| Qué valida | Estructura: tipos, formatos, campos requeridos | Reglas de negocio: unicidad, existencia de relaciones, límites |
| Accede a DB | No | Sí |
| Respuesta en fallo | HTTP 422 | Excepción de negocio / HTTP 422 |
Infrastructure
Implementa los detalles técnicos: HTTP, base de datos, documentación. Depende del dominio pero el dominio no depende de ella.
Qué va aquí:
- Http/Controllers: Controladores Slim. Solo manejan HTTP: extraen datos del request, invocan servicios de Application y construyen la respuesta. Sin lógica de negocio.
- Http/Routes: Definición de rutas Slim con sus middlewares de validación.
- Persistence/Models: Modelos de acceso a datos con PDO directo. Ejecutan SQL, mapean resultados a DTOs, implementan soft delete.
- Persistence/Repositories: Implementaciones concretas de las interfaces definidas en
Domain/Contracts/oDomain/.../Repositories/. Usan Doctrine DBAL o PDO. - Persistence/Queries: Query builders especializados para consultas complejas que no encajan en un Model simple.
- Documentation: Clases que generan la especificación OpenAPI del módulo.
Presentation
Capa de transferencia de datos HTTP. Contiene únicamente DTOs organizados por entidad.
Qué va aquí:
- DTOs de request: Objetos que representan los datos que llegan del cliente. Generalmente creados por el Controller a partir del body HTTP.
- DTOs de response: Objetos que representan los datos que se devuelven al cliente.
- Enums de presentación: Enumeraciones usadas en la serialización de respuestas.
Los DTOs de Presentation/ no contienen lógica de negocio. Solo tienen propiedades tipadas y métodos de construcción (fromArray()) y serialización (toArray()).
Contracts
Interfaces públicas que el módulo expone al exterior. Permiten que otros módulos dependan de una abstracción en lugar de una implementación concreta.
Cuándo usar Contracts/ vs Domain/Contracts/:
Contracts/ (raíz del módulo) | Domain/Contracts/ | |
|---|---|---|
| Audiencia | Otros módulos del sistema | Implementaciones de infraestructura del mismo módulo |
| Ejemplo | MiembrosServiceInterface para que otro módulo consulte miembros | CategoriaMembresiaRepository para que Doctrine implemente |
Comparación con la arquitectura Slim (legacy del proyecto)
| Aspecto | Slim 5 capas (carpetas raíz) | Modules/<Module> (DDD) |
|---|---|---|
| Organización | Por tipo de archivo (service/, models/, controller/) | Por módulo (Modules/Crm/, Modules/Membresia/) |
| Cohesión | Todo el sistema en cada carpeta raíz | Todo el módulo en una carpeta |
| Dominio | Sin capa de dominio explícita | Domain/ con entidades, VOs, eventos |
| Repositorios | Model como repositorio implícito | Interfaz en dominio + implementación en infraestructura |
| Contratos | Sin contratos entre módulos | Contracts/ explícita |
| Complejidad | Baja — adecuada para CRUD simple | Alta — necesaria para subdominios complejos |
| Namespace | App\service\Ventas\... | Membresia\Application\Services\... |
| Cuándo usarla | Módulos con lógica de negocio directa | Módulos con subdominios, eventos o repositorios intercambiables |
La arquitectura Modules/<Module> no reemplaza la arquitectura Slim en todos los casos. Para módulos con CRUD directo sin lógica compleja, la estructura de 5 capas sigue siendo apropiada. Usar Modules/<Module> cuando el módulo justifica la separación adicional.
Flujo de una request
HTTP Request
↓
Infrastructure/Http/Routes/<Entidad>Route.php
↓ middleware
Application/Validators/<Entidad>Validator.php → 422 si falla
↓
Infrastructure/Http/Controllers/<Entidad>Controller.php
↓ instancia DTOs de Presentation/DTOs/
Application/Services/<Servicio>.php
↓ valida reglas de negocio
↓ gestiona transacción
Domain/... → lógica pura de negocio
↓
Infrastructure/Persistence/Repositories/<Impl> → acceso a DB
↓ o
Infrastructure/Persistence/Models/<Modelo> → acceso a DB
↓
PostgreSQL (schema del tenant activo)Cuándo crear un nuevo módulo con esta estructura
Usar Modules/<Module> cuando el módulo cumple al menos dos de estas condiciones:
- Tiene más de un subdominio con lógica propia (ej. facturación separada del core del módulo).
- Necesita repositorios con implementaciones intercambiables (ej. Doctrine vs PDO).
- Genera eventos de dominio que otros componentes deben escuchar.
- Expone contratos que otros módulos consumen (más allá de llamadas directas a servicios).
- El módulo tiene dominio rico: entidades con comportamiento, value objects, reglas de negocio que no dependen de DB.
Para módulos CRUD simples sin lógica compleja, continuar usando la estructura de 5 capas en carpetas raíz.
Guía para crear un módulo nuevo
1. Estructura mínima de partida
Para un módulo nuevo, comenzar con las carpetas esenciales:
Modules/<NuevoModulo>/
├── Application/
│ ├── Services/
│ └── Validators/
├── Contracts/
├── Domain/
├── Infrastructure/
│ ├── Http/
│ │ ├── Controllers/
│ │ └── Routes/
│ └── Persistence/
│ └── Models/
└── Presentation/
└── DTOs/Agregar subcarpetas adicionales (Domain/Events/, Domain/Facturacion/, Persistence/Repositories/) sólo cuando el módulo las necesite concretamente.
2. Registrar las rutas en el entry point
Las rutas del módulo se registran en index.php o en el archivo de configuración de rutas Slim del proyecto. El archivo principal de rutas del módulo sigue la convención <Modulo>Routes.php.
3. Registrar los servicios en el contenedor DI
Los servicios de Application y las implementaciones de repositorio se registran en el contenedor PHP-DI. Las interfaces del dominio deben mapearse a sus implementaciones de Infrastructure.
4. Configurar el autoloader
El namespace raíz del módulo (ej. Membresia\) debe estar registrado en composer.json bajo autoload.psr-4.
Convenciones de nomenclatura
Namespaces PHP
Los módulos usan el nombre del módulo como namespace raíz, sin prefijo App\:
Membresia\Application\Services\<NombreService>
Membresia\Application\Validators\<Entidad>Validator
Membresia\Contracts\<Nombre>Interface
Membresia\Domain\Facturacion\Entities\<Entidad>
Membresia\Domain\Facturacion\ValueObjects\<Nombre>
Membresia\Domain\Events\<Nombre>Event
Membresia\Infrastructure\Http\Controllers\<Entidad>Controller
Membresia\Infrastructure\Http\Routes\<Entidad>Route
Membresia\Infrastructure\Persistence\Models\<Nombre>
Membresia\Infrastructure\Persistence\Repositories\Doctrine<Nombre>Repository
Membresia\Presentation\DTOs\<Entidad>\<Nombre>DTO
Crm\Application\Services\<NombreService>
Crm\Infrastructure\Http\Routes\<Nombre>RoutesClases
| Tipo | Sufijo | Ejemplo |
|---|---|---|
| Controller | Controller | DisciplinaController |
| Route | Route o Routes | DisciplinaRoute, MembresiaRoutes |
| Application Service | Service | DisciplinaService, MembresiaCacheService |
| Domain Service | Service | FacturacionDomainService |
| Validator (HTTP) | Validator | DisciplinaValidator |
| Model (persistencia PDO) | sin sufijo o Model | Disciplina, DisciplinaModel |
| Repository interface | Repository o RepositoryInterface | CategoriaMembresiaRepository |
| Repository implementation | Doctrine<Nombre>Repository | DoctrineCategoriaMembresiaRepository |
| Aggregate | sin sufijo especial | FacturacionAggregate |
| Value Object | sin sufijo especial | ImporteFactura, FechaVencimiento |
| Event | Event | MiembroBajaEvent, MiembroReactivacionEvent |
| Listener | Listener | MiembroBajaListener |
| DTO de presentación | DTO o Request | DisciplinaDTO, CreateDisciplinaRequest |
| Contratos públicos | Interface o ServiceInterface | MiembrosServiceInterface |
Archivos de rutas
Cada entidad principal tiene su propio archivo de rutas. Existe además un archivo maestro del módulo que los agrupa e importa:
Infrastructure/Http/Routes/
├── <Modulo>Routes.php # Archivo maestro: registra el grupo principal
├── <Entidad1>Route.php # Rutas de la entidad 1
└── <Entidad2>Route.php # Rutas de la entidad 2Multi-tenancy en módulos
Los módulos bajo Modules/<Module> respetan el mismo modelo de multi-tenancy del sistema. El ConnectionManager está disponible para inyección y la conexión activa respeta el search_path configurado por el middleware de conexión a partir del header X-Schema.
Cada tabla creada para un módulo debe tener definido su nivel de schema en las migraciones:
- EMPRESA: datos compartidos entre todas las sucursales de una empresa.
- SUCURSAL: datos del schema de la sucursal activa.
- CAJA: datos del schema de la caja activa.
La separación oficial/prueba (base de datos con sufijo _p) aplica de la misma manera que en el resto del backend.
Referencias
- Arquitectura Backend Moderna (Slim 5 capas)
- Arquitectura Backend Legacy (deprecada)
- Patrones de multi-tenancy
- Service Decomposition Pattern
Última actualización: 2026-02-18