Repository navigation
Expand file tree
/
Copy pathExpSagaPattern.java
More file actions
284 lines (239 loc) · 13.1 KB
/
Copy pathExpSagaPattern.java
File metadata and controls
284 lines (239 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
import java.util.*;
/**
* Implementación del patrón Saga coreografiado con Java puro.
*
* Saga "Crear Pedido":
* PedidoService → InventarioService → PagoService → EnvioService
*
* Si un paso falla, se emiten eventos de compensación en orden inverso:
* PagoService falla → InventarioService libera stock → PedidoService cancela pedido
*
* Componentes:
* - EventBus: bus de eventos simple (publish/subscribe)
* - Cada servicio escucha sus eventos y emite el siguiente
* - Compensaciones: cada paso tiene su evento de rollback
*/
public class ExpSagaPattern {
// ─────────────────────────────────────────────
// EVENT BUS
// ─────────────────────────────────────────────
static class EventBus {
private final Map<String, List<java.util.function.Consumer<Map<String, Object>>>> handlers =
new HashMap<>();
void suscribir(String evento, java.util.function.Consumer<Map<String, Object>> handler) {
handlers.computeIfAbsent(evento, k -> new ArrayList<>()).add(handler);
}
void publicar(String evento, Map<String, Object> datos) {
System.out.printf(" [EventBus] ← %s %s%n", evento, datos);
List<java.util.function.Consumer<Map<String, Object>>> subs =
handlers.getOrDefault(evento, Collections.emptyList());
for (var handler : subs) {
handler.accept(datos);
}
}
}
// Helper para crear datos del evento
static Map<String, Object> evento(Object... pares) {
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < pares.length; i += 2) {
map.put((String) pares[i], pares[i + 1]);
}
return map;
}
// ─────────────────────────────────────────────
// SERVICIOS DE LA SAGA
// ─────────────────────────────────────────────
// ── Servicio de Pedidos ────────────────────────────────────────────
static class PedidoService {
private final EventBus bus;
private final Map<String, String> pedidos = new HashMap<>(); // id → estado
PedidoService(EventBus bus) {
this.bus = bus;
// Paso 1: Crear pedido
bus.suscribir("CrearPedido", datos -> {
String pedidoId = (String) datos.get("pedidoId");
String producto = (String) datos.get("producto");
System.out.printf(" [PedidoService] Creando pedido %s para producto '%s'%n",
pedidoId, producto);
pedidos.put(pedidoId, "CREADO");
// Emitir evento para que InventarioService reserve stock
bus.publicar("PedidoCreado", evento(
"pedidoId", pedidoId,
"producto", producto,
"cantidad", datos.get("cantidad")));
});
// Compensación: cancelar pedido si algo falló después
bus.suscribir("CancelarPedido", datos -> {
String pedidoId = (String) datos.get("pedidoId");
String motivo = (String) datos.get("motivo");
pedidos.put(pedidoId, "CANCELADO");
System.out.printf(" [PedidoService] COMPENSACIÓN: Pedido %s CANCELADO ← motivo: %s%n",
pedidoId, motivo);
});
// Completar pedido si todo fue bien
bus.suscribir("EnvioCreado", datos -> {
String pedidoId = (String) datos.get("pedidoId");
pedidos.put(pedidoId, "COMPLETADO");
System.out.printf(" [PedidoService] Pedido %s COMPLETADO exitosamente%n", pedidoId);
});
}
void crearPedido(String pedidoId, String producto, int cantidad) {
System.out.printf("%n[Saga] Iniciando: pedidoId=%s producto='%s' cantidad=%d%n",
pedidoId, producto, cantidad);
System.out.println("─".repeat(65));
bus.publicar("CrearPedido", evento(
"pedidoId", pedidoId,
"producto", producto,
"cantidad", cantidad));
}
String estadoPedido(String pedidoId) {
return pedidos.getOrDefault(pedidoId, "DESCONOCIDO");
}
}
// ── Servicio de Inventario ─────────────────────────────────────────
static class InventarioService {
private final EventBus bus;
private final Map<String, Integer> stock = new HashMap<>();
private final Map<String, Integer> reservas = new HashMap<>(); // pedidoId → cantidad reservada
InventarioService(EventBus bus) {
this.bus = bus;
stock.put("laptop", 5);
stock.put("mouse", 0); // sin stock para simular fallo
// Paso 2: Reservar stock
bus.suscribir("PedidoCreado", datos -> {
String pedidoId = (String) datos.get("pedidoId");
String producto = (String) datos.get("producto");
int cantidad = (int) datos.get("cantidad");
int stockDisponible = stock.getOrDefault(producto, 0);
System.out.printf(" [InventarioService] Stock disponible de '%s': %d, solicitado: %d%n",
producto, stockDisponible, cantidad);
if (stockDisponible >= cantidad) {
stock.put(producto, stockDisponible - cantidad);
reservas.put(pedidoId, cantidad);
System.out.printf(" [InventarioService] Stock reservado para pedido %s%n", pedidoId);
bus.publicar("StockReservado", evento(
"pedidoId", pedidoId,
"producto", producto,
"cantidad", cantidad));
} else {
System.out.printf(" [InventarioService] FALLO: Stock insuficiente para '%s'%n", producto);
// Compensación directa hacia PedidoService
bus.publicar("CancelarPedido", evento(
"pedidoId", pedidoId,
"motivo", "Stock insuficiente de: " + producto));
}
});
// Compensación: liberar stock si el pago falló
bus.suscribir("LiberarStock", datos -> {
String pedidoId = (String) datos.get("pedidoId");
String producto = (String) datos.get("producto");
Integer cantidad = reservas.remove(pedidoId);
if (cantidad != null) {
stock.merge(producto, cantidad, Integer::sum);
System.out.printf(" [InventarioService] COMPENSACIÓN: Stock liberado para pedido %s " +
"(producto '%s' +%d)%n", pedidoId, producto, cantidad);
}
// Propagar compensación hacia PedidoService
bus.publicar("CancelarPedido", evento(
"pedidoId", pedidoId,
"motivo", (String) datos.get("motivo")));
});
}
}
// ── Servicio de Pagos ──────────────────────────────────────────────
static class PagoService {
private final EventBus bus;
private boolean simularFalloPago = false;
PagoService(EventBus bus) {
this.bus = bus;
// Paso 3: Procesar pago
bus.suscribir("StockReservado", datos -> {
String pedidoId = (String) datos.get("pedidoId");
String producto = (String) datos.get("producto");
System.out.printf(" [PagoService] Procesando pago para pedido %s...%n", pedidoId);
if (simularFalloPago) {
System.out.printf(" [PagoService] FALLO: Pago rechazado (fondos insuficientes)%n");
// Compensación: liberar el stock reservado
bus.publicar("LiberarStock", evento(
"pedidoId", pedidoId,
"producto", producto,
"cantidad", datos.get("cantidad"),
"motivo", "Pago rechazado"));
} else {
System.out.printf(" [PagoService] Pago aprobado para pedido %s%n", pedidoId);
bus.publicar("PagoCompletado", evento(
"pedidoId", pedidoId,
"producto", producto,
"cantidad", datos.get("cantidad")));
}
});
}
void activarFalloPago() { simularFalloPago = true; }
}
// ── Servicio de Envío ──────────────────────────────────────────────
static class EnvioService {
private final EventBus bus;
EnvioService(EventBus bus) {
this.bus = bus;
// Paso 4: Crear envío
bus.suscribir("PagoCompletado", datos -> {
String pedidoId = (String) datos.get("pedidoId");
String envioId = "ENV-" + pedidoId;
System.out.printf(" [EnvioService] Creando envío %s para pedido %s%n",
envioId, pedidoId);
bus.publicar("EnvioCreado", evento(
"pedidoId", pedidoId,
"envioId", envioId));
});
}
}
// ─────────────────────────────────────────────
// MAIN: demo de Saga exitosa y Saga con compensación
// ─────────────────────────────────────────────
public static void main(String[] args) {
System.out.println("═".repeat(65));
System.out.println(" SAGA PATTERN (Coreografía) — Java puro");
System.out.println("═".repeat(65));
// ── Escenario 1: Saga exitosa ──────────────────────────────────
System.out.println("\n══ ESCENARIO 1: Saga exitosa (todos los pasos OK) ══");
EventBus bus1 = new EventBus();
PedidoService pedidoSvc1 = new PedidoService(bus1);
new InventarioService(bus1);
new PagoService(bus1);
new EnvioService(bus1);
pedidoSvc1.crearPedido("P-001", "laptop", 2);
System.out.printf("%n→ Estado final del pedido P-001: %s%n",
pedidoSvc1.estadoPedido("P-001"));
// ── Escenario 2: Saga con compensación — pago falla ───────────
System.out.println("\n\n══ ESCENARIO 2: Pago falla → compensaciones en cascada ══");
EventBus bus2 = new EventBus();
PedidoService pedidoSvc2 = new PedidoService(bus2);
new InventarioService(bus2);
PagoService pagoSvc = new PagoService(bus2);
new EnvioService(bus2);
pagoSvc.activarFalloPago(); // simular pago rechazado
pedidoSvc2.crearPedido("P-002", "laptop", 1);
System.out.printf("%n→ Estado final del pedido P-002: %s%n",
pedidoSvc2.estadoPedido("P-002"));
// ── Escenario 3: Stock insuficiente ───────────────────────────
System.out.println("\n\n══ ESCENARIO 3: Stock insuficiente → compensación inmediata ══");
EventBus bus3 = new EventBus();
PedidoService pedidoSvc3 = new PedidoService(bus3);
new InventarioService(bus3);
new PagoService(bus3);
new EnvioService(bus3);
pedidoSvc3.crearPedido("P-003", "mouse", 1); // mouse tiene stock=0
System.out.printf("%n→ Estado final del pedido P-003: %s%n",
pedidoSvc3.estadoPedido("P-003"));
System.out.println("\n" + "═".repeat(65));
System.out.println(" RESUMEN SAGA COREOGRAFÍA");
System.out.println("═".repeat(65));
System.out.println(" + Cada servicio es autónomo y reactivo a eventos");
System.out.println(" + No hay orquestador central → bajo acoplamiento");
System.out.println(" - Difícil trazar el flujo completo (requiere distributed tracing)");
System.out.println(" - Las compensaciones deben implementarse en cada servicio");
System.out.println(" Clave: cada paso debe ser IDEMPOTENTE y la compensación");
System.out.println(" debe ser el inverso lógico del paso original");
System.out.println("═".repeat(65));
}
}