Lineamientos de Arquitectura — Plataforma Imagy
Principios Fundamentales
- JWT Propagation: El JWT se propaga completo entre servicios. Cada servicio valida independientemente. Nunca headers custom para identidad.
- Database-per-Domain: Cada dominio de negocio tiene su propia base de datos. Nunca acceder a la BD de otro servicio.
- Event-Driven Communication: Comunicación asíncrona via RabbitMQ (MassTransit) entre dominios. Síncrona solo cuando se necesita respuesta inmediata.
- Multi-Tenancy por RLS: Todas las tablas con
tenant_id. PostgreSQL Row Level Security activo. Nunca queries sin filtro de tenant. - Maker-Checker: Cambios críticos (productos, roles, configuraciones) requieren aprobación configurable por tenant.
- Trazabilidad Total: Todo evento se publica. Cada acción deja rastro. Subject 360 consolida la vista del cliente.
- FTR-Compliant: Toda decisión de infraestructura debe cumplir requisitos de AWS Foundational Technical Review.
Stack Tecnológico (NO negociable)
| Componente | Tecnología | Versión mínima |
|---|---|---|
| Backend | .NET 10 | 10.0 |
| Base de datos | PostgreSQL | 16 |
| ORM (escritura) | Entity Framework Core | 10.0 |
| Queries (lectura) | Dapper | 2.x |
| Cache | Valkey (Redis-compatible) | 8.x |
| Mensajería | RabbitMQ (MassTransit) | 8.x |
| Auth | Keycloak | 24.x |
| API Gateway | YARP | 2.x |
| Frontend | React 19 + Vite 8 + TypeScript 6 | — |
| State Management (FE) | Zustand | 5.x |
| Forms (FE) | React Hook Form + Zod | 7.x / 3.x |
| Testing (BE) | xUnit + FluentAssertions | — |
| Testing (FE) | Vitest + Testing Library | — |
| IaC | AWS CDK (TypeScript) | 2.x |
| Containers | Docker (multi-stage builds) | — |
| Orchestration (AWS) | ECS Fargate | — |
| Orchestration (local) | Docker Compose | — |
Patrón CQRS: EF Core (escritura) + Dapper (lectura)
- Escritura (Commands): Entity Framework Core contra la instancia primaria de PostgreSQL
- Lectura (Queries): Dapper contra la instancia read replica de PostgreSQL
csharp
// Dos connection strings por servicio
public class DatabaseSettings
{
public string WriteConnectionString { get; set; } // → RDS Primary
public string ReadConnectionString { get; set; } // → RDS Read Replica
}| Operación | Herramienta | Instancia DB |
|---|---|---|
| INSERT, UPDATE, DELETE | EF Core | Primary |
| SELECT (listados, filtros, reportes) | Dapper | Read Replica |
| SELECT post-escritura (read-your-writes) | EF Core | Primary |
Estructura de Proyecto por Servicio
src/
├── Imagy.{Domain}.Api/ # Controllers, middleware, DI config
├── Imagy.{Domain}.Application/ # Use cases, commands, queries (MediatR)
├── Imagy.{Domain}.Domain/ # Entidades, value objects, interfaces
├── Imagy.{Domain}.Infrastructure/ # EF Core, Dapper, RabbitMQ, HTTP clients
│ ├── Persistence/ # EF Core DbContext (Primary)
│ ├── ReadModel/ # Dapper repositories (Read Replica)
│ ├── Messaging/ # MassTransit consumers/publishers
│ └── ExternalServices/ # HTTP clients a otros servicios
├── Imagy.{Domain}.Migrations/ # DbUp - scripts SQL versionados
│ ├── Scripts/ # V001__, V002__, etc.
│ ├── Seed/ # S001__, S002__, etc.
│ └── Program.cs # Runner
└── Imagy.{Domain}.Tests/ # Unit + Integration testsMulti-Tenancy
- El
tenant_idse extrae del JWT (claimtenant_id) validado por middleware .NET - Cada servicio usa
IIdentityContextpara acceder al tenant/user actual - EF Core interceptor ejecuta
SET app.current_tenant_iden cada conexión - Dapper repositories ejecutan el mismo SET antes de cada query
- En llamadas servicio-a-servicio, el JWT se propaga automáticamente
- En consumers de eventos, el
TenantIdviene del payload del mensaje - NUNCA hacer queries sin tenant context (excepto platform_admin endpoints)
- NUNCA usar headers custom (X-Tenant-Id) — siempre JWT firmado
Comunicación entre Servicios
| Tipo | Cuándo | Mecanismo |
|---|---|---|
| Evento async | Notificaciones, actualización de Subject 360, auditoría | RabbitMQ (MassTransit) |
| Query síncrona | Servicio necesita datos de otro para decidir | HTTP + JWT propagado |
| Command síncrono | Servicio dispara acción en otro | HTTP + JWT propagado |
Reglas de Integración
- No acceder a la DB de otro servicio directamente — siempre via API o evento
- No duplicar lógica de negocio — si necesitas algo de otro servicio, consúmelo
- Eventos son contratos — cambiar la estructura requiere coordinación
- Backward compatibility — agregar campos OK, eliminar/renombrar NUNCA
- Idempotencia — todos los consumers deben ser idempotentes
Convenciones de Código
Naming
- Tablas: snake_case plural (
flow_versions,credit_products) - Columnas: snake_case (
tenant_id,created_at) - Primary Keys: UUID v4, columna
id - Foreign Keys:
{entity}_id - Timestamps:
timestamp with time zone(UTC siempre) - Soft Delete:
is_activeboolean (nunca DELETE físico en datos de negocio) - JSON:
jsonbpara datos semi-estructurados - Namespaces .NET:
Imagy.{Domain}.{Layer}(ej:Imagy.Lending.Application) - Interfaces: Prefijo
I(ej:IIdentityContext,ICreditProductRepository)
Endpoints API
- Prefijo:
/api/v{version}/{resource} - Versionamiento: URL path segment usando
Asp.Versioning.Mvc - Respuestas: envelope
{ data, errors, metadata } - Paginación:
?page=1&page_size=20→{ total, page, page_size } - Filtros: query params
Eventos RabbitMQ
- Exchange:
imagy.events(topic) - Routing key:
{domain}.{entity}.{action}(ej:lending.credit.disbursed) - Payload: estructura base obligatoria (ver
event-contracts.md)
Dominios de Negocio
| Dominio | Servicio | Base de Datos | Responsabilidad |
|---|---|---|---|
| Identity | Imagy Identity (Gateway + Management) | imagy_identity | Auth, tenants, orgs, sesiones, RBAC |
| Flow | ImagFlow Engine | imagy_flow | Diseño y ejecución de flujos, proveedores, reglas |
| Lending | ImagLend | imagy_lending | Productos de crédito, originación, cartera |
| Sign | ImagSign | imagy_sign | Firma digital/electrónica, documentos |
| Subject | ImagID (Subject 360) | imagy_subject | Perfil unificado, dispositivos, listas, scoring |
| Compliance | ImagGuard (futuro) | imagy_compliance | KYC, AML, PEP |