El sistema utiliza dos fuentes de datos principales con estrategia CQRS (Command Query Responsibility Segregation), separando las operaciones de lectura y escritura para optimizar el rendimiento.
flowchart TD
%% Estilos globales
classDef capa fill:#cfe8fc,stroke:#4682b4,stroke-width:2px,color:#000,border-radius:10px;
classDef dbwrite fill:#d5f5e3,stroke:#2e8b57,stroke-width:2px,color:#000,border-radius:10px;
classDef dbread fill:#fce5cd,stroke:#d2691e,stroke-width:2px,color:#000,border-radius:10px;
classDef kafka fill:#f6d8f8,stroke:#8b008b,stroke-width:2px,color:#000,border-radius:10px;
classDef detalle fill:#ffffff,stroke:#888,stroke-width:1.5px,color:#000,border-radius:8px;
%% Nodos principales
A["CAPA DE APLICACION
Microservicios GraphQL"]
A -->|WRITE - Mutations| B["PostgreSQL WRITE DB
Transaccional"]
A -->|READ - Queries| C["MongoDB READ DB
Optimizada"]
%% Detalles PostgreSQL
B --> B1["Movimientos"]
B --> B2["Productos"]
B --> B3["Transacciones"]
B --> B4["ACID completo"]
%% Detalles MongoDB
C --> C1["Vista Histórico"]
C --> C2["Agregaciones"]
C --> C3["Búsquedas"]
C --> C4["Denormalizado / Sin Joins"]
%% Kafka Event Bus
B -->|Event Emitted| D["KAFKA EVENT BUS
Topic: data.sync
Latencia: 100–500 ms"]
D -->|Sync - Consistencia Eventual| C
%% Asignar estilos
class A capa;
class B dbwrite;
class C dbread;
class D kafka;
class B1,B2,B3,B4,C1,C2,C3,C4 detalle;
La consistencia eventual es apropiada para este sistema porque:
| Tipo de Consulta | Base de Datos | Justificación |
|---|---|---|
| Histórico de movimientos | MongoDB (Read) | Consistencia eventual aceptable (delay de segundos OK) |
| Dashboards y reportes | MongoDB (Read) | Datos agregados, no requieren tiempo real |
| Saldo actual de cuenta | PostgreSQL (Write) | Requiere consistencia fuerte, lectura directa |
| Validación de transferencia | PostgreSQL (Write) | Operación crítica, requiere ACID |
| Búsqueda de transacciones | MongoDB (Read) | Optimizada para búsquedas complejas |
flowchart TD
%% Estilos
classDef paso fill:#d6eaf8,stroke:#2471a3,stroke-width:2px,color:#000,border-radius:10px;
classDef dbwrite fill:#d5f5e3,stroke:#239b56,stroke-width:2px,color:#000,border-radius:10px;
classDef kafka fill:#f6d8f8,stroke:#8b008b,stroke-width:2px,color:#000,border-radius:10px;
classDef worker fill:#fdebd0,stroke:#d68910,stroke-width:2px,color:#000,border-radius:10px;
classDef dbread fill:#fcf3cf,stroke:#b7950b,stroke-width:2px,color:#000,border-radius:10px;
classDef resultado fill:#e8daef,stroke:#6c3483,stroke-width:2px,color:#000,border-radius:10px;
%% PASO 1
A["🟢 PASO 1:
Usuario realiza transferencia"]
A --> B["GraphQL Mutation → Microservicio de Pagos"]
%% PASO 2
B --> C["PASO 2:
Escritura en PostgreSQL (Write DB)"]
C --> C1["BEGIN TRANSACTION"]
C --> C2["INSERT INTO transactions(...)"]
C --> C3["UPDATE accounts SET balance = balance - amount"]
C --> C4["COMMIT TRANSACTION ✓"]
C --> C5["Transacción exitosa
(ACID garantizado)"]
%% PASO 3
C --> D["PASO 3:
Emisión de Evento a Kafka"]
D --> D1["Event:
{ type: 'TRANSACTION_CREATED',
transactionId: 'txn-12345',
accountId: 'acc-67890',
amount: 1000,
timestamp: '2025-10-06T10:30:00Z' }"]
D --> D2["Publicado en topic: data.sync"]
%% PASO 4
D --> E["PASO 4:
Consumidor procesa evento"]
E --> E1["Worker Thread procesa en paralelo"]
E --> E2["Enriquece datos:
+ info cliente, + categoría"]
E --> E3["Latencia: 100–300 ms"]
%% PASO 5
E --> F["PASO 5:
Actualización en MongoDB (Read DB)"]
F --> F1["db.transactions.insertOne({
transactionId:'txn-12345',
accountId:'acc-67890',
amount:1000,
customerName:'Juan Pérez',
category:'Transferencia',
timestamp:'2025-10-06T10:30:00Z'
})"]
F --> F2["Actualización denormalizada
(sin joins)"]
%% PASO 6
F --> G["PASO 6:
Dato disponible para consultas rápidas"]
G --> G1["Consultas desde MongoDB: < 20ms
(vs 150–200ms si fuera join en PostgreSQL)"]
%% Asignación de clases
class A,B paso;
class C,C1,C2,C3,C4,C5 dbwrite;
class D,D1,D2 kafka;
class E,E1,E2,E3 worker;
class F,F1,F2 dbread;
class G,G1 resultado;
Para optimizar aún más el rendimiento, se implementa una capa de caché con Redis para datos de clientes frecuentes.
flowchart TD
%% ==== Estilos ====
classDef ms fill:#d6eaf8,stroke:#2471a3,stroke-width:2px,color:#000,border-radius:10px;
classDef cache fill:#fdebd0,stroke:#d68910,stroke-width:2px,color:#000,border-radius:10px;
classDef db fill:#d5f5e3,stroke:#1e8449,stroke-width:2px,color:#000,border-radius:10px;
%% ==== Microservicio ====
A["MS-Datos-Cliente"]
class A ms
%% ==== Conexiones ====
A --> B["Redis
(Cache)
TTL: 1 hora
LRU eviction
Hit rate: 70%"]
A --> C["PostgreSQL
(Fuente)
Datos Cliente
Authoritative"]
%% ==== Clases ====
class B cache
class C db
| Métrica | Sin Caché | Con Redis | Mejora |
|---|---|---|---|
| Latencia promedio | 50ms | 15ms (70% hits a 2ms) | 70% |
| Queries a PostgreSQL | 10,000/min | 3,000/min | 70% |
| Carga CPU PostgreSQL | 60% | 20% | 67% |
| Throughput (req/seg) | 500 | 1,500 | 200% |
-- Tabla de Cuentas
CREATE TABLE accounts (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL,
account_number VARCHAR(20) UNIQUE NOT NULL,
balance DECIMAL(15,2) NOT NULL DEFAULT 0,
account_type VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Tabla de Transacciones (Write Model)
CREATE TABLE transactions (
id UUID PRIMARY KEY,
account_id UUID NOT NULL REFERENCES accounts(id),
transaction_type VARCHAR(50) NOT NULL,
amount DECIMAL(15,2) NOT NULL,
balance_after DECIMAL(15,2) NOT NULL,
description TEXT,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
idempotency_key VARCHAR(100) UNIQUE
);
-- Índices optimizados para escritura
CREATE INDEX idx_transactions_account ON transactions(account_id);
CREATE INDEX idx_transactions_created ON transactions(created_at DESC);
// Colección de Transacciones (Read Model - Denormalizado)
{
"_id": "txn-12345",
"transactionId": "txn-12345",
"accountId": "acc-67890",
"accountNumber": "1234567890",
// Datos denormalizados del cliente
"customer": {
"id": "cust-111",
"name": "Juan Pérez",
"email": "juan@example.com"
},
// Datos de la transacción
"type": "TRANSFER_OUT",
"amount": 1000.00,
"balanceAfter": 48000.00,
"description": "Transferencia a cuenta externa",
"category": "Transferencia",
"status": "COMPLETED",
// Metadata enriquecida
"timestamp": ISODate("2025-10-06T10:30:00Z"),
"tags": ["transfer", "external", "high-amount"],
// Índices para búsquedas rápidas
"searchableText": "transferencia juan perez 1000"
}
// Índices optimizados para lectura
db.transactions.createIndex({ "accountId": 1, "timestamp": -1 })
db.transactions.createIndex({ "customer.id": 1 })
db.transactions.createIndex({ "searchableText": "text" })
flowchart TD
A["Primary
(Read+Write)"] -- Streaming Replication
(Asíncrona) --> R1["Replica 1
(Read-only)
Zona AZ-A"]
A --> R2["Replica 2
(Read-only)
Zona AZ-B"] & R3["Replica 3
(Read-only)
Zona AZ-C"]
A@{ shape: rounded}
R1@{ shape: rounded}
R2@{ shape: rounded}
R3@{ shape: rounded}
A:::pastel
A:::Peach
A:::Pine
R1:::pastel
R1:::Ash
R1:::Peach
R2:::pastel
R2:::Peach
R3:::pastel
R3:::Peach
classDef pastel fill:#ffe7ba,stroke:#a88039,stroke-width:2px,color:#463918
classDef Pine stroke-width:1px, stroke-dasharray:none, stroke:#254336, fill:#27654A, color:#FFFFFF
classDef Ash stroke-width:1px, stroke-dasharray:none, stroke:#999999, fill:#EEEEEE, color:#000000
classDef Peach stroke-width:1px, stroke-dasharray:none, stroke:#FBB35A, fill:#FFEFDB, color:#8F632D
| Tipo de Backup | Frecuencia | Retención | RTO | RPO |
|---|---|---|---|---|
| Full Backup PostgreSQL | Semanal (Domingo 2am) | 3 meses | 30 min | 7 días |
| Incremental PostgreSQL | Diario (2am) | 30 días | 20 min | 1 día |
| WAL Logs PostgreSQL | Continuo | 7 días | 15 min | 5 min |
| MongoDB Snapshot | Cada 6 horas | 48 horas | 10 min | 6 horas |
| Redis Persistence (RDB) | Cada hora | 24 horas | 5 min | 1 hora |