@volontariapp/outbox 0.9.40-snap-8e4f7a0 → 0.9.41
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/CHANGELOG.md +15 -0
- package/README.md +125 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.41
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- README bump
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @volontariapp/config@3.2.1
|
|
11
|
+
- @volontariapp/database@3.4.4
|
|
12
|
+
- @volontariapp/errors@0.6.1
|
|
13
|
+
- @volontariapp/logger@0.2.6
|
|
14
|
+
- @volontariapp/messaging@2.10.1
|
|
15
|
+
- @volontariapp/shared@0.8.1
|
|
16
|
+
- @volontariapp/testing@1.0.2
|
|
17
|
+
|
|
3
18
|
## 0.9.40
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @volontariapp/outbox
|
|
2
|
+
|
|
3
|
+
## Overview & The Transactional Outbox Pattern
|
|
4
|
+
|
|
5
|
+
Le package `outbox` est le pilier central de l'architecture asynchrone et événementielle de Volontariapp.
|
|
6
|
+
Il résout le **Problème de la Double Écriture (Dual Write Problem)** : comment garantir qu'une entité métier est sauvegardée en base ET qu'un message/job est bien envoyé à Redis/BullMQ sans risque d'incohérence si l'un des deux systèmes tombe ?
|
|
7
|
+
|
|
8
|
+
La solution implémentée ici est agnostique de NestJS (Node.js pur) pour des performances optimales et une séparation claire des responsabilités.
|
|
9
|
+
|
|
10
|
+
## Architecture et Rôles des Composants
|
|
11
|
+
|
|
12
|
+
L'architecture interne de l'Outbox est divisée en plusieurs responsabilités claires, du polling de la base de données jusqu'à l'envoi physique dans le Broker (Redis).
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
classDiagram
|
|
16
|
+
direction TB
|
|
17
|
+
|
|
18
|
+
class Writer {
|
|
19
|
+
<<Interface Microservice>>
|
|
20
|
+
+write(payload, entityManager)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class Consumer {
|
|
24
|
+
<<Polling Daemon>>
|
|
25
|
+
+pollPendingRows()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class Dispatcher {
|
|
29
|
+
<<Router & Orchestrator>>
|
|
30
|
+
+dispatch(rows)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class Pusher {
|
|
34
|
+
<<Infrastructure Adapter>>
|
|
35
|
+
+pushToBroker(message)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class DB {
|
|
39
|
+
<<PostgreSQL>>
|
|
40
|
+
jobs_outbox
|
|
41
|
+
event_outbox
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class Redis {
|
|
45
|
+
<<Message Broker>>
|
|
46
|
+
BullMQ
|
|
47
|
+
Redis Streams
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Writer --> DB : Insère dans la même transaction que le métier
|
|
51
|
+
Consumer --> DB : SELECT WHERE status = 'Pending'
|
|
52
|
+
Consumer --> Dispatcher : Envoie les lignes récupérées
|
|
53
|
+
Dispatcher --> Pusher : Aiguille selon le type (Job vs Event)
|
|
54
|
+
Pusher --> Redis : Publie physiquement
|
|
55
|
+
Dispatcher --> DB : UPDATE status = 'Done'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Détail des Composants
|
|
59
|
+
- **Writers** : Utilisés par les microservices (ex: `ms-event`) pour insérer une intention d'action asynchrone (Job ou Domain Event) dans la même transaction SQL que l'action métier.
|
|
60
|
+
- **Consumers** : Boucles infinies tournant en tâche de fond (ou dans un Cron Kubernetes) qui lisent les tables d'outbox à la recherche de lignes `Pending`.
|
|
61
|
+
- **Dispatchers** : Reçoivent les lignes du Consumer. Ils orchestrent la logique de retry et décident à quel Pusher déléguer (ex: un Job va dans BullMQ, un Domain Event va dans un Redis Stream). Une fois poussé avec succès, le Dispatcher marque la ligne comme `Done` en SQL.
|
|
62
|
+
- **Pushers** : Adaptateurs de bas niveau responsables de la connexion réseau avec Redis/Kafka pour l'envoi du binaire/JSON.
|
|
63
|
+
|
|
64
|
+
## Flux d'Exécution (Séquence)
|
|
65
|
+
|
|
66
|
+
```mermaid
|
|
67
|
+
sequenceDiagram
|
|
68
|
+
participant API as Microservice (ex: ms-event)
|
|
69
|
+
participant DB as PostgreSQL (Transaction)
|
|
70
|
+
participant Consumer as Outbox Consumer
|
|
71
|
+
participant Dispatcher as Outbox Dispatcher
|
|
72
|
+
participant Pusher as Redis Pusher
|
|
73
|
+
|
|
74
|
+
API->>DB: BEGIN Transaction
|
|
75
|
+
API->>DB: INSERT INTO event (logique métier)
|
|
76
|
+
API->>DB: INSERT INTO jobs_outbox (status = 'Pending')
|
|
77
|
+
API->>DB: COMMIT Transaction
|
|
78
|
+
|
|
79
|
+
loop Polling (Background)
|
|
80
|
+
Consumer->>DB: SELECT * FROM jobs_outbox WHERE status = 'Pending' LIMIT 10
|
|
81
|
+
Consumer->>Dispatcher: dispatch(rows)
|
|
82
|
+
Dispatcher->>DB: UPDATE jobs_outbox SET status = 'Processing'
|
|
83
|
+
Dispatcher->>Pusher: pushToBroker(payload)
|
|
84
|
+
Pusher-->>Dispatcher: Ack (Poussé avec succès)
|
|
85
|
+
Dispatcher->>DB: UPDATE jobs_outbox SET status = 'Done'
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Structure des Dossiers
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
src/
|
|
93
|
+
├── writers/ # Classes utilisées par les MS pour écrire dans la DB Outbox
|
|
94
|
+
├── consumers/ # Boucles de polling récupérant les lignes de DB 'Pending'
|
|
95
|
+
├── dispatchers/ # Logique de routage vers BullMQ ou Redis Stream
|
|
96
|
+
├── pushers/ # Classes poussant physiquement vers Redis
|
|
97
|
+
└── repositories/ # Accès direct aux tables `jobs_outbox` / `event_outbox`
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Exemple d'Implémentation
|
|
101
|
+
|
|
102
|
+
### Écriture Transactionnelle depuis un Microservice
|
|
103
|
+
|
|
104
|
+
Le microservice utilise le `Writer` fourni par la librairie en lui passant le gestionnaire de transaction TypeORM (EntityManager).
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { JobsOutboxWriter } from '@volontariapp/outbox';
|
|
108
|
+
|
|
109
|
+
export class EventService {
|
|
110
|
+
async createEventWithJob(payload: any, manager: EntityManager) {
|
|
111
|
+
// 1. Sauvegarde métier classique
|
|
112
|
+
const event = await manager.save(EventEntity, payload);
|
|
113
|
+
|
|
114
|
+
// 2. Écriture du Job de façon transactionnelle
|
|
115
|
+
const writer = new JobsOutboxWriter(manager);
|
|
116
|
+
await writer.write({
|
|
117
|
+
queueName: 'event-processing',
|
|
118
|
+
jobName: 'notify-followers',
|
|
119
|
+
payload: { eventId: event.id },
|
|
120
|
+
options: { delay: 5000 }
|
|
121
|
+
});
|
|
122
|
+
// Si la transaction échoue, le job n'est pas inséré : Cohérence absolue.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volontariapp/outbox",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.41",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"db:down": "docker compose -f ../../ci-tools/testing/docker-compose.yml --profile test stop redis"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@volontariapp/config": "3.2.
|
|
45
|
-
"@volontariapp/database": "3.4.
|
|
46
|
-
"@volontariapp/errors": "0.6.
|
|
47
|
-
"@volontariapp/logger": "0.2.
|
|
48
|
-
"@volontariapp/messaging": "2.10.
|
|
49
|
-
"@volontariapp/shared": "0.8.
|
|
50
|
-
"@volontariapp/testing": "1.0.
|
|
44
|
+
"@volontariapp/config": "3.2.1",
|
|
45
|
+
"@volontariapp/database": "3.4.4",
|
|
46
|
+
"@volontariapp/errors": "0.6.1",
|
|
47
|
+
"@volontariapp/logger": "0.2.6",
|
|
48
|
+
"@volontariapp/messaging": "2.10.1",
|
|
49
|
+
"@volontariapp/shared": "0.8.1",
|
|
50
|
+
"@volontariapp/testing": "1.0.2",
|
|
51
51
|
"bullmq": "^5.76.5",
|
|
52
52
|
"ioredis": "^5.10.1"
|
|
53
53
|
},
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@types/jest": "^30.0.0",
|
|
57
57
|
"@types/node": "^22.10.7",
|
|
58
58
|
"@types/pg": "^8",
|
|
59
|
-
"@volontariapp/testing": "1.0.
|
|
59
|
+
"@volontariapp/testing": "1.0.2",
|
|
60
60
|
"jest": "^30.3.0",
|
|
61
61
|
"ts-jest": "^29.4.6",
|
|
62
62
|
"ts-node": "^10.9.2",
|