pp-command-bus 1.2.3 → 1.3.1
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 +706 -168
- package/dist/command-bus/command-bus.spec.js +1 -1
- package/dist/command-bus/command.d.ts +0 -1
- package/dist/command-bus/command.js +0 -2
- package/dist/command-bus/command.js.map +1 -1
- package/dist/command-bus/config/command-bus-config.d.ts +1 -1
- package/dist/command-bus/config/command-bus-config.js +1 -1
- package/dist/command-bus/config/command-bus-config.spec.js +2 -2
- package/dist/command-bus/index.d.ts +0 -3
- package/dist/command-bus/index.js +2 -13
- package/dist/command-bus/index.js.map +1 -1
- package/dist/command-bus/job/job-options-builder.d.ts +2 -5
- package/dist/command-bus/job/job-options-builder.js +8 -18
- package/dist/command-bus/job/job-options-builder.js.map +1 -1
- package/dist/command-bus/job/job-options-builder.spec.js +4 -4
- package/dist/command-bus/job/job-processor.spec.js +2 -2
- package/dist/command-bus/rpc/rpc-coordinator.d.ts +6 -28
- package/dist/command-bus/rpc/rpc-coordinator.js +12 -35
- package/dist/command-bus/rpc/rpc-coordinator.js.map +1 -1
- package/dist/command-bus/worker/worker-orchestrator.d.ts +9 -6
- package/dist/command-bus/worker/worker-orchestrator.js +18 -12
- package/dist/command-bus/worker/worker-orchestrator.js.map +1 -1
- package/dist/command-bus/worker/worker-orchestrator.spec.js +5 -12
- package/dist/command-bus/worker/worker-orchestrator.spec.js.map +1 -1
- package/dist/pp-command-bus-1.3.1.tgz +0 -0
- package/package.json +4 -4
- package/dist/pp-command-bus-1.2.3.tgz +0 -0
package/README.md
CHANGED
|
@@ -4,39 +4,207 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
|
|
|
4
4
|
|
|
5
5
|
## Opis
|
|
6
6
|
|
|
7
|
-
**pp-command-bus** to biblioteka do obsługi rozproszonych komend zgodna ze wzorcem **CQRS (Command Query Responsibility Segregation)**.
|
|
7
|
+
**pp-command-bus** to zaawansowana biblioteka do obsługi rozproszonych komend zgodna ze wzorcem **CQRS (Command Query Responsibility Segregation)**. Zapewnia wysoką wydajność, automatyczną optymalizację i zaawansowane funkcje produkcyjne.
|
|
8
|
+
|
|
9
|
+
### Kluczowe Cechy
|
|
8
10
|
|
|
9
11
|
- ✅ **Fire-and-forget commands** - wysyłanie komend bez oczekiwania na wynik
|
|
10
|
-
- ✅ **RPC (Remote Procedure Call)** - synchroniczne wywołania z oczekiwaniem na odpowiedź
|
|
12
|
+
- ✅ **RPC (Remote Procedure Call)** - synchroniczne wywołania przez Redis Pub/Sub z oczekiwaniem na odpowiedź
|
|
13
|
+
- ✅ **Automatyczna kompresja** - gzip dla payloadów RPC >1KB (konfigurowalne)
|
|
14
|
+
- ✅ **Auto-optymalizacja** - dynamiczne dostosowywanie concurrency na podstawie CPU/RAM
|
|
15
|
+
- ✅ **Process isolation** - izolacja odpowiedzi RPC między procesami Node.js
|
|
11
16
|
- ✅ **Job queuing** - kolejkowanie zadań z retry, backoff i delayed execution
|
|
12
17
|
- ✅ **BullMQ integration** - wydajna kolejka zadań na Redis/DragonflyDB
|
|
13
18
|
- ✅ **TypeScript** - pełne wsparcie typów i strict mode
|
|
14
19
|
- ✅ **Memory leak protection** - zaawansowana diagnostyka i cleanup
|
|
15
|
-
- ✅ **Command logging** - opcjonalne logowanie komend do plików
|
|
16
|
-
- ✅ **Modularna architektura** - komponenty zgodne z zasadami SOLID i SRP
|
|
20
|
+
- ✅ **Command logging** - opcjonalne logowanie komend do plików JSONL
|
|
21
|
+
- ✅ **Modularna architektura** - komponenty zgodne z zasadami SOLID, DDD i SRP
|
|
17
22
|
|
|
18
|
-
##
|
|
23
|
+
## Architektura Systemu
|
|
24
|
+
|
|
25
|
+
### Diagram Komponentów
|
|
19
26
|
|
|
20
27
|
```
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
│
|
|
24
|
-
│
|
|
25
|
-
│
|
|
26
|
-
│
|
|
27
|
-
│
|
|
28
|
-
│
|
|
29
|
-
│
|
|
30
|
-
│
|
|
31
|
-
│
|
|
32
|
-
│
|
|
33
|
-
|
|
34
|
-
│
|
|
35
|
-
│
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
29
|
+
│ CommandBus │
|
|
30
|
+
│ ┌────────────────┐ ┌──────────────┐ ┌─────────────────────────┐ │
|
|
31
|
+
│ │ QueueManager │ │ RpcCoordinator│ │ WorkerOrchestrator │ │
|
|
32
|
+
│ │ - Cache kolejek│ │ - Pub/Sub │ │ - Dynamiczne concurrency│ │
|
|
33
|
+
│ │ - BullMQ Queue │ │ - Process ID │ │ - Worker benchmark │ │
|
|
34
|
+
│ └────────┬───────┘ └──────┬───────┘ └────────┬────────────────┘ │
|
|
35
|
+
│ │ │ │ │
|
|
36
|
+
│ │ │ │ │
|
|
37
|
+
│ ┌────────▼──────────────────▼────────────────────▼───────────────┐ │
|
|
38
|
+
│ │ JobProcessor (Command Handler Execution) │ │
|
|
39
|
+
│ │ - Kompresja/dekompresja payloadów │ │
|
|
40
|
+
│ │ - Wykonywanie handlerów │ │
|
|
41
|
+
│ │ - Wysyłanie odpowiedzi RPC przez Pub/Sub │ │
|
|
42
|
+
│ └──────────────────────────┬──────────────────────────────────────┘ │
|
|
43
|
+
└─────────────────────────────┼────────────────────────────────────────┘
|
|
44
|
+
│
|
|
45
|
+
┌─────────▼──────────┐
|
|
46
|
+
│ PayloadCompression │
|
|
47
|
+
│ Service │
|
|
48
|
+
│ - gzip compression │
|
|
49
|
+
│ - threshold: 1KB │
|
|
50
|
+
└─────────────────────┘
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Główne Serwisy
|
|
54
|
+
|
|
55
|
+
#### 1. **CommandBus** (główna klasa orkiestrująca)
|
|
56
|
+
- **Odpowiedzialność**: Główny punkt wejścia dla użytkownika, orkiestracja wszystkich komponentów
|
|
57
|
+
- **Metody publiczne**:
|
|
58
|
+
- `dispatch(command)` - wysyłanie fire-and-forget
|
|
59
|
+
- `call(command, timeout)` - synchroniczne RPC
|
|
60
|
+
- `handle(commandClass, handler)` - rejestracja handlerów
|
|
61
|
+
- `close()` - graceful shutdown
|
|
62
|
+
- **Zarządzanie połączeniami**: 3 dedykowane połączenia Redis (Queue, Worker, RPC)
|
|
63
|
+
|
|
64
|
+
#### 2. **RpcCoordinator** (zarządzanie RPC)
|
|
65
|
+
- **Odpowiedzialność**: Zarządzanie cyklem życia wywołań RPC przez Redis Pub/Sub
|
|
66
|
+
- **Kluczowe funkcje**:
|
|
67
|
+
- **Shared Subscriber** - jeden subscriber dla wszystkich RPC calls (pattern matching)
|
|
68
|
+
- **Process Isolation** - UUID procesu Node.js dla izolacji odpowiedzi między procesami
|
|
69
|
+
- **Timeout Management** - automatyczne cleanup po timeout
|
|
70
|
+
- **Multiplexing** - routing odpowiedzi do odpowiednich promises
|
|
71
|
+
- **Kanały**: `rpc:response:{processId}:{correlationId}`
|
|
72
|
+
|
|
73
|
+
#### 3. **WorkerOrchestrator** (orkiestracja workerów)
|
|
74
|
+
- **Odpowiedzialność**: Zarządzanie workerami BullMQ i dynamiczna optymalizacja
|
|
75
|
+
- **Kluczowe funkcje**:
|
|
76
|
+
- **WorkerBenchmark** - automatyczny benchmark przy rejestracji handlera
|
|
77
|
+
- **WorkerMetricsCollector** - event-driven metrics collection
|
|
78
|
+
- **Dynamic Concurrency** - dostosowywanie concurrency +/-20% co 30s
|
|
79
|
+
- **Event Handlers** - obsługa zdarzeń: active, completed, failed, stalled
|
|
80
|
+
- **Limity concurrency**: min 10, max 2000
|
|
81
|
+
|
|
82
|
+
#### 4. **JobProcessor** (wykonywanie handlerów)
|
|
83
|
+
- **Odpowiedzialność**: Wykonywanie handlerów komend i obsługa odpowiedzi RPC
|
|
84
|
+
- **Flow przetwarzania**:
|
|
85
|
+
1. Dekompresja payloadu (jeśli skompresowany)
|
|
86
|
+
2. Rekonstrukcja obiektów Date
|
|
87
|
+
3. Opcjonalne logowanie komendy
|
|
88
|
+
4. Wykonanie handlera
|
|
89
|
+
5. Wysłanie odpowiedzi RPC przez Pub/Sub (jeśli RPC)
|
|
90
|
+
- **Kompresja odpowiedzi**: automatyczna kompresja przez PayloadCompressionService
|
|
91
|
+
|
|
92
|
+
#### 5. **QueueManager** (zarządzanie kolejkami)
|
|
93
|
+
- **Odpowiedzialność**: Cache kolejek BullMQ dla optymalizacji pamięci
|
|
94
|
+
- **Funkcje**:
|
|
95
|
+
- `getOrCreateQueue(commandName)` - lazy loading kolejek
|
|
96
|
+
- `closeAllQueues()` - graceful shutdown wszystkich kolejek
|
|
97
|
+
- **Naming**: `{CommandName}` jako nazwa kolejki
|
|
98
|
+
|
|
99
|
+
#### 6. **PayloadCompressionService** (kompresja)
|
|
100
|
+
- **Odpowiedzialność**: Automatyczna kompresja/dekompresja payloadów gzip
|
|
101
|
+
- **Threshold**: 1024 bajty (1KB) domyślnie, konfigurowalne przez ENV
|
|
102
|
+
- **Metody**:
|
|
103
|
+
- `compressCommand(command)` - dodaje flagę `__compressed`
|
|
104
|
+
- `decompressCommand(command)` - dekompresja i usunięcie flagi
|
|
105
|
+
- `compress(data)` - generyczna kompresja do base64
|
|
106
|
+
- `decompress(data, compressed)` - generyczna dekompresja
|
|
107
|
+
- **Współdzielony serwis**: jedna instancja dla całego CommandBus
|
|
108
|
+
|
|
109
|
+
#### 7. **CommandLogger** (opcjonalne logowanie)
|
|
110
|
+
- **Odpowiedzialność**: Persystencja komend do plików JSONL
|
|
111
|
+
- **Format**: `{timestamp}.jsonl` - rotacja co godzinę
|
|
112
|
+
- **Zawartość**: pełny payload komendy z metadanymi
|
|
113
|
+
- **Aktywacja**: przez ENV `COMMAND_BUS_LOG=./command-logs`
|
|
114
|
+
|
|
115
|
+
#### 8. **AutoConfigOptimizer** (auto-optymalizacja)
|
|
116
|
+
- **Odpowiedzialność**: Obliczanie optymalnego concurrency na podstawie zasobów systemowych
|
|
117
|
+
- **Heurystyka**:
|
|
118
|
+
- I/O-heavy workload (Redis/BullMQ)
|
|
119
|
+
- `concurrency = CPU cores * 2 + (availableMemory / 512MB)`
|
|
120
|
+
- Zakładane 512MB RAM per worker
|
|
121
|
+
- **Aktywacja**: domyślnie włączone (ENV `COMMAND_BUS_AUTO_OPTIMIZE=false` wyłącza)
|
|
122
|
+
|
|
123
|
+
### Flow Przepływu Danych
|
|
124
|
+
|
|
125
|
+
#### Flow 1: dispatch() - Fire-and-forget
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
User Code
|
|
129
|
+
│
|
|
130
|
+
├─→ 1. commandBus.dispatch(command)
|
|
131
|
+
│
|
|
132
|
+
├─→ 2. PayloadCompressionService.compressCommand(command)
|
|
133
|
+
│ └─→ Jeśli payload >1KB → gzip → base64 → __compressed: true
|
|
134
|
+
│
|
|
135
|
+
├─→ 3. QueueManager.getOrCreateQueue(commandName)
|
|
136
|
+
│ └─→ Cache hit/miss → zwraca Queue
|
|
137
|
+
│
|
|
138
|
+
├─→ 4. queue.add(commandName, compressedCommand, options)
|
|
139
|
+
│ └─→ BullMQ dodaje job do Redis
|
|
140
|
+
│
|
|
141
|
+
└─→ 5. Promise<void> resolved (nie czekamy na wynik)
|
|
142
|
+
|
|
143
|
+
Worker Side (asynchronicznie)
|
|
144
|
+
│
|
|
145
|
+
├─→ 6. Worker pobiera job z kolejki
|
|
146
|
+
│
|
|
147
|
+
├─→ 7. JobProcessor.process(job)
|
|
148
|
+
│ ├─→ Dekompresja (jeśli __compressed)
|
|
149
|
+
│ ├─→ Rekonstrukcja Date
|
|
150
|
+
│ ├─→ Opcjonalne logowanie (CommandLogger)
|
|
151
|
+
│ └─→ Wykonanie handlera
|
|
152
|
+
│
|
|
153
|
+
└─→ 8. Worker kończy job (success/fail)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Flow 2: call() - RPC przez Redis Pub/Sub
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
User Code
|
|
160
|
+
│
|
|
161
|
+
├─→ 1. commandBus.call(command, timeout)
|
|
162
|
+
│
|
|
163
|
+
├─→ 2. RpcCoordinator.registerCall(correlationId, commandName, timeout)
|
|
164
|
+
│ ├─→ Oczekiwanie na gotowość shared subscriber (5s timeout)
|
|
165
|
+
│ ├─→ Utworzenie Promise<T> dla odpowiedzi
|
|
166
|
+
│ ├─→ Zapisanie pending call w Map
|
|
167
|
+
│ └─→ Zwrócenie responsePromise (bez blokowania)
|
|
168
|
+
│
|
|
169
|
+
├─→ 3. PayloadCompressionService.compressCommand(command)
|
|
170
|
+
│ └─→ Jeśli payload >1KB → gzip → __compressed: true
|
|
171
|
+
│
|
|
172
|
+
├─→ 4. RpcCoordinator.prepareRpcCommand(compressedCommand)
|
|
173
|
+
│ └─→ Dodaje __rpcMetadata: { correlationId, responseChannel, timestamp }
|
|
174
|
+
│
|
|
175
|
+
├─→ 5. QueueManager.getOrCreateQueue(commandName)
|
|
176
|
+
│ └─→ Cache hit/miss → zwraca Queue
|
|
177
|
+
│
|
|
178
|
+
├─→ 6. queue.add(commandName, commandWithMetadata, options)
|
|
179
|
+
│ └─→ BullMQ dodaje job do Redis
|
|
180
|
+
│
|
|
181
|
+
└─→ 7. await responsePromise (czeka na odpowiedź z Worker)
|
|
182
|
+
|
|
183
|
+
Worker Side
|
|
184
|
+
│
|
|
185
|
+
├─→ 8. Worker pobiera job z kolejki
|
|
186
|
+
│
|
|
187
|
+
├─→ 9. JobProcessor.process(job)
|
|
188
|
+
│ ├─→ Dekompresja payloadu (jeśli __compressed)
|
|
189
|
+
│ ├─→ Rekonstrukcja Date
|
|
190
|
+
│ ├─→ Wykonanie handlera → result
|
|
191
|
+
│ │
|
|
192
|
+
│ └─→ 10. JobProcessor.sendRpcResponse(rpcMetadata, result, null)
|
|
193
|
+
│ ├─→ PayloadCompressionService.compress({ correlationId, result, error })
|
|
194
|
+
│ ├─→ Wrapper: { data, compressed }
|
|
195
|
+
│ └─→ redis.publish(responseChannel, JSON.stringify(wrapper))
|
|
196
|
+
│
|
|
197
|
+
Shared Subscriber (RpcCoordinator)
|
|
198
|
+
│
|
|
199
|
+
├─→ 11. pmessage event → handleRpcMessage(channel, message)
|
|
200
|
+
│ ├─→ Ekstraktuj correlationId z channel
|
|
201
|
+
│ ├─→ Weryfikuj processInstanceId (process isolation)
|
|
202
|
+
│ ├─→ Znajdź pending call w Map
|
|
203
|
+
│ ├─→ PayloadCompressionService.decompress(wrapper.data, wrapper.compressed)
|
|
204
|
+
│ ├─→ resolve(result) lub reject(error)
|
|
205
|
+
│ └─→ Cleanup: clearTimeout, delete z Map
|
|
206
|
+
│
|
|
207
|
+
└─→ 12. User Code otrzymuje wynik → Promise<T> resolved
|
|
40
208
|
```
|
|
41
209
|
|
|
42
210
|
## Instalacja
|
|
@@ -64,7 +232,7 @@ const config = new CommandBusConfig({
|
|
|
64
232
|
logger: console, // ILogger interface
|
|
65
233
|
logLevel: 'log', // debug | log | warn | error
|
|
66
234
|
concurrency: 5,
|
|
67
|
-
maxAttempts:
|
|
235
|
+
maxAttempts: 1,
|
|
68
236
|
});
|
|
69
237
|
|
|
70
238
|
// Utwórz instancję CommandBus
|
|
@@ -116,31 +284,164 @@ const result = await commandBus.call<{ userId: string }>(command, 5000); // time
|
|
|
116
284
|
console.log(`User created with ID: ${result.userId}`);
|
|
117
285
|
```
|
|
118
286
|
|
|
287
|
+
## Zmienne Środowiskowe
|
|
288
|
+
|
|
289
|
+
Biblioteka wspiera konfigurację poprzez zmienne środowiskowe z prefiksem `COMMAND_BUS_` (z fallbackiem do starszych nazw `EVENT_BUS_*`):
|
|
290
|
+
|
|
291
|
+
### Kompletna Lista Zmiennych
|
|
292
|
+
|
|
293
|
+
| Zmienna | Typ | Wartość Domyślna | Opis |
|
|
294
|
+
|---------|-----|------------------|------|
|
|
295
|
+
| `REDIS_URL` | string | `redis://localhost:6379` | URL połączenia Redis/DragonflyDB (wspiera username, password, db) |
|
|
296
|
+
| `LOG_LEVEL` | enum | `log` | Poziom logowania: `debug`, `log`, `warn`, `error` |
|
|
297
|
+
| `COMMAND_BUS_CONCURRENCY` | number | `1` (lub auto) | Liczba równoległych workerów do przetwarzania komend |
|
|
298
|
+
| `COMMAND_BUS_MAX_ATTEMPTS` | number | `1` | Maksymalna liczba prób przetworzenia zadania |
|
|
299
|
+
| `COMMAND_BUS_BACKOFF_DELAY` | number | `2000` | Opóźnienie między próbami w milisekundach |
|
|
300
|
+
| `COMMAND_BUS_QUEUE_MODE` | enum | `fifo` | Tryb przetwarzania kolejki: `fifo` (First In First Out) lub `lifo` (Last In First Out) |
|
|
301
|
+
| `COMMAND_BUS_LOG` | string | _(puste)_ | Ścieżka do katalogu logów komend (JSONL format, rotacja co godzinę) |
|
|
302
|
+
| `COMMAND_BUS_AUTO_OPTIMIZE` | boolean | `true` | Włącz auto-optymalizację concurrency na podstawie CPU/RAM |
|
|
303
|
+
| `COMMAND_BUS_COMPRESSION_THRESHOLD` | number | `1024` | Próg kompresji gzip dla payloadów RPC w bajtach (1KB domyślnie) |
|
|
304
|
+
|
|
305
|
+
### Fallback do Starszych Nazw
|
|
306
|
+
|
|
307
|
+
Dla kompatybilności wstecznej obsługiwane są również prefiksy `EVENT_BUS_*`:
|
|
308
|
+
|
|
309
|
+
- `EVENT_BUS_CONCURRENCY` → `COMMAND_BUS_CONCURRENCY`
|
|
310
|
+
- `EVENT_BUS_MAX_ATTEMPTS` → `COMMAND_BUS_MAX_ATTEMPTS`
|
|
311
|
+
- `EVENT_BUS_BACKOFF_DELAY` → `COMMAND_BUS_BACKOFF_DELAY`
|
|
312
|
+
- `EVENT_BUS_QUEUE_MODE` → `COMMAND_BUS_QUEUE_MODE`
|
|
313
|
+
- `EVENT_BUS_LOG` → `COMMAND_BUS_LOG`
|
|
314
|
+
|
|
315
|
+
### Przykład Konfiguracji .env
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
# Połączenie Redis
|
|
319
|
+
REDIS_URL=redis://username:password@localhost:6379/0
|
|
320
|
+
|
|
321
|
+
# Poziom logowania
|
|
322
|
+
LOG_LEVEL=log # debug | log | warn | error
|
|
323
|
+
|
|
324
|
+
# Auto-optymalizacja (domyślnie włączona)
|
|
325
|
+
COMMAND_BUS_AUTO_OPTIMIZE=true
|
|
326
|
+
|
|
327
|
+
# Concurrency (opcjonalnie - auto-optymalizacja ustawi optymalną wartość)
|
|
328
|
+
# COMMAND_BUS_CONCURRENCY=10
|
|
329
|
+
|
|
330
|
+
# Retry i backoff
|
|
331
|
+
COMMAND_BUS_MAX_ATTEMPTS=1 # Maksymalna liczba prób
|
|
332
|
+
COMMAND_BUS_BACKOFF_DELAY=3000 # Opóźnienie między próbami (3s)
|
|
333
|
+
|
|
334
|
+
# Tryb kolejki
|
|
335
|
+
COMMAND_BUS_QUEUE_MODE=fifo # fifo lub lifo
|
|
336
|
+
|
|
337
|
+
# Kompresja payloadów (próg w bajtach)
|
|
338
|
+
COMMAND_BUS_COMPRESSION_THRESHOLD=2048 # 2KB (domyślnie 1KB)
|
|
339
|
+
|
|
340
|
+
# Logowanie komend do plików
|
|
341
|
+
COMMAND_BUS_LOG=./command-logs # Ścieżka do katalogu logów
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Priorytety Konfiguracji
|
|
345
|
+
|
|
346
|
+
1. **Parametry konstruktora** - najwyższy priorytet
|
|
347
|
+
2. **Zmienne środowiskowe** - średni priorytet
|
|
348
|
+
3. **Wartości domyślne** - najniższy priorytet
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// Przykład: parametry konstruktora nadpisują ENV
|
|
352
|
+
const config = new CommandBusConfig({
|
|
353
|
+
redisUrl: 'redis://localhost:6379',
|
|
354
|
+
concurrency: 20, // Nadpisuje COMMAND_BUS_CONCURRENCY
|
|
355
|
+
autoOptimize: false, // Wyłącza auto-optymalizację
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
119
359
|
## Konfiguracja zaawansowana
|
|
120
360
|
|
|
121
|
-
###
|
|
361
|
+
### Auto-optymalizacja Concurrency
|
|
362
|
+
|
|
363
|
+
Auto-optymalizacja automatycznie oblicza optymalną wartość concurrency na podstawie zasobów systemowych:
|
|
122
364
|
|
|
123
365
|
```typescript
|
|
366
|
+
// Auto-optymalizacja włączona (domyślnie)
|
|
124
367
|
const config = new CommandBusConfig({
|
|
125
|
-
redisUrl: 'redis://
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
368
|
+
redisUrl: 'redis://localhost:6379',
|
|
369
|
+
autoOptimize: true, // Domyślnie true
|
|
370
|
+
// concurrency zostanie obliczone jako: CPU cores * 2 + (availableMemory / 512MB)
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Wyłączenie auto-optymalizacji
|
|
374
|
+
const config2 = new CommandBusConfig({
|
|
375
|
+
redisUrl: 'redis://localhost:6379',
|
|
376
|
+
autoOptimize: false,
|
|
377
|
+
concurrency: 10, // Ręczna wartość
|
|
132
378
|
});
|
|
133
379
|
```
|
|
134
380
|
|
|
381
|
+
**Algorytm auto-optymalizacji**:
|
|
382
|
+
```
|
|
383
|
+
concurrency = (CPU cores * 2) + Math.floor(availableMemory / 512MB)
|
|
384
|
+
|
|
385
|
+
Przykład:
|
|
386
|
+
- 8 CPU cores
|
|
387
|
+
- 16GB RAM dostępne
|
|
388
|
+
- concurrency = (8 * 2) + Math.floor(16384 / 512) = 16 + 32 = 48
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Kompresja Payloadów
|
|
392
|
+
|
|
393
|
+
Automatyczna kompresja gzip dla payloadów RPC większych niż threshold:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const config = new CommandBusConfig({
|
|
397
|
+
redisUrl: 'redis://localhost:6379',
|
|
398
|
+
compressionThreshold: 2048, // 2KB (domyślnie 1KB)
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Przykład: payload 3KB zostanie automatycznie skompresowany
|
|
402
|
+
const largeCommand = new ProcessReportCommand(largeData); // 3KB
|
|
403
|
+
const result = await commandBus.call(largeCommand); // Automatyczna kompresja/dekompresja
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Korzyści kompresji**:
|
|
407
|
+
- Redukcja transferu danych przez Redis
|
|
408
|
+
- Szybsze przesyłanie dużych payloadów
|
|
409
|
+
- Niższe zużycie pamięci Redis
|
|
410
|
+
- Transparent dla użytkownika (automatyczna dekompresja)
|
|
411
|
+
|
|
135
412
|
### Command Logging
|
|
136
413
|
|
|
137
|
-
Logowanie komend do plików (
|
|
414
|
+
Logowanie komend do plików JSONL (rotacja co godzinę):
|
|
138
415
|
|
|
139
416
|
```typescript
|
|
140
417
|
const config = new CommandBusConfig({
|
|
141
418
|
redisUrl: 'redis://localhost:6379',
|
|
142
419
|
commandLog: './command-logs', // Ścieżka do katalogu logów
|
|
143
420
|
});
|
|
421
|
+
|
|
422
|
+
// Struktura plików:
|
|
423
|
+
// ./command-logs/2025-01-27T10.jsonl
|
|
424
|
+
// ./command-logs/2025-01-27T11.jsonl
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Format JSONL (JSON Lines)**:
|
|
428
|
+
```json
|
|
429
|
+
{"__name":"CreateUserCommand","__id":"uuid","__time":1706347200000,"email":"jan@example.com","name":"Jan"}
|
|
430
|
+
{"__name":"ProcessOrderCommand","__id":"uuid","__time":1706347201000,"orderId":"12345"}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Opcje Redis
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
const config = new CommandBusConfig({
|
|
437
|
+
redisUrl: 'redis://username:password@localhost:6379/0',
|
|
438
|
+
// Parsuje się do:
|
|
439
|
+
// - host: localhost
|
|
440
|
+
// - port: 6379
|
|
441
|
+
// - username: username (opcjonalnie)
|
|
442
|
+
// - password: password (opcjonalnie)
|
|
443
|
+
// - db: 0 (opcjonalnie)
|
|
444
|
+
});
|
|
144
445
|
```
|
|
145
446
|
|
|
146
447
|
### Concurrency i Retry
|
|
@@ -148,19 +449,18 @@ const config = new CommandBusConfig({
|
|
|
148
449
|
```typescript
|
|
149
450
|
const config = new CommandBusConfig({
|
|
150
451
|
redisUrl: 'redis://localhost:6379',
|
|
151
|
-
concurrency: 10, // Liczba równoległych workerów
|
|
452
|
+
concurrency: 10, // Liczba równoległych workerów (lub auto)
|
|
152
453
|
maxAttempts: 5, // Maksymalna liczba prób przetworzenia zadania
|
|
153
|
-
backoffDelay:
|
|
454
|
+
backoffDelay: 3000, // Opóźnienie między próbami (3s)
|
|
154
455
|
});
|
|
155
456
|
```
|
|
156
457
|
|
|
157
|
-
### Tryb kolejki
|
|
458
|
+
### Tryb kolejki
|
|
158
459
|
|
|
159
460
|
```typescript
|
|
160
461
|
const config = new CommandBusConfig({
|
|
161
462
|
redisUrl: 'redis://localhost:6379',
|
|
162
463
|
queueMode: 'fifo', // 'fifo' (First In First Out) lub 'lifo' (Last In First Out)
|
|
163
|
-
rpcReplyTtl: 60000, // TTL dla odpowiedzi RPC w milisekundach (1 minuta)
|
|
164
464
|
});
|
|
165
465
|
```
|
|
166
466
|
|
|
@@ -207,24 +507,46 @@ const config2 = new CommandBusConfig({
|
|
|
207
507
|
|
|
208
508
|
Wysyła komendę do kolejki bez oczekiwania na wynik (fire-and-forget).
|
|
209
509
|
|
|
510
|
+
**Flow**:
|
|
511
|
+
1. Kompresja payloadu (jeśli >threshold)
|
|
512
|
+
2. Pobranie/utworzenie kolejki z cache
|
|
513
|
+
3. Dodanie job do BullMQ
|
|
514
|
+
4. Natychmiastowy return
|
|
515
|
+
|
|
210
516
|
```typescript
|
|
211
517
|
await commandBus.dispatch(new CreateUserCommand('email@example.com', 'Jan Kowalski'));
|
|
212
518
|
```
|
|
213
519
|
|
|
214
520
|
#### `call<T>(command: Command, timeout?: number): Promise<T>`
|
|
215
521
|
|
|
216
|
-
Wywołuje komendę synchronicznie i czeka na odpowiedź (RPC). Domyślny timeout: 30000ms (30s).
|
|
522
|
+
Wywołuje komendę synchronicznie i czeka na odpowiedź przez Redis Pub/Sub (RPC). Domyślny timeout: 30000ms (30s).
|
|
523
|
+
|
|
524
|
+
**Flow**:
|
|
525
|
+
1. Rejestracja pending call z timeoutem
|
|
526
|
+
2. Kompresja payloadu (jeśli >threshold)
|
|
527
|
+
3. Przygotowanie metadanych RPC (correlationId, responseChannel)
|
|
528
|
+
4. Dodanie job do BullMQ
|
|
529
|
+
5. Oczekiwanie na odpowiedź przez shared subscriber
|
|
530
|
+
6. Dekompresja odpowiedzi
|
|
531
|
+
7. Zwrócenie wyniku lub błędu
|
|
217
532
|
|
|
218
533
|
```typescript
|
|
219
534
|
const result = await commandBus.call<{ userId: string }>(command, 5000);
|
|
220
535
|
```
|
|
221
536
|
|
|
222
|
-
#### `handle<T>(commandClass, handler):
|
|
537
|
+
#### `handle<T>(commandClass, handler): void`
|
|
538
|
+
|
|
539
|
+
Rejestruje handler dla określonej klasy komendy. Tylko jeden handler per typ komendy.
|
|
223
540
|
|
|
224
|
-
|
|
541
|
+
**Automatyczne akcje**:
|
|
542
|
+
1. Rejestracja handlera w Map
|
|
543
|
+
2. Utworzenie workera BullMQ
|
|
544
|
+
3. Uruchomienie benchmarku dla optymalnego concurrency
|
|
545
|
+
4. Utworzenie metrics collector
|
|
546
|
+
5. Setup event handlers
|
|
225
547
|
|
|
226
548
|
```typescript
|
|
227
|
-
|
|
549
|
+
commandBus.handle(CreateUserCommand, async (command) => {
|
|
228
550
|
const user = await createUser(command.email, command.name);
|
|
229
551
|
return { userId: user.id };
|
|
230
552
|
});
|
|
@@ -232,7 +554,13 @@ await commandBus.handle(CreateUserCommand, async (command) => {
|
|
|
232
554
|
|
|
233
555
|
#### `close(): Promise<void>`
|
|
234
556
|
|
|
235
|
-
Zamyka wszystkie połączenia i workery.
|
|
557
|
+
Zamyka wszystkie połączenia i workery z graceful shutdown.
|
|
558
|
+
|
|
559
|
+
**Cleanup**:
|
|
560
|
+
1. Zamknięcie wszystkich workerów BullMQ
|
|
561
|
+
2. Zamknięcie wszystkich kolejek z cache
|
|
562
|
+
3. Zamknięcie RpcCoordinator (reject pending calls)
|
|
563
|
+
4. Zamknięcie 3 połączeń Redis (Queue, Worker, RPC)
|
|
236
564
|
|
|
237
565
|
```typescript
|
|
238
566
|
await commandBus.close();
|
|
@@ -250,10 +578,13 @@ class MyCommand extends Command {
|
|
|
250
578
|
}
|
|
251
579
|
```
|
|
252
580
|
|
|
253
|
-
|
|
254
|
-
- `__id` - unikalny UUID
|
|
255
|
-
- `__name` - nazwa klasy komendy
|
|
256
|
-
- `
|
|
581
|
+
**Właściwości automatyczne**:
|
|
582
|
+
- `__id` - unikalny UUID (randomUUID)
|
|
583
|
+
- `__name` - nazwa klasy komendy (constructor.name)
|
|
584
|
+
- `__time` - timestamp utworzenia (Date.now())
|
|
585
|
+
|
|
586
|
+
**Metody statyczne**:
|
|
587
|
+
- `reconstructDates(obj)` - rekonstrukcja obiektów Date z serializowanych danych
|
|
257
588
|
|
|
258
589
|
### CommandBusConfig
|
|
259
590
|
|
|
@@ -264,72 +595,205 @@ interface CommandBusConfigOptions {
|
|
|
264
595
|
redisUrl?: string; // URL Redis (domyślnie: 'redis://localhost:6379' lub REDIS_URL)
|
|
265
596
|
logger?: ILogger; // Logger (domyślnie console)
|
|
266
597
|
logLevel?: 'debug' | 'log' | 'warn' | 'error'; // Poziom logowania (domyślnie 'log')
|
|
267
|
-
concurrency?: number; // Liczba workerów
|
|
268
|
-
maxAttempts?: number; // Maksymalna liczba prób (domyślnie
|
|
598
|
+
concurrency?: number; // Liczba workerów (domyślnie 1 lub auto)
|
|
599
|
+
maxAttempts?: number; // Maksymalna liczba prób (domyślnie 1)
|
|
269
600
|
backoffDelay?: number; // Opóźnienie między próbami w ms (domyślnie 2000)
|
|
270
601
|
queueMode?: 'fifo' | 'lifo'; // Tryb kolejki (domyślnie 'fifo')
|
|
271
602
|
commandLog?: string; // Ścieżka do katalogu logów komend (opcjonalnie)
|
|
603
|
+
autoOptimize?: boolean; // Auto-optymalizacja concurrency (domyślnie true)
|
|
604
|
+
compressionThreshold?: number; // Próg kompresji w bajtach (domyślnie 1024)
|
|
272
605
|
}
|
|
273
606
|
```
|
|
274
607
|
|
|
275
|
-
##
|
|
608
|
+
## Przykłady Użycia
|
|
276
609
|
|
|
277
|
-
|
|
610
|
+
### Podstawowy przykład z RPC
|
|
278
611
|
|
|
279
|
-
|
|
612
|
+
```typescript
|
|
613
|
+
import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
|
|
614
|
+
|
|
615
|
+
// Definicja komendy
|
|
616
|
+
class CalculateCommand extends Command {
|
|
617
|
+
constructor(
|
|
618
|
+
public readonly a: number,
|
|
619
|
+
public readonly b: number,
|
|
620
|
+
public readonly operation: 'add' | 'multiply',
|
|
621
|
+
) {
|
|
622
|
+
super();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Konfiguracja
|
|
627
|
+
const config = new CommandBusConfig({
|
|
628
|
+
redisUrl: 'redis://localhost:6379',
|
|
629
|
+
});
|
|
630
|
+
const commandBus = new CommandBus(config);
|
|
631
|
+
|
|
632
|
+
// Rejestracja handlera
|
|
633
|
+
commandBus.handle(CalculateCommand, async (command) => {
|
|
634
|
+
console.log(`Calculating: ${command.a} ${command.operation} ${command.b}`);
|
|
635
|
+
|
|
636
|
+
switch (command.operation) {
|
|
637
|
+
case 'add':
|
|
638
|
+
return command.a + command.b;
|
|
639
|
+
case 'multiply':
|
|
640
|
+
return command.a * command.b;
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// Fire-and-forget
|
|
645
|
+
await commandBus.dispatch(new CalculateCommand(5, 3, 'add'));
|
|
646
|
+
|
|
647
|
+
// RPC - czekamy na wynik
|
|
648
|
+
const result = await commandBus.call<number>(
|
|
649
|
+
new CalculateCommand(5, 3, 'multiply'),
|
|
650
|
+
5000 // timeout 5s
|
|
651
|
+
);
|
|
652
|
+
console.log(`Result: ${result}`); // Result: 15
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Równoległe wywołania RPC
|
|
280
656
|
|
|
281
657
|
```typescript
|
|
282
|
-
|
|
658
|
+
// Wiele równoległych RPC calls
|
|
659
|
+
const [result1, result2, result3] = await Promise.all([
|
|
660
|
+
commandBus.call<number>(new CalculateCommand(10, 5, 'add')),
|
|
661
|
+
commandBus.call<UserInfo>(new GetUserInfoCommand('user-1')),
|
|
662
|
+
commandBus.call<ValidationResult>(new ValidateUserCommand('jan@example.com', 30)),
|
|
663
|
+
]);
|
|
664
|
+
|
|
665
|
+
console.log('All results:', { result1, result2, result3 });
|
|
283
666
|
```
|
|
284
667
|
|
|
285
|
-
###
|
|
668
|
+
### Obsługa błędów
|
|
286
669
|
|
|
287
|
-
```
|
|
288
|
-
|
|
670
|
+
```typescript
|
|
671
|
+
commandBus.handle(CreateUserCommand, async (command) => {
|
|
672
|
+
try {
|
|
673
|
+
// Walidacja
|
|
674
|
+
if (!command.email.includes('@')) {
|
|
675
|
+
throw new Error('Invalid email format');
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const user = await createUser(command.email, command.name);
|
|
679
|
+
return { userId: user.id };
|
|
680
|
+
} catch (error) {
|
|
681
|
+
// Loguj błąd
|
|
682
|
+
console.error('Failed to create user:', error);
|
|
683
|
+
|
|
684
|
+
// Rzuć błąd - BullMQ spróbuje ponownie (maxAttempts)
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// Obsługa błędów w RPC
|
|
690
|
+
try {
|
|
691
|
+
const result = await commandBus.call(new CreateUserCommand('invalid-email', 'Jan'));
|
|
692
|
+
} catch (error) {
|
|
693
|
+
console.error('RPC failed:', error.message);
|
|
694
|
+
}
|
|
289
695
|
```
|
|
290
696
|
|
|
697
|
+
### Graceful Shutdown
|
|
698
|
+
|
|
291
699
|
```typescript
|
|
292
|
-
|
|
700
|
+
process.on('SIGTERM', async () => {
|
|
701
|
+
console.log('Shutting down CommandBus...');
|
|
702
|
+
await commandBus.close();
|
|
703
|
+
process.exit(0);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
process.on('SIGINT', async () => {
|
|
707
|
+
console.log('Shutting down CommandBus...');
|
|
708
|
+
await commandBus.close();
|
|
709
|
+
process.exit(0);
|
|
710
|
+
});
|
|
293
711
|
```
|
|
294
712
|
|
|
295
|
-
|
|
713
|
+
## Zaawansowane Funkcje
|
|
296
714
|
|
|
297
|
-
|
|
715
|
+
### 1. Dynamiczne Concurrency
|
|
298
716
|
|
|
299
|
-
|
|
717
|
+
WorkerOrchestrator automatycznie dostosowuje concurrency na podstawie metryk:
|
|
300
718
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
719
|
+
- **Benchmark przy starcie** - optymalny concurrency dla każdego workera
|
|
720
|
+
- **Event-driven metrics** - zbieranie metryk z workerów
|
|
721
|
+
- **Dynamiczne dostosowanie** - +/-20% co 30s (cooldown)
|
|
722
|
+
- **Limity** - min 10, max 2000
|
|
304
723
|
|
|
305
|
-
|
|
306
|
-
|
|
724
|
+
```typescript
|
|
725
|
+
// Automatyczne - benchmark ustali optymalną wartość
|
|
726
|
+
const config = new CommandBusConfig({
|
|
727
|
+
redisUrl: 'redis://localhost:6379',
|
|
728
|
+
// concurrency zostanie ustalone przez benchmark (np. 15-20)
|
|
729
|
+
});
|
|
730
|
+
```
|
|
307
731
|
|
|
308
|
-
|
|
309
|
-
COMMAND_BUS_CONCURRENCY=5 # Liczba workerów komend (fallback: EVENT_BUS_CONCURRENCY)
|
|
732
|
+
### 2. Process Isolation w RPC
|
|
310
733
|
|
|
311
|
-
|
|
312
|
-
COMMAND_BUS_MAX_ATTEMPTS=3 # Maksymalna liczba prób (fallback: EVENT_BUS_MAX_ATTEMPTS)
|
|
313
|
-
COMMAND_BUS_BACKOFF_DELAY=2000 # Opóźnienie między próbami w ms (fallback: EVENT_BUS_BACKOFF_DELAY)
|
|
734
|
+
Każdy proces Node.js ma unikalny UUID - odpowiedzi RPC są izolowane między procesami:
|
|
314
735
|
|
|
315
|
-
|
|
316
|
-
|
|
736
|
+
```
|
|
737
|
+
Process A (UUID: abc-123):
|
|
738
|
+
- Kanał: rpc:response:abc-123:*
|
|
739
|
+
- Otrzymuje tylko swoje odpowiedzi
|
|
317
740
|
|
|
318
|
-
|
|
319
|
-
|
|
741
|
+
Process B (UUID: def-456):
|
|
742
|
+
- Kanał: rpc:response:def-456:*
|
|
743
|
+
- Otrzymuje tylko swoje odpowiedzi
|
|
320
744
|
```
|
|
321
745
|
|
|
322
|
-
###
|
|
746
|
+
### 3. Shared Subscriber Pattern
|
|
747
|
+
|
|
748
|
+
Jeden shared subscriber dla wszystkich RPC calls zamiast N subskrybentów:
|
|
323
749
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
750
|
+
**Tradycyjne podejście** (N subskrybentów):
|
|
751
|
+
```
|
|
752
|
+
1000 RPC calls → 1000 Redis subscriptions → duże obciążenie
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Shared Subscriber** (1 subskrybent):
|
|
756
|
+
```
|
|
757
|
+
1000 RPC calls → 1 Redis pattern subscription → multiplexing w pamięci
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### 4. Automatyczna Kompresja
|
|
761
|
+
|
|
762
|
+
PayloadCompressionService automatycznie kompresuje duże payloady:
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
// Mała komenda (<1KB) - brak kompresji
|
|
766
|
+
const smallCommand = new CreateUserCommand('jan@example.com', 'Jan');
|
|
767
|
+
await commandBus.call(smallCommand); // Bez kompresji
|
|
768
|
+
|
|
769
|
+
// Duża komenda (>1KB) - automatyczna kompresja
|
|
770
|
+
const largeCommand = new ProcessReportCommand(largeData); // 5KB
|
|
771
|
+
await commandBus.call(largeCommand); // Automatyczna kompresja gzip → base64
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**Flagi kompresji**:
|
|
775
|
+
- `__compressed: true` - payload został skompresowany
|
|
776
|
+
- Automatyczna dekompresja w JobProcessor
|
|
777
|
+
- Transparent dla użytkownika
|
|
778
|
+
|
|
779
|
+
### 5. Command Logging
|
|
780
|
+
|
|
781
|
+
Persystencja wszystkich komend do plików JSONL:
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
const config = new CommandBusConfig({
|
|
785
|
+
commandLog: './command-logs',
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
// Każda komenda jest logowana do pliku:
|
|
789
|
+
// ./command-logs/2025-01-27T10.jsonl
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
**Use cases**:
|
|
793
|
+
- Auditing i compliance
|
|
794
|
+
- Replay komend
|
|
795
|
+
- Debugging produkcyjnych problemów
|
|
796
|
+
- Analiza przepływu komend
|
|
333
797
|
|
|
334
798
|
## Best Practices
|
|
335
799
|
|
|
@@ -364,42 +828,55 @@ commandBus.handle(CreateUserCommand, async (command) => {
|
|
|
364
828
|
});
|
|
365
829
|
```
|
|
366
830
|
|
|
367
|
-
### 3.
|
|
831
|
+
### 3. Monitorowanie RPC timeoutów
|
|
368
832
|
|
|
369
833
|
```typescript
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return { userId: user.id };
|
|
374
|
-
} catch (error) {
|
|
375
|
-
// Loguj błąd
|
|
376
|
-
console.error('Failed to create user:', error);
|
|
834
|
+
// Ustaw timeout dostosowany do czasu przetwarzania komendy
|
|
835
|
+
const shortCommand = new QuickCommand();
|
|
836
|
+
const result1 = await commandBus.call(shortCommand, 1000); // 1s dla szybkich operacji
|
|
377
837
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
});
|
|
838
|
+
const longRunningCommand = new ProcessReportCommand();
|
|
839
|
+
const result2 = await commandBus.call(longRunningCommand, 60000); // 60s dla długich operacji
|
|
382
840
|
```
|
|
383
841
|
|
|
384
|
-
### 4.
|
|
842
|
+
### 4. Walidacja w handlerach
|
|
385
843
|
|
|
386
844
|
```typescript
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
845
|
+
commandBus.handle(CreateUserCommand, async (command) => {
|
|
846
|
+
// Walidacja na początku
|
|
847
|
+
if (!command.email || !command.email.includes('@')) {
|
|
848
|
+
throw new Error('Invalid email');
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (!command.name || command.name.length < 2) {
|
|
852
|
+
throw new Error('Invalid name');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Logika biznesowa
|
|
856
|
+
return await createUser(command.email, command.name);
|
|
391
857
|
});
|
|
392
858
|
```
|
|
393
859
|
|
|
394
|
-
### 5.
|
|
860
|
+
### 5. Logowanie kontekstu
|
|
395
861
|
|
|
396
862
|
```typescript
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
863
|
+
commandBus.handle(CreateUserCommand, async (command) => {
|
|
864
|
+
console.log('Processing CreateUserCommand', {
|
|
865
|
+
commandId: command.__id,
|
|
866
|
+
timestamp: command.__time,
|
|
867
|
+
email: command.email,
|
|
868
|
+
});
|
|
400
869
|
|
|
401
|
-
|
|
402
|
-
const
|
|
870
|
+
// Logika biznesowa
|
|
871
|
+
const user = await createUser(command.email, command.name);
|
|
872
|
+
|
|
873
|
+
console.log('User created successfully', {
|
|
874
|
+
commandId: command.__id,
|
|
875
|
+
userId: user.id,
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
return { userId: user.id };
|
|
879
|
+
});
|
|
403
880
|
```
|
|
404
881
|
|
|
405
882
|
## Testing
|
|
@@ -416,12 +893,13 @@ describe('User Commands', () => {
|
|
|
416
893
|
const config = new CommandBusConfig({
|
|
417
894
|
redisUrl: 'redis://localhost:6379',
|
|
418
895
|
logger: console,
|
|
896
|
+
logLevel: 'error', // Tylko błędy w testach
|
|
419
897
|
});
|
|
420
898
|
|
|
421
899
|
commandBus = new CommandBus(config);
|
|
422
900
|
|
|
423
901
|
// Zarejestruj handler
|
|
424
|
-
|
|
902
|
+
commandBus.handle(CreateUserCommand, async (command) => {
|
|
425
903
|
return { userId: 'test-user-id' };
|
|
426
904
|
});
|
|
427
905
|
});
|
|
@@ -435,6 +913,24 @@ describe('User Commands', () => {
|
|
|
435
913
|
const result = await commandBus.call<{ userId: string }>(command, 5000);
|
|
436
914
|
|
|
437
915
|
expect(result.userId).toBeDefined();
|
|
916
|
+
expect(result.userId).toBe('test-user-id');
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
it('should handle multiple commands in parallel', async () => {
|
|
920
|
+
const commands = [
|
|
921
|
+
new CreateUserCommand('user1@example.com', 'User 1'),
|
|
922
|
+
new CreateUserCommand('user2@example.com', 'User 2'),
|
|
923
|
+
new CreateUserCommand('user3@example.com', 'User 3'),
|
|
924
|
+
];
|
|
925
|
+
|
|
926
|
+
const results = await Promise.all(
|
|
927
|
+
commands.map((cmd) => commandBus.call<{ userId: string }>(cmd, 5000))
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
expect(results).toHaveLength(3);
|
|
931
|
+
results.forEach((result) => {
|
|
932
|
+
expect(result.userId).toBeDefined();
|
|
933
|
+
});
|
|
438
934
|
});
|
|
439
935
|
});
|
|
440
936
|
```
|
|
@@ -459,7 +955,7 @@ Zwiększ timeout lub sprawdź czy handler został zarejestrowany:
|
|
|
459
955
|
const result = await commandBus.call(command, 60000); // 60s
|
|
460
956
|
|
|
461
957
|
// Sprawdź czy handler został zarejestrowany PRZED wywołaniem
|
|
462
|
-
|
|
958
|
+
commandBus.handle(MyCommand, async (command) => {
|
|
463
959
|
// Handler implementation
|
|
464
960
|
return { result: 'success' };
|
|
465
961
|
});
|
|
@@ -475,19 +971,102 @@ Upewnij się że handler został zarejestrowany przed wysłaniem komendy:
|
|
|
475
971
|
```typescript
|
|
476
972
|
// ❌ ŹLE - handler po dispatch
|
|
477
973
|
await commandBus.dispatch(new MyCommand());
|
|
478
|
-
|
|
974
|
+
commandBus.handle(MyCommand, async (cmd) => { ... }); // Za późno!
|
|
479
975
|
|
|
480
976
|
// ✅ DOBRZE - handler przed dispatch
|
|
481
|
-
|
|
977
|
+
commandBus.handle(MyCommand, async (cmd) => { ... });
|
|
482
978
|
await commandBus.dispatch(new MyCommand()); // Teraz OK
|
|
483
979
|
```
|
|
484
980
|
|
|
485
|
-
|
|
981
|
+
### Wysokie zużycie pamięci
|
|
486
982
|
|
|
487
|
-
|
|
983
|
+
1. **Wyłącz command logging** jeśli nie jest potrzebne
|
|
984
|
+
2. **Zmniejsz concurrency** jeśli workery używają dużo pamięci
|
|
985
|
+
3. **Zwiększ compressionThreshold** jeśli duże payloady powodują problemy
|
|
488
986
|
|
|
489
|
-
|
|
490
|
-
|
|
987
|
+
```typescript
|
|
988
|
+
const config = new CommandBusConfig({
|
|
989
|
+
commandLog: undefined, // Wyłącz logging
|
|
990
|
+
concurrency: 5, // Zmniejsz concurrency
|
|
991
|
+
compressionThreshold: 512, // Kompresuj już od 512B
|
|
992
|
+
});
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### Worker stalled
|
|
996
|
+
|
|
997
|
+
Worker został zatrzymany (prawdopodobnie crashed). BullMQ automatycznie przeniesie job do innego workera.
|
|
998
|
+
|
|
999
|
+
**Przyczyny**:
|
|
1000
|
+
- Out of memory
|
|
1001
|
+
- Uncaught exception w handlerze
|
|
1002
|
+
- Timeout w handlerze
|
|
1003
|
+
|
|
1004
|
+
**Rozwiązanie**:
|
|
1005
|
+
- Dodaj try/catch w handlerze
|
|
1006
|
+
- Zwiększ pamięć dla procesu
|
|
1007
|
+
- Zmniejsz concurrency
|
|
1008
|
+
|
|
1009
|
+
## Struktura Projektu
|
|
1010
|
+
|
|
1011
|
+
```
|
|
1012
|
+
src/
|
|
1013
|
+
├── command-bus/ # Główna logika CommandBus
|
|
1014
|
+
│ ├── config/ # Konfiguracja CommandBus
|
|
1015
|
+
│ │ ├── command-bus-config.ts
|
|
1016
|
+
│ │ └── auto-config-optimizer.ts
|
|
1017
|
+
│ ├── job/ # Przetwarzanie jobów i opcje
|
|
1018
|
+
│ │ ├── job-processor.ts
|
|
1019
|
+
│ │ └── job-options-builder.ts
|
|
1020
|
+
│ ├── logging/ # Command logging do plików
|
|
1021
|
+
│ │ └── command-logger.ts
|
|
1022
|
+
│ ├── queue/ # Queue management i cache
|
|
1023
|
+
│ │ └── queue-manager.ts
|
|
1024
|
+
│ ├── rpc/ # RPC coordination
|
|
1025
|
+
│ │ ├── rpc-coordinator.ts
|
|
1026
|
+
│ │ └── payload-compression.service.ts
|
|
1027
|
+
│ ├── worker/ # Worker orchestration
|
|
1028
|
+
│ │ ├── worker-orchestrator.ts
|
|
1029
|
+
│ │ ├── worker-benchmark.ts
|
|
1030
|
+
│ │ └── worker-metrics-collector.ts
|
|
1031
|
+
│ ├── types/ # Typy TypeScript
|
|
1032
|
+
│ │ └── index.ts
|
|
1033
|
+
│ ├── command.ts # Klasa bazowa Command
|
|
1034
|
+
│ └── index.ts # CommandBus główna klasa
|
|
1035
|
+
├── shared/ # Wspólne komponenty
|
|
1036
|
+
│ ├── config/ # Base config z Redis
|
|
1037
|
+
│ │ └── base-config.ts
|
|
1038
|
+
│ ├── logging/ # Logger wrapper z poziomami
|
|
1039
|
+
│ │ ├── logger.ts
|
|
1040
|
+
│ │ └── log-level.ts
|
|
1041
|
+
│ └── types.ts # Współdzielone typy
|
|
1042
|
+
├── examples/ # Przykłady użycia
|
|
1043
|
+
│ ├── rpc.demo.ts # Demo RPC calls
|
|
1044
|
+
│ ├── rpc-throughput.demo.ts # Demo wydajności RPC
|
|
1045
|
+
│ └── rpc-compression.demo.ts # Demo kompresji
|
|
1046
|
+
└── index.ts # Główny export pakietu
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
## Migracja z pp-event-bus 1.x
|
|
1050
|
+
|
|
1051
|
+
Jeśli używałeś Command Bus z pakietu `pp-event-bus` w wersji 1.x:
|
|
1052
|
+
|
|
1053
|
+
### Przed:
|
|
1054
|
+
|
|
1055
|
+
```typescript
|
|
1056
|
+
import { CommandBus, Command, CommandBusConfig } from 'pp-event-bus';
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
### Po:
|
|
1060
|
+
|
|
1061
|
+
```bash
|
|
1062
|
+
npm install pp-command-bus
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
```typescript
|
|
1066
|
+
import { CommandBus, Command, CommandBusConfig } from 'pp-command-bus';
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
**Pełna zgodność API** - jedyna zmiana to źródło importu.
|
|
491
1070
|
|
|
492
1071
|
## Wersjonowanie i Releases
|
|
493
1072
|
|
|
@@ -528,56 +1107,6 @@ test: dodano testy dla command logging
|
|
|
528
1107
|
- `chore:` - inne zmiany (bez release)
|
|
529
1108
|
- `!` lub `BREAKING CHANGE:` - breaking change (→ major release)
|
|
530
1109
|
|
|
531
|
-
### Proces release
|
|
532
|
-
|
|
533
|
-
1. **Commituj zgodnie z konwencją** - husky sprawdzi format commita
|
|
534
|
-
2. **Merge do master** - GitLab CI automatycznie:
|
|
535
|
-
- Uruchomi testy i linting
|
|
536
|
-
- Zbuduje projekt
|
|
537
|
-
- Wygeneruje nową wersję (semantic-release)
|
|
538
|
-
- Zaktualizuje CHANGELOG.md
|
|
539
|
-
- Opublikuje do npm
|
|
540
|
-
- Utworzy GitLab release
|
|
541
|
-
|
|
542
|
-
### Branch model
|
|
543
|
-
|
|
544
|
-
- `master` - branch produkcyjny (→ release do npm)
|
|
545
|
-
- `beta` - branch pre-release (→ beta release do npm)
|
|
546
|
-
- feature branches - tworzenie nowych funkcjonalności
|
|
547
|
-
|
|
548
|
-
### CI/CD Pipeline
|
|
549
|
-
|
|
550
|
-
GitLab CI wykonuje następujące etapy:
|
|
551
|
-
|
|
552
|
-
**Quality Stage:**
|
|
553
|
-
- `lint` - sprawdzenie jakości kodu (ESLint + Prettier)
|
|
554
|
-
- `test` - testy jednostkowe i coverage
|
|
555
|
-
|
|
556
|
-
**Build Stage:**
|
|
557
|
-
- `build` - kompilacja TypeScript do JavaScript
|
|
558
|
-
|
|
559
|
-
**Release Stage:**
|
|
560
|
-
- `release:production` - automatyczny release z master
|
|
561
|
-
- `release:beta` - automatyczny release z beta
|
|
562
|
-
- `release:dry-run` - podgląd zmian (manual trigger)
|
|
563
|
-
|
|
564
|
-
### Zmienne środowiskowe w GitLab
|
|
565
|
-
|
|
566
|
-
Aby pipeline działał, skonfiguruj w GitLab CI/CD Settings → Variables:
|
|
567
|
-
|
|
568
|
-
- `NPM_TOKEN` - token do publikacji w npm (z scope publish)
|
|
569
|
-
- `GITLAB_TOKEN` - token do tworzenia release'ów w GitLab
|
|
570
|
-
|
|
571
|
-
### Testowanie release lokalnie
|
|
572
|
-
|
|
573
|
-
```bash
|
|
574
|
-
# Podgląd co zostanie wypuszczone
|
|
575
|
-
npm run semantic-release:dry-run
|
|
576
|
-
|
|
577
|
-
# Sprawdzenie ostatniego release
|
|
578
|
-
git tag --sort=-v:refname | head -n 1
|
|
579
|
-
```
|
|
580
|
-
|
|
581
1110
|
## Dokumentacja
|
|
582
1111
|
|
|
583
1112
|
### Architektura
|
|
@@ -587,12 +1116,21 @@ Szczegółowa dokumentacja architektury systemu, wzorców projektowych i zasad S
|
|
|
587
1116
|
|
|
588
1117
|
### Komponenty
|
|
589
1118
|
|
|
590
|
-
- **QueueManager** - zarządzanie kolejkami BullMQ i cache
|
|
591
|
-
- **WorkerOrchestrator** - orkiestracja workerów
|
|
1119
|
+
- **QueueManager** - zarządzanie kolejkami BullMQ i cache kolejek
|
|
1120
|
+
- **WorkerOrchestrator** - orkiestracja workerów, dynamiczne concurrency, benchmark
|
|
592
1121
|
- **JobProcessor** - przetwarzanie jobów i wykonanie handlerów
|
|
593
|
-
- **RpcCoordinator** - zarządzanie wywołaniami RPC
|
|
1122
|
+
- **RpcCoordinator** - zarządzanie wywołaniami RPC przez Redis Pub/Sub
|
|
594
1123
|
- **JobOptionsBuilder** - konfiguracja opcji dla jobów BullMQ
|
|
595
1124
|
- **CommandLogger** - persystencja komend do plików JSONL
|
|
1125
|
+
- **PayloadCompressionService** - automatyczna kompresja gzip
|
|
1126
|
+
- **AutoConfigOptimizer** - optymalizacja concurrency na podstawie zasobów
|
|
1127
|
+
|
|
1128
|
+
## Licencja
|
|
1129
|
+
|
|
1130
|
+
MIT
|
|
1131
|
+
|
|
1132
|
+
## Autorzy
|
|
1133
|
+
- Mariusz Lejkowski <m.lejkowski@polskiepolisy.pl>
|
|
596
1134
|
|
|
597
1135
|
## Linki
|
|
598
1136
|
|