@ronaldjdevfs/forge 1.3.0-beta → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/package.json +1 -1
- package/skills/forge/SKILL.md +57 -128
- package/skills/forge/command/forge.md +59 -14
- package/skills/forge/reference/adr.md +242 -0
- package/skills/forge/reference/anti-corruption-layer.md +340 -0
- package/skills/forge/reference/api-design.md +7 -0
- package/skills/forge/reference/api-versioning.md +354 -0
- package/skills/forge/reference/architectural-depth-checklist.md +311 -0
- package/skills/forge/reference/architecture-template.md +41 -0
- package/skills/forge/reference/assay.md +6 -0
- package/skills/forge/reference/bounded-contexts.md +311 -0
- package/skills/forge/reference/chain.md +6 -0
- package/skills/forge/reference/cohesion-checklist.md +256 -0
- package/skills/forge/reference/cqrs.md +286 -0
- package/skills/forge/reference/data-patterns.md +6 -0
- package/skills/forge/reference/di-strategies.md +6 -0
- package/skills/forge/reference/errors.md +5 -0
- package/skills/forge/reference/events.md +8 -0
- package/skills/forge/reference/evolutionary-architecture.md +300 -0
- package/skills/forge/reference/forge.md +7 -0
- package/skills/forge/reference/hooks.md +6 -0
- package/skills/forge/reference/idempotency.md +283 -0
- package/skills/forge/reference/inscribe.md +5 -0
- package/skills/forge/reference/inspect.md +6 -0
- package/skills/forge/reference/modular-monolith.md +252 -0
- package/skills/forge/reference/observability.md +5 -0
- package/skills/forge/reference/quench.md +5 -0
- package/skills/forge/reference/relocate.md +6 -0
- package/skills/forge/reference/sagas.md +359 -0
- package/skills/forge/reference/security-patterns.md +6 -0
- package/skills/forge/reference/smelt.md +6 -0
- package/skills/forge/reference/temper.md +6 -0
- package/skills/forge/reference/testing-patterns.md +6 -0
- package/skills/forge/reference/transactional-outbox.md +311 -0
- package/skills/forge/scripts/architecture.mjs +10 -5
- package/skills/forge/scripts/assay.mjs +2 -2
- package/skills/forge/scripts/chain.mjs +31 -5
- package/skills/forge/scripts/context.mjs +24 -4
- package/skills/forge/scripts/detect.mjs +57 -36
- package/skills/forge/scripts/forge-boot.mjs +108 -0
- package/skills/forge/scripts/forge-config.mjs +182 -3
- package/skills/forge/scripts/forge-state.mjs +1 -1
- package/skills/forge/scripts/forgeSentinel-lib.mjs +2 -2
- package/skills/forge/scripts/forgeSentinel.mjs +2 -2
- package/skills/forge/scripts/forgeSmith.mjs +2 -2
- package/skills/forge/scripts/graph.mjs +65 -9
- package/skills/forge/scripts/hook.mjs +2 -2
- package/skills/forge/scripts/inspect.mjs +56 -48
- package/skills/forge/scripts/parse-imports.mjs +0 -2
- package/skills/forge/scripts/posttool.mjs +211 -17
- package/skills/forge/scripts/recommendation-engine.mjs +125 -0
- package/skills/forge/scripts/rename.mjs +62 -14
- package/skills/forge/scripts/rollback.mjs +5 -3
- package/skills/forge/templates/feature/acl-gateway.ts.md +52 -0
- package/skills/forge/templates/feature/acl-repository.ts.md +36 -0
- package/skills/forge/templates/feature/acl-translator.ts.md +24 -0
- package/skills/forge/templates/feature/cqrs-query.ts.md +40 -0
- package/skills/forge/templates/feature/entity.ts.md +1 -1
- package/skills/forge/templates/feature/mapper.ts.md +1 -1
- package/skills/forge/templates/feature/outbox-repository.ts.md +36 -0
- package/skills/forge/templates/feature/repository-impl.ts.md +2 -2
- package/skills/forge/templates/feature/repository-interface.ts.md +2 -2
- package/skills/forge/templates/feature/saga-orchestrator.ts.md +72 -0
- package/skills/forge/templates/feature/schema.ts.md +1 -1
- package/skills/forge/templates/feature/use-case.ts.md +2 -2
- package/skills/forge/templates/platform/outbox-relayer.ts.md +80 -0
- package/skills/forge/tests/core.test.mjs +288 -4
- package/src/cli.js +26 -13
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Sagas — Transacciones Multi-Feature
|
|
2
|
+
|
|
3
|
+
Los procesos de negocio rara vez caben en una sola feature. Una saga coordina múltiples pasos a través de bounded contexts, garantizando consistencia eventual sin ACID distribuido.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## El Problema
|
|
8
|
+
|
|
9
|
+
Una transacción como "realizar un pedido" cruza múltiples features:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Orders: crear pedido → Inventory: reservar stock → Payments: cobrar → Shipping: preparar envío
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
En un monolith con una sola BD, esto sería una transacción ACID. Cuando cada feature tiene su propio schema o BD, no hay transacción distribuida. Las sagas resuelven este problema.
|
|
16
|
+
|
|
17
|
+
### Una saga no es:
|
|
18
|
+
- Una transacción ACID (no hay rollback, hay compensación)
|
|
19
|
+
- Un workflow BPMN con motor externo (aunque puede parecerse)
|
|
20
|
+
- Un event pipeline unidireccional (tiene lógica de decisión)
|
|
21
|
+
|
|
22
|
+
### Una saga es:
|
|
23
|
+
- Una secuencia de transacciones locales
|
|
24
|
+
- Cada paso tiene una acción compensatoria si falla
|
|
25
|
+
- La saga completa tiene consistencia eventual garantizada
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Coreografía (Choreography)
|
|
30
|
+
|
|
31
|
+
Cada participante publica eventos y reacciona a eventos de otros. No hay coordinador central.
|
|
32
|
+
|
|
33
|
+
```mermaid
|
|
34
|
+
sequenceDiagram
|
|
35
|
+
participant Orders
|
|
36
|
+
participant Inventory
|
|
37
|
+
participant Payments
|
|
38
|
+
participant Shipping
|
|
39
|
+
|
|
40
|
+
Orders->>Orders: crear pedido
|
|
41
|
+
Orders->>Inventory: OrderPlaced (event)
|
|
42
|
+
Inventory->>Inventory: reservar stock
|
|
43
|
+
alt stock suficiente
|
|
44
|
+
Inventory->>Orders: StockReserved (event)
|
|
45
|
+
Orders->>Payments: cobrar (event)
|
|
46
|
+
Payments->>Payments: procesar pago
|
|
47
|
+
else stock insuficiente
|
|
48
|
+
Inventory->>Orders: StockReservationFailed (event)
|
|
49
|
+
Orders->>Orders: cancelar pedido
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Implementación coreografía
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// Cada feature publica y escucha eventos
|
|
57
|
+
// features/orders/adapters/in/events/OrderSaga.ts
|
|
58
|
+
export class OrderSaga {
|
|
59
|
+
constructor(
|
|
60
|
+
private readonly orderRepo: IOrderRepository,
|
|
61
|
+
private readonly eventBus: IEventBus,
|
|
62
|
+
) {
|
|
63
|
+
// Escucha respuestas de otros features
|
|
64
|
+
this.eventBus.subscribe("StockReserved", this.onStockReserved.bind(this));
|
|
65
|
+
this.eventBus.subscribe("StockReservationFailed", this.onStockFailed.bind(this));
|
|
66
|
+
this.eventBus.subscribe("PaymentProcessed", this.onPaymentProcessed.bind(this));
|
|
67
|
+
this.eventBus.subscribe("PaymentFailed", this.onPaymentFailed.bind(this));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async onStockReserved(event: StockReservedEvent): Promise<void> {
|
|
71
|
+
await this.orderRepo.updateStatus(event.orderId, "stock_reserved");
|
|
72
|
+
this.eventBus.publish(new RequestPaymentCommand(event.orderId, event.amount));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async onStockFailed(event: StockReservationFailedEvent): Promise<void> {
|
|
76
|
+
await this.orderRepo.updateStatus(event.orderId, "cancelled_no_stock");
|
|
77
|
+
// No hay compensación adicional: el pedido nunca se confirmó
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async onPaymentProcessed(event: PaymentProcessedEvent): Promise<void> {
|
|
81
|
+
await this.orderRepo.updateStatus(event.orderId, "paid");
|
|
82
|
+
this.eventBus.publish(new RequestShippingCommand(event.orderId));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async onPaymentFailed(event: PaymentFailedEvent): Promise<void> {
|
|
86
|
+
await this.orderRepo.updateStatus(event.orderId, "payment_failed");
|
|
87
|
+
// Compensación: liberar stock
|
|
88
|
+
this.eventBus.publish(new ReleaseStockCommand(event.orderId));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Pros y contras
|
|
94
|
+
|
|
95
|
+
| Pro | Contra |
|
|
96
|
+
|---|---|
|
|
97
|
+
| Sin punto central de fallo | Lógica de saga distribuida: difícil de entender el flujo completo |
|
|
98
|
+
| Cada feature es autónomo | Seguimiento complejo (¿en qué paso está cada pedido?) |
|
|
99
|
+
| Fácil de añadir nuevos participantes | Riesgo de ciclos de eventos |
|
|
100
|
+
| Escala horizontalmente | Testing de integración más complejo |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Orquestación (Orchestration)
|
|
105
|
+
|
|
106
|
+
Un coordinador central (orchestrator) dirige cada paso y decide el flujo.
|
|
107
|
+
|
|
108
|
+
```mermaid
|
|
109
|
+
sequenceDiagram
|
|
110
|
+
participant CheckoutSaga as Orchestrator
|
|
111
|
+
participant Orders
|
|
112
|
+
participant Inventory
|
|
113
|
+
participant Payments
|
|
114
|
+
participant Shipping
|
|
115
|
+
|
|
116
|
+
CheckoutSaga->>Orders: crearPedido()
|
|
117
|
+
Orders-->>CheckoutSaga: ok
|
|
118
|
+
CheckoutSaga->>Inventory: reservarStock()
|
|
119
|
+
Inventory-->>CheckoutSaga: ok
|
|
120
|
+
CheckoutSaga->>Payments: procesarPago()
|
|
121
|
+
alt pago exitoso
|
|
122
|
+
Payments-->>CheckoutSaga: ok
|
|
123
|
+
CheckoutSaga->>Shipping: prepararEnvio()
|
|
124
|
+
else pago falla
|
|
125
|
+
Payments-->>CheckoutSaga: failed
|
|
126
|
+
CheckoutSaga->>Inventory: liberarStock() ← compensación
|
|
127
|
+
CheckoutSaga->>Orders: cancelarPedido() ← compensación
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Implementación orquestación
|
|
132
|
+
|
|
133
|
+
El orchestrator es un feature independiente:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
src/features/checkout-saga/
|
|
137
|
+
domain/
|
|
138
|
+
SagaInstance.entity.ts ← estado de cada saga activa
|
|
139
|
+
ISagaStepExecutor.ts ← interfaz para ejecutar pasos
|
|
140
|
+
application/
|
|
141
|
+
use-cases/
|
|
142
|
+
ExecuteCheckoutSaga.ts ← orquesta la saga
|
|
143
|
+
adapters/
|
|
144
|
+
out/
|
|
145
|
+
saga-steps/
|
|
146
|
+
CreateOrderStep.ts ← llama a Orders
|
|
147
|
+
ReserveStockStep.ts ← llama a Inventory
|
|
148
|
+
ProcessPaymentStep.ts ← llama a Payments
|
|
149
|
+
PrepareShippingStep.ts ← llama a Shipping
|
|
150
|
+
CancelOrderStep.ts ← compensación de Orders
|
|
151
|
+
ReleaseStockStep.ts ← compensación de Inventory
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// features/checkout-saga/application/use-cases/ExecuteCheckoutSaga.ts
|
|
156
|
+
export class ExecuteCheckoutSaga {
|
|
157
|
+
constructor(
|
|
158
|
+
private readonly sagaRepo: ISagaInstanceRepository,
|
|
159
|
+
private readonly stepFactory: SagaStepFactory,
|
|
160
|
+
) {}
|
|
161
|
+
|
|
162
|
+
async execute(command: StartCheckoutCommand): Promise<void> {
|
|
163
|
+
const saga = SagaInstance.start("CHECKOUT", {
|
|
164
|
+
orderId: command.orderId,
|
|
165
|
+
customerId: command.customerId,
|
|
166
|
+
items: command.items,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const steps = [
|
|
170
|
+
this.stepFactory.createOrderStep(),
|
|
171
|
+
this.stepFactory.reserveStockStep(),
|
|
172
|
+
this.stepFactory.processPaymentStep(),
|
|
173
|
+
this.stepFactory.prepareShippingStep(),
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
for (const step of steps) {
|
|
177
|
+
try {
|
|
178
|
+
await step.execute(saga.context);
|
|
179
|
+
saga.advance(step.name);
|
|
180
|
+
await this.sagaRepo.save(saga);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
saga.fail(step.name, error.message);
|
|
183
|
+
await this.compensate(saga, steps);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
saga.complete();
|
|
189
|
+
await this.sagaRepo.save(saga);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async compensate(saga: SagaInstance, steps: SagaStep[]): Promise<void> {
|
|
193
|
+
// Ejecuta compensaciones en orden inverso
|
|
194
|
+
const executedSteps = steps.slice(0, saga.currentStepIndex);
|
|
195
|
+
for (const step of executedSteps.reverse()) {
|
|
196
|
+
try {
|
|
197
|
+
await step.compensate(saga.context);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
// La compensación falló — requiere intervención manual
|
|
200
|
+
saga.requireManualIntervention();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Pros y contras
|
|
208
|
+
|
|
209
|
+
| Pro | Contra |
|
|
210
|
+
|---|---|
|
|
211
|
+
| Flujo completo visible en un lugar | Punto central de fallo |
|
|
212
|
+
| Fácil de testear el orchestrator | El orchestrator conoce todos los participantes |
|
|
213
|
+
| Compensaciones explícitas y centralizadas | Riesgo de orchestrator con lógica de negocio |
|
|
214
|
+
| Trazabilidad: cada saga tiene estado | Más boilerplate que coreografía |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Cuándo Elegir Cada Una
|
|
219
|
+
|
|
220
|
+
| Situación | Recomendación |
|
|
221
|
+
|---|---|
|
|
222
|
+
| 2-3 features, flujo simple | Coreografía |
|
|
223
|
+
| 4+ features, flujo con ramas | Orquestación |
|
|
224
|
+
| Equipos autónomos que no quieren coordinar | Coreografía |
|
|
225
|
+
| Requisito de trazabilidad y monitoreo | Orquestación |
|
|
226
|
+
| Flujo con muchas compensaciones | Orquestación |
|
|
227
|
+
| Feature nuevo que se suma al flujo existente | Coreografía (solo escucha/publica) |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Manejo de Fallos
|
|
232
|
+
|
|
233
|
+
### Retry con Backoff
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
export class RetryPolicy {
|
|
237
|
+
constructor(
|
|
238
|
+
private readonly maxRetries: number = 3,
|
|
239
|
+
private readonly baseDelay: number = 100,
|
|
240
|
+
) {}
|
|
241
|
+
|
|
242
|
+
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
243
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
244
|
+
try {
|
|
245
|
+
return await fn();
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (attempt === this.maxRetries) throw error;
|
|
248
|
+
const delay = this.baseDelay * Math.pow(2, attempt - 1);
|
|
249
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Dead Letter Queue (DLQ)
|
|
257
|
+
|
|
258
|
+
Cuando una saga no puede completarse ni compensarse después de N reintentos:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
if (saga.retryCount >= 5) {
|
|
262
|
+
saga.sendToDLQ({
|
|
263
|
+
reason: "Payment service unavailable after 5 retries",
|
|
264
|
+
context: saga.context,
|
|
265
|
+
lastError: error.message,
|
|
266
|
+
});
|
|
267
|
+
// Requiere intervención humana
|
|
268
|
+
await this.sagaRepo.markFailed(saga);
|
|
269
|
+
await this.notificationService.alertOperations(
|
|
270
|
+
`Saga ${saga.id} requiere intervención manual`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Fallback Humano
|
|
276
|
+
|
|
277
|
+
Para casos donde la compensación automática no es posible (ej. un pago ya procesado que no puede revertirse automáticamente):
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
if (error.name === "IrreversibleError") {
|
|
281
|
+
saga.requireManualIntervention({
|
|
282
|
+
reason: "Payment already captured, manual refund required",
|
|
283
|
+
instructions: `Login to payment dashboard, refund transaction ${event.paymentId}, then resume saga ${saga.id}`,
|
|
284
|
+
severity: "HIGH",
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Conexión con el Modelo de Forge
|
|
292
|
+
|
|
293
|
+
### Reglas y Sagas
|
|
294
|
+
|
|
295
|
+
| Regla | Aplicación |
|
|
296
|
+
|---|---|
|
|
297
|
+
| **R8** (no cross-feature imports) | La saga se comunica con features mediante eventos o interfaces. Nunca importa directamente. |
|
|
298
|
+
| **R5** (domain → infra) | La saga opera en application/use-cases/. No hay infra en dominio de saga. |
|
|
299
|
+
| **R9** (no ciclos) | Las sagas coreografiadas deben auditarse para evitar ciclos de eventos (A→B→C→A). |
|
|
300
|
+
|
|
301
|
+
### Ubicación de la saga en la arquitectura
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
src/features/
|
|
305
|
+
checkout-saga/ ← orchestrator como feature
|
|
306
|
+
domain/
|
|
307
|
+
SagaInstance.entity.ts
|
|
308
|
+
ISagaStepExecutor.ts
|
|
309
|
+
application/
|
|
310
|
+
use-cases/
|
|
311
|
+
ExecuteCheckoutSaga.ts
|
|
312
|
+
adapters/
|
|
313
|
+
out/
|
|
314
|
+
saga-steps/
|
|
315
|
+
CreateOrderStep.ts ← llama a contracts de Orders
|
|
316
|
+
ReserveStockStep.ts ← llama a contracts de Inventory
|
|
317
|
+
ProcessPaymentStep.ts ← llama a contracts de Payments
|
|
318
|
+
saga-persistence/
|
|
319
|
+
PostgresSagaRepository.ts
|
|
320
|
+
orders/
|
|
321
|
+
adapters/
|
|
322
|
+
in/
|
|
323
|
+
events/ ← escucha eventos de la saga
|
|
324
|
+
http/
|
|
325
|
+
commands/ ← la saga llama aquí via contract
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Anti-patrones
|
|
331
|
+
|
|
332
|
+
| Anti-patrón | Problema | Solución |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| **Saga sin compensación** | Un paso falla y el sistema queda inconsistente. | Cada paso debe tener compensación definida explícitamente. |
|
|
335
|
+
| **Orchestrator con lógica de negocio** | El orchestrator decide descuentos, valida reglas, calcula montos. | El orchestrator solo coordina. La lógica de negocio vive en los use cases de cada feature. |
|
|
336
|
+
| **Coreografía sin trazabilidad** | Múltiples eventos volando, nadie sabe el estado global de un proceso. | Usar saga log + event store. Monitorear con `forge chain`. |
|
|
337
|
+
| **Timeout único** | Todas las operaciones tienen el mismo timeout. Operaciones lentas (pagos) fallan donde rápidas (stock) no. | Timeout configurable por paso de saga. |
|
|
338
|
+
| **Compensación que falla sin alerta** | La compensación falla silenciosamente. El sistema cree que compensó pero no lo hizo. | Alertas de operaciones para compensaciones fallidas. Siempre. |
|
|
339
|
+
| **Saga como transacción ACID** | Se intenta revertir un paso que ya tuvo efectos visibles para el usuario. | Las sagas ofrecen consistencia eventual. Los efectos visibles deben diseñarse para ser reversibles (o aceptar que no lo son). |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Conexión con Forge
|
|
344
|
+
|
|
345
|
+
| Comando | Acción |
|
|
346
|
+
|---|---|
|
|
347
|
+
| `forge cast checkout-saga` | Crea feature orchestrator con estructura de saga |
|
|
348
|
+
| `forge inspect` | Detecta features que se comunican sincrónicamente (candidatos a saga) |
|
|
349
|
+
| `forge graph` | Visualiza el flujo de la saga como grafo de dependencias entre features |
|
|
350
|
+
| `forge chain` | Verifica que la saga no introduce ciclos (R9) |
|
|
351
|
+
| `forge assay` | Evalúa si la estrategia de coordinación (coreografía vs orquestación) es la adecuada |
|
|
352
|
+
|
|
353
|
+
## Ver también
|
|
354
|
+
|
|
355
|
+
- `reference/bounded-contexts.md` — contexts que la saga coordina
|
|
356
|
+
- `reference/events.md` — eventos como unidad de la saga
|
|
357
|
+
- `reference/cqrs.md` — commands como pasos de la saga
|
|
358
|
+
- `reference/transactional-outbox.md` — entrega confiable de eventos de saga
|
|
359
|
+
- `reference/idempotency.md` — retry seguro de pasos de saga
|
|
@@ -85,3 +85,9 @@ export const createUserSchema = z.object({
|
|
|
85
85
|
- Helmet para headers de seguridad (CSP, X-Frame-Options, etc.)
|
|
86
86
|
- Secretos en variables de entorno, nunca en código
|
|
87
87
|
- Auditoría de acciones sensibles: login, delete, role change, export
|
|
88
|
+
|
|
89
|
+
## Ver también
|
|
90
|
+
|
|
91
|
+
- `reference/api-design.md` — middleware de seguridad, validación de entrada
|
|
92
|
+
- `reference/observability.md` — audit logging como práctica de observabilidad
|
|
93
|
+
- `reference/testing-patterns.md` — tests de middleware y seguridad
|
|
@@ -29,3 +29,9 @@ Extrae código reutilizable desde features hacia `shared/`.
|
|
|
29
29
|
- El código extraído NO debe importar de infraestructura
|
|
30
30
|
- El código extraído debe ser puro o depender solo de otros componentes shared
|
|
31
31
|
- Si el código depende de platform, considerar moverlo a platform en lugar de shared
|
|
32
|
+
|
|
33
|
+
## Ver también
|
|
34
|
+
|
|
35
|
+
- `reference/relocate.md` — operación similar de extracción
|
|
36
|
+
- `reference/data-patterns.md` — identificación de qué extraer a shared
|
|
37
|
+
- `reference/errors.md` — errores tipados como candidatos a smelt
|
|
@@ -47,3 +47,9 @@ Templa la arquitectura aplicando reglas de inyección de dependencias, seguridad
|
|
|
47
47
|
- ❌ Importar tsyringe en archivos de dominio
|
|
48
48
|
- ❌ Mezclar DI manual y contenedor en el mismo feature
|
|
49
49
|
- ❌ Proxies automáticos o decoradores ocultos que dificulten el rastreo
|
|
50
|
+
|
|
51
|
+
## Ver también
|
|
52
|
+
|
|
53
|
+
- `reference/di-strategies.md` — selección de estrategia DI antes de temperar
|
|
54
|
+
- `reference/patterns.md` — convenciones de naming en DI
|
|
55
|
+
- `reference/testing-patterns.md` — testabilidad que DI disciplinada habilita
|
|
@@ -67,3 +67,9 @@ it("POST /users returns 201", async () => {
|
|
|
67
67
|
- Nombrar tests como unidades de comportamiento, no métodos
|
|
68
68
|
- Coverage mínimo sugerido: 85% use cases, 75% adapters
|
|
69
69
|
- Los mappers se testean con fixtures: entrada conocida → salida esperada
|
|
70
|
+
|
|
71
|
+
## Ver también
|
|
72
|
+
|
|
73
|
+
- `reference/anti-corruption-layer.md` — tests de ACL y traducción entre contexts
|
|
74
|
+
- `reference/di-strategies.md` — DI testing con mocks manuales
|
|
75
|
+
- `reference/temper.md` — DI disciplinada que habilita testabilidad
|