Plataforma técnica · Orchestrator
Observabilidad
Orchestrator Observabilidad Audit
La observabilidad del Orchestrator es infraestructura cross-cutting: vive fuera del dominio Command y atraviesa todas las peticiones HTTP. Se compone de dos subsistemas complementarios:
Cómo se relaciona con el dominio Command
Section titled “Cómo se relaciona con el dominio Command”| Componente | Vive en | Naturaleza |
|---|---|---|
auditLogger | lib/audit.ts | Middleware + API explícita. |
metricsMiddleware | middleware/metrics.ts | Middleware + API explícita. |
MonitoringService (dashboard) | domain/command/MonitoringService.ts | Consumidor: lee los agregados que estos dos producen. |
El Monitoring Dashboard del Command Center es la capa de presentación sobre esta infraestructura — no la implementa, solo la lee desde monitoring.v_dashboard_summary.
Arquitectura
Section titled “Arquitectura”Ambos subsistemas son singletons que se enganchan al pipeline de middleware de Express. Mantienen estado en memoria (cola/contadores) y persisten por lotes a tablas PostgreSQL bajo el esquema monitoring en nostromo_command.
flowchart TB
%% =========================
%% Pila de Middleware Express
%% =========================
subgraph Express["Pila de Middleware Express"]
TrustProxy[trust proxy]
HTTPS[Redirección HTTPS]
Helmet[seguridad helmet]
Limiter[globalLimiter]
CORS[cors]
Morgan[registro morgan]
RequestMW[requestMiddleware]
MetricsMW[metricsMiddleware]
AuditMW[auditMiddleware]
Routes[Rutas API]
TrustProxy --> HTTPS
HTTPS --> Helmet
Helmet --> Limiter
Limiter --> CORS
CORS --> Morgan
Morgan --> RequestMW
RequestMW --> MetricsMW
MetricsMW --> AuditMW
end
%% =========================
%% Bases de datos
%% =========================
subgraph DBs["PostgreSQL · nostromo_command"]
AuditDB[(monitoring.audit_log)]
MetricsDB[(monitoring.system_metrics)]
end
%% =========================
%% Sistema de Auditoría
%% =========================
subgraph AuditSys["Sistema de Auditoría"]
AuditLogger[AuditLogger Singleton]
AuditAPI[API auditLog]
AuditBuffer[Cola en Memoria]
AuditTimer[Flush 5s o 50 entradas]
AuditLogger --> AuditAPI
AuditAPI --> AuditBuffer
AuditBuffer --> AuditTimer
AuditTimer -->|INSERT por lotes| AuditDB
end
%% =========================
%% Sistema de Métricas
%% =========================
subgraph MetricsSys["Sistema de Métricas"]
MetricsCollector[MetricsCollector Singleton]
MetricsAPI[API metrics]
MetricsStore[Counters / Gauges / Histograms]
MetricsTimer[Flush 60s]
MetricsEndpoint[GET /metrics]
MetricsCollector -->|Sistema cada 30s| MetricsStore
MetricsAPI --> MetricsStore
MetricsStore --> MetricsTimer
MetricsTimer -->|INSERT por lotes| MetricsDB
MetricsCollector --> MetricsEndpoint
end
MetricsMW -->|Rastrear petición| MetricsCollector
AuditMW -->|Registrar mutación| AuditLogger
Routes -->|Llamadas explícitas| AuditAPI
Routes -->|Llamadas explícitas| MetricsAPI
AuditMW --> Routes Posición en la pila
Section titled “Posición en la pila”El orden importa:
- Antes de autenticación: métricas y audit capturan todos los intentos, incluso fallos de login. El
auditMiddlewareexcluye explícitamente/api/auth/login(que se loguea manualmente con éxito/fallo desde el handler). - Después de
requestIdMiddleware: ambas operan conreq.id(correlación). MetricsMWantes queAuditMW: para medir el rendimiento “puro” sin contar el costo del audit.
Apagado Elegante
Section titled “Apagado Elegante”Ambos sistemas registran un hook process.on('beforeExit') para vaciar el buffer:
process.on('beforeExit', async () => {await auditLogger.shutdown(); // Flush entradas pendientesawait metrics.shutdown(); // Flush métricas pendientes});Garantiza que un SIGTERM ordenado (Docker stop, systemd, Cloudflare cycle) no pierda los últimos eventos.
Patrones de Uso
Section titled “Patrones de Uso”Audit explícito desde un handler
Section titled “Audit explícito desde un handler”import { auditLog, extractAuditContext } from '@/lib/audit';
const ctx = extractAuditContext(req);await centralPool.query('INSERT INTO employees ...');auditLog.insert('remuneraciones.employees', newEmployee, ctx);Métrica personalizada
Section titled “Métrica personalizada”const timer = createTimer();const result = await payrollEngine.calculate(input);timer.observe('payroll_calculation_duration_ms', {employee_count: input.employees.length});