pp-command-bus 1.4.0 → 2.0.0
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 +402 -1113
- package/dist/command-bus/command-bus.spec.js +144 -370
- package/dist/command-bus/command-bus.spec.js.map +1 -1
- package/dist/command-bus/command.d.ts +23 -5
- package/dist/command-bus/command.js +20 -34
- package/dist/command-bus/command.js.map +1 -1
- package/dist/command-bus/config/command-bus-config.d.ts +75 -21
- package/dist/command-bus/config/command-bus-config.js +99 -58
- package/dist/command-bus/config/command-bus-config.js.map +1 -1
- package/dist/command-bus/config/command-bus-config.spec.js +174 -100
- package/dist/command-bus/config/command-bus-config.spec.js.map +1 -1
- package/dist/command-bus/index.d.ts +39 -52
- package/dist/command-bus/index.js +133 -126
- package/dist/command-bus/index.js.map +1 -1
- package/dist/command-bus/logging/command-logger.d.ts +2 -0
- package/dist/command-bus/logging/command-logger.js +7 -0
- package/dist/command-bus/logging/command-logger.js.map +1 -1
- package/dist/command-bus/logging/command-logger.spec.js +49 -14
- package/dist/command-bus/logging/command-logger.spec.js.map +1 -1
- package/dist/command-bus/serialization/index.d.ts +6 -0
- package/dist/command-bus/serialization/index.js +9 -0
- package/dist/command-bus/serialization/index.js.map +1 -0
- package/dist/command-bus/serialization/msgpack-serializer.d.ts +26 -0
- package/dist/command-bus/serialization/msgpack-serializer.js +70 -0
- package/dist/command-bus/serialization/msgpack-serializer.js.map +1 -0
- package/dist/command-bus/serialization/msgpack-serializer.spec.js +223 -0
- package/dist/command-bus/serialization/msgpack-serializer.spec.js.map +1 -0
- package/dist/command-bus/serialization/serializer.interface.d.ts +21 -0
- package/dist/command-bus/serialization/serializer.interface.js +3 -0
- package/dist/command-bus/serialization/serializer.interface.js.map +1 -0
- package/dist/command-bus/transport/consumer-loop.d.ts +45 -0
- package/dist/command-bus/transport/consumer-loop.js +90 -0
- package/dist/command-bus/transport/consumer-loop.js.map +1 -0
- package/dist/command-bus/transport/consumer-loop.spec.js +216 -0
- package/dist/command-bus/transport/consumer-loop.spec.js.map +1 -0
- package/dist/command-bus/transport/index.d.ts +21 -0
- package/dist/command-bus/transport/index.js +23 -0
- package/dist/command-bus/transport/index.js.map +1 -0
- package/dist/command-bus/transport/message-processor.d.ts +59 -0
- package/dist/command-bus/transport/message-processor.js +111 -0
- package/dist/command-bus/transport/message-processor.js.map +1 -0
- package/dist/command-bus/transport/message-processor.spec.js +185 -0
- package/dist/command-bus/transport/message-processor.spec.js.map +1 -0
- package/dist/command-bus/transport/pending-recovery.d.ts +54 -0
- package/dist/command-bus/transport/pending-recovery.js +139 -0
- package/dist/command-bus/transport/pending-recovery.js.map +1 -0
- package/dist/command-bus/transport/pending-recovery.spec.js +176 -0
- package/dist/command-bus/transport/pending-recovery.spec.js.map +1 -0
- package/dist/command-bus/transport/redis-codec.d.ts +24 -0
- package/dist/command-bus/transport/redis-codec.js +33 -0
- package/dist/command-bus/transport/redis-codec.js.map +1 -0
- package/dist/command-bus/transport/redis-codec.spec.js +53 -0
- package/dist/command-bus/transport/redis-codec.spec.js.map +1 -0
- package/dist/command-bus/transport/redis-streams-transport.d.ts +91 -0
- package/dist/command-bus/transport/redis-streams-transport.js +134 -0
- package/dist/command-bus/transport/redis-streams-transport.js.map +1 -0
- package/dist/command-bus/transport/redis-streams-transport.spec.js +420 -0
- package/dist/command-bus/transport/redis-streams-transport.spec.js.map +1 -0
- package/dist/command-bus/transport/rpc-handler.d.ts +39 -0
- package/dist/command-bus/transport/rpc-handler.js +87 -0
- package/dist/command-bus/transport/rpc-handler.js.map +1 -0
- package/dist/command-bus/transport/rpc-handler.spec.js +157 -0
- package/dist/command-bus/transport/rpc-handler.spec.js.map +1 -0
- package/dist/command-bus/transport/stream-consumer.d.ts +89 -0
- package/dist/command-bus/transport/stream-consumer.js +181 -0
- package/dist/command-bus/transport/stream-consumer.js.map +1 -0
- package/dist/command-bus/transport/stream-consumer.spec.js +284 -0
- package/dist/command-bus/transport/stream-consumer.spec.js.map +1 -0
- package/dist/command-bus/transport/stream-producer.d.ts +23 -0
- package/dist/command-bus/transport/stream-producer.js +70 -0
- package/dist/command-bus/transport/stream-producer.js.map +1 -0
- package/dist/command-bus/transport/stream-producer.spec.js +125 -0
- package/dist/command-bus/transport/stream-producer.spec.js.map +1 -0
- package/dist/command-bus/transport/transport.interface.d.ts +87 -0
- package/dist/command-bus/transport/transport.interface.js +3 -0
- package/dist/command-bus/transport/transport.interface.js.map +1 -0
- package/dist/command-bus/types/index.d.ts +9 -80
- package/dist/examples/rpc-throughput.demo.js +24 -22
- package/dist/examples/rpc-throughput.demo.js.map +1 -1
- package/dist/examples/rpc.demo.js +47 -53
- package/dist/examples/rpc.demo.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/pp-command-bus-2.0.0.tgz +0 -0
- package/dist/shared/redis/connection-pool.d.ts +54 -0
- package/dist/shared/redis/connection-pool.js +117 -0
- package/dist/shared/redis/connection-pool.js.map +1 -0
- package/dist/shared/redis/connection-pool.spec.js +114 -0
- package/dist/shared/redis/connection-pool.spec.js.map +1 -0
- package/dist/shared/redis/index.d.ts +5 -3
- package/dist/shared/redis/index.js +6 -4
- package/dist/shared/redis/index.js.map +1 -1
- package/dist/shared/redis/rpc-connection-pool.d.ts +61 -0
- package/dist/shared/redis/rpc-connection-pool.js +154 -0
- package/dist/shared/redis/rpc-connection-pool.js.map +1 -0
- package/dist/shared/redis/rpc-connection-pool.spec.d.ts +1 -0
- package/dist/shared/redis/rpc-connection-pool.spec.js +173 -0
- package/dist/shared/redis/rpc-connection-pool.spec.js.map +1 -0
- package/dist/shared/types.d.ts +0 -4
- package/dist/shared/utils/error-utils.d.ts +8 -0
- package/dist/shared/utils/error-utils.js +14 -0
- package/dist/shared/utils/error-utils.js.map +1 -0
- package/package.json +12 -12
- package/dist/command-bus/config/auto-config-optimizer.d.ts +0 -35
- package/dist/command-bus/config/auto-config-optimizer.js +0 -52
- package/dist/command-bus/config/auto-config-optimizer.js.map +0 -1
- package/dist/command-bus/config/auto-config-optimizer.spec.js +0 -42
- package/dist/command-bus/config/auto-config-optimizer.spec.js.map +0 -1
- package/dist/command-bus/job/index.d.ts +0 -6
- package/dist/command-bus/job/index.js +0 -15
- package/dist/command-bus/job/index.js.map +0 -1
- package/dist/command-bus/job/job-options-builder.d.ts +0 -21
- package/dist/command-bus/job/job-options-builder.js +0 -58
- package/dist/command-bus/job/job-options-builder.js.map +0 -1
- package/dist/command-bus/job/job-options-builder.spec.js +0 -156
- package/dist/command-bus/job/job-options-builder.spec.js.map +0 -1
- package/dist/command-bus/job/job-processor.d.ts +0 -39
- package/dist/command-bus/job/job-processor.js +0 -203
- package/dist/command-bus/job/job-processor.js.map +0 -1
- package/dist/command-bus/job/job-processor.spec.js +0 -437
- package/dist/command-bus/job/job-processor.spec.js.map +0 -1
- package/dist/command-bus/queue/index.d.ts +0 -5
- package/dist/command-bus/queue/index.js +0 -13
- package/dist/command-bus/queue/index.js.map +0 -1
- package/dist/command-bus/queue/queue-manager.d.ts +0 -56
- package/dist/command-bus/queue/queue-manager.js +0 -163
- package/dist/command-bus/queue/queue-manager.js.map +0 -1
- package/dist/command-bus/queue/queue-manager.spec.js +0 -371
- package/dist/command-bus/queue/queue-manager.spec.js.map +0 -1
- package/dist/command-bus/rpc/index.d.ts +0 -11
- package/dist/command-bus/rpc/index.js +0 -19
- package/dist/command-bus/rpc/index.js.map +0 -1
- package/dist/command-bus/rpc/payload-compression.service.d.ts +0 -51
- package/dist/command-bus/rpc/payload-compression.service.js +0 -218
- package/dist/command-bus/rpc/payload-compression.service.js.map +0 -1
- package/dist/command-bus/rpc/payload-compression.service.spec.js +0 -379
- package/dist/command-bus/rpc/payload-compression.service.spec.js.map +0 -1
- package/dist/command-bus/rpc/rpc-coordinator.d.ts +0 -96
- package/dist/command-bus/rpc/rpc-coordinator.js +0 -500
- package/dist/command-bus/rpc/rpc-coordinator.js.map +0 -1
- package/dist/command-bus/rpc/rpc-coordinator.spec.js +0 -622
- package/dist/command-bus/rpc/rpc-coordinator.spec.js.map +0 -1
- package/dist/command-bus/rpc/rpc-job-cancellation.service.d.ts +0 -82
- package/dist/command-bus/rpc/rpc-job-cancellation.service.js +0 -180
- package/dist/command-bus/rpc/rpc-job-cancellation.service.js.map +0 -1
- package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js +0 -286
- package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js.map +0 -1
- package/dist/command-bus/worker/index.d.ts +0 -10
- package/dist/command-bus/worker/index.js +0 -19
- package/dist/command-bus/worker/index.js.map +0 -1
- package/dist/command-bus/worker/worker-benchmark.d.ts +0 -71
- package/dist/command-bus/worker/worker-benchmark.js +0 -203
- package/dist/command-bus/worker/worker-benchmark.js.map +0 -1
- package/dist/command-bus/worker/worker-benchmark.spec.js +0 -310
- package/dist/command-bus/worker/worker-benchmark.spec.js.map +0 -1
- package/dist/command-bus/worker/worker-metrics-collector.d.ts +0 -98
- package/dist/command-bus/worker/worker-metrics-collector.js +0 -242
- package/dist/command-bus/worker/worker-metrics-collector.js.map +0 -1
- package/dist/command-bus/worker/worker-orchestrator.d.ts +0 -70
- package/dist/command-bus/worker/worker-orchestrator.js +0 -339
- package/dist/command-bus/worker/worker-orchestrator.js.map +0 -1
- package/dist/command-bus/worker/worker-orchestrator.spec.js +0 -712
- package/dist/command-bus/worker/worker-orchestrator.spec.js.map +0 -1
- package/dist/examples/auto-config.demo.d.ts +0 -9
- package/dist/examples/auto-config.demo.js +0 -106
- package/dist/examples/auto-config.demo.js.map +0 -1
- package/dist/examples/rpc-compression.demo.d.ts +0 -5
- package/dist/examples/rpc-compression.demo.js +0 -363
- package/dist/examples/rpc-compression.demo.js.map +0 -1
- package/dist/examples/rpc-resilience.demo.d.ts +0 -11
- package/dist/examples/rpc-resilience.demo.js +0 -235
- package/dist/examples/rpc-resilience.demo.js.map +0 -1
- package/dist/pp-command-bus-1.4.0.tgz +0 -0
- package/dist/shared/config/base-config.d.ts +0 -54
- package/dist/shared/config/base-config.js +0 -114
- package/dist/shared/config/base-config.js.map +0 -1
- package/dist/shared/config/base-config.spec.js +0 -204
- package/dist/shared/config/base-config.spec.js.map +0 -1
- package/dist/shared/config/index.d.ts +0 -1
- package/dist/shared/config/index.js +0 -9
- package/dist/shared/config/index.js.map +0 -1
- package/dist/shared/redis/redis-connection-factory.d.ts +0 -66
- package/dist/shared/redis/redis-connection-factory.js +0 -113
- package/dist/shared/redis/redis-connection-factory.js.map +0 -1
- /package/dist/command-bus/{config/auto-config-optimizer.spec.d.ts → serialization/msgpack-serializer.spec.d.ts} +0 -0
- /package/dist/command-bus/{job/job-options-builder.spec.d.ts → transport/consumer-loop.spec.d.ts} +0 -0
- /package/dist/command-bus/{job/job-processor.spec.d.ts → transport/message-processor.spec.d.ts} +0 -0
- /package/dist/command-bus/{queue/queue-manager.spec.d.ts → transport/pending-recovery.spec.d.ts} +0 -0
- /package/dist/command-bus/{rpc/payload-compression.service.spec.d.ts → transport/redis-codec.spec.d.ts} +0 -0
- /package/dist/command-bus/{rpc/rpc-coordinator.spec.d.ts → transport/redis-streams-transport.spec.d.ts} +0 -0
- /package/dist/command-bus/{rpc/rpc-job-cancellation.service.spec.d.ts → transport/rpc-handler.spec.d.ts} +0 -0
- /package/dist/command-bus/{worker/worker-benchmark.spec.d.ts → transport/stream-consumer.spec.d.ts} +0 -0
- /package/dist/command-bus/{worker/worker-orchestrator.spec.d.ts → transport/stream-producer.spec.d.ts} +0 -0
- /package/dist/shared/{config/base-config.spec.d.ts → redis/connection-pool.spec.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,239 +1,103 @@
|
|
|
1
1
|
# PP Command Bus
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Rozproszona biblioteka Command Bus oparta na **Redis Streams** z serializacją **MessagePack** i RPC przez **LPUSH/BRPOP**. Zoptymalizowana pod **DragonflyDB**.
|
|
4
|
+
|
|
5
|
+
## Spis treści
|
|
6
|
+
|
|
7
|
+
- [Opis](#opis)
|
|
8
|
+
- [Architektura](#architektura)
|
|
9
|
+
- [Instalacja](#instalacja)
|
|
10
|
+
- [Szybki start](#szybki-start)
|
|
11
|
+
- [API](#api)
|
|
12
|
+
- [Konfiguracja](#konfiguracja)
|
|
13
|
+
- [Flow danych](#flow-danych)
|
|
14
|
+
- [Komponenty](#komponenty)
|
|
15
|
+
- [Struktura projektu](#struktura-projektu)
|
|
16
|
+
- [Testowanie](#testowanie)
|
|
17
|
+
- [Changelog](#changelog)
|
|
4
18
|
|
|
5
19
|
## Opis
|
|
6
20
|
|
|
7
|
-
**pp-command-bus** to
|
|
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
|
-
|
|
21
|
+
**pp-command-bus** to biblioteka do obsługi rozproszonych komend zgodna ze wzorcem **CQRS**. Zapewnia:
|
|
22
|
+
|
|
23
|
+
- **Fire-and-forget dispatch** — komendy wysyłane przez `XADD` do Redis Streams
|
|
24
|
+
- **Batch dispatch** — wiele komend w jednym pipeline Redis
|
|
25
|
+
- **RPC (request/response)** — synchroniczne wywołania z timeout przez `LPUSH/BRPOP`
|
|
26
|
+
- **Consumer Groups** — dokładnie jedno przetworzenie komendy (exactly-once delivery)
|
|
27
|
+
- **Dead Letter Queue** — wiadomości po przekroczeniu prób trafiają do `dlq:{stream}`
|
|
28
|
+
- **Auto-recovery** — automatyczne przejmowanie stalled wiadomości przez `XPENDING/XCLAIM`
|
|
29
|
+
- **MessagePack** — binarna serializacja z natywną obsługą `Date` (extension type)
|
|
30
|
+
|
|
31
|
+
## Architektura
|
|
32
|
+
|
|
33
|
+
### Diagram komponentów
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
37
|
+
│ CommandBus │
|
|
38
|
+
│ dispatch() | dispatchBatch() | call() | handle() | close() │
|
|
39
|
+
└────────────────────────────┬────────────────────────────────┘
|
|
40
|
+
│
|
|
41
|
+
┌────────┴────────┐
|
|
42
|
+
│ ITransport │
|
|
43
|
+
└────────┬────────┘
|
|
44
|
+
│
|
|
45
|
+
┌────────────────────────────┴────────────────────────────────┐
|
|
46
|
+
│ RedisStreamsTransport │
|
|
47
|
+
│ (fasada / kompozycja) │
|
|
48
|
+
├─────────────┬──────────────┬──────────────┬─────────────────┤
|
|
49
|
+
│ │ │ │ │
|
|
50
|
+
│ ┌───────────┴─┐ ┌─────────┴──────┐ ┌────┴─────┐ ┌─────────┴──┐
|
|
51
|
+
│ │ Stream │ │ Stream │ │ Rpc │ │ Pending │
|
|
52
|
+
│ │ Producer │ │ Consumer │ │ Handler │ │ Recovery │
|
|
53
|
+
│ │ (XADD) │ │ (koordynator) │ │ (BRPOP) │ │ (XCLAIM) │
|
|
54
|
+
│ └─────────────┘ └───────┬────────┘ └──────────┘ └────────────┘
|
|
55
|
+
│ │ │
|
|
56
|
+
│ ┌──────────┴──────────┐ │
|
|
57
|
+
│ │ │ │
|
|
58
|
+
│ ┌────────┴─────────┐ ┌────────┴────────┐ │
|
|
59
|
+
│ │ Message │ │ Consumer │ │
|
|
60
|
+
│ │ Processor │ │ Loop │ │
|
|
61
|
+
│ │ (parse/XACK/RPC) │ │ (XREADGROUP) │ │
|
|
62
|
+
│ └──────────────────┘ └─────────────────┘ │
|
|
63
|
+
├─────────────────────────────────────────────────────────────┤
|
|
64
|
+
│ Warstwa połączeń │
|
|
65
|
+
│ ┌─────────────────────┐ ┌──────────────────────┐ │
|
|
66
|
+
│ │ RedisConnectionPool │ │ RpcConnectionPool │ │
|
|
67
|
+
│ │ (round-robin, eager)│ │ (bounded, lazy, BRPOP)│ │
|
|
68
|
+
│ └─────────────────────┘ └──────────────────────┘ │
|
|
69
|
+
├─────────────────────────────────────────────────────────────┤
|
|
70
|
+
│ Warstwa serializacji │
|
|
71
|
+
│ ┌─────────────────────┐ ┌──────────────────────┐ │
|
|
72
|
+
│ │ MsgpackSerializer │ │ RedisCodec │ │
|
|
73
|
+
│ │ (Date extension) │ │ (base64 encode) │ │
|
|
74
|
+
│ └─────────────────────┘ └──────────────────────┘ │
|
|
75
|
+
└─────────────────────────────────────────────────────────────┘
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Segregacja interfejsów (ISP)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
interface IStreamProducer {
|
|
82
|
+
enqueue(streamName: string, data: Buffer): Promise<string>;
|
|
83
|
+
enqueueBatch(entries: Array<{ streamName: string; data: Buffer }>): Promise<string[]>;
|
|
84
|
+
}
|
|
53
85
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- **Odpowiedzialność**: Główny punkt wejścia dla użytkownika, orkiestracja wszystkich komponentów
|
|
58
|
-
- **Metody publiczne**:
|
|
59
|
-
- `dispatch(command)` - wysyłanie fire-and-forget
|
|
60
|
-
- `call(command, timeout)` - synchroniczne RPC
|
|
61
|
-
- `handle(commandClass, handler)` - rejestracja handlerów
|
|
62
|
-
- `close()` - graceful shutdown
|
|
63
|
-
- **Zarządzanie połączeniami**: 3 dedykowane połączenia Redis (Queue, Worker, RPC)
|
|
64
|
-
|
|
65
|
-
#### 2. **RpcCoordinator** (zarządzanie RPC)
|
|
66
|
-
- **Odpowiedzialność**: Zarządzanie cyklem życia wywołań RPC przez Redis Pub/Sub
|
|
67
|
-
- **Kluczowe funkcje**:
|
|
68
|
-
- **Shared Subscriber** - jeden subscriber dla wszystkich RPC calls (pattern matching)
|
|
69
|
-
- **Process Isolation** - UUID procesu Node.js dla izolacji odpowiedzi między procesami
|
|
70
|
-
- **Timeout Management** - automatyczne cleanup po timeout
|
|
71
|
-
- **Job Cancellation** - usuwanie niepodjętych jobów przy timeout (optymalizacja zasobów)
|
|
72
|
-
- **Multiplexing** - routing odpowiedzi do odpowiednich promises
|
|
73
|
-
- **Kanały**: `rpc:response:{processId}:{correlationId}`
|
|
74
|
-
|
|
75
|
-
#### 3. **WorkerOrchestrator** (orkiestracja workerów)
|
|
76
|
-
- **Odpowiedzialność**: Zarządzanie workerami BullMQ i dynamiczna optymalizacja
|
|
77
|
-
- **Kluczowe funkcje**:
|
|
78
|
-
- **WorkerBenchmark** - automatyczny benchmark przy rejestracji handlera
|
|
79
|
-
- **WorkerMetricsCollector** - event-driven metrics collection
|
|
80
|
-
- **Dynamic Concurrency** - dostosowywanie concurrency +/-20% co 30s
|
|
81
|
-
- **Event Handlers** - obsługa zdarzeń: active, completed, failed, stalled
|
|
82
|
-
- **Limity concurrency**: min 10, max 2000
|
|
83
|
-
|
|
84
|
-
#### 4. **JobProcessor** (wykonywanie handlerów)
|
|
85
|
-
- **Odpowiedzialność**: Wykonywanie handlerów komend i obsługa odpowiedzi RPC
|
|
86
|
-
- **Flow przetwarzania**:
|
|
87
|
-
1. Dekompresja payloadu (jeśli skompresowany)
|
|
88
|
-
2. Sprawdzenie flagi cancellation (pomijanie anulowanych RPC)
|
|
89
|
-
3. Rekonstrukcja obiektów Date
|
|
90
|
-
4. Opcjonalne logowanie komendy
|
|
91
|
-
5. Wykonanie handlera
|
|
92
|
-
6. Wysłanie odpowiedzi RPC przez Pub/Sub (jeśli RPC)
|
|
93
|
-
- **Kompresja odpowiedzi**: automatyczna kompresja przez PayloadCompressionService
|
|
94
|
-
- **RPC Cancellation**: pomijanie jobów oznaczonych jako anulowane (timeout)
|
|
95
|
-
|
|
96
|
-
#### 5. **QueueManager** (zarządzanie kolejkami)
|
|
97
|
-
- **Odpowiedzialność**: Cache kolejek BullMQ dla optymalizacji pamięci
|
|
98
|
-
- **Funkcje**:
|
|
99
|
-
- `getOrCreateQueue(commandName)` - lazy loading kolejek
|
|
100
|
-
- `closeAllQueues()` - graceful shutdown wszystkich kolejek
|
|
101
|
-
- **Naming**: `{CommandName}` jako nazwa kolejki
|
|
102
|
-
|
|
103
|
-
#### 6. **PayloadCompressionService** (kompresja)
|
|
104
|
-
- **Odpowiedzialność**: Automatyczna kompresja/dekompresja payloadów gzip
|
|
105
|
-
- **Threshold**: 1024 bajty (1KB) domyślnie, konfigurowalne przez ENV
|
|
106
|
-
- **Metody**:
|
|
107
|
-
- `compressCommand(command)` - dodaje flagę `__compressed`
|
|
108
|
-
- `decompressCommand(command)` - dekompresja i usunięcie flagi
|
|
109
|
-
- `compress(data)` - generyczna kompresja do base64
|
|
110
|
-
- `decompress(data, compressed)` - generyczna dekompresja
|
|
111
|
-
- **Współdzielony serwis**: jedna instancja dla całego CommandBus
|
|
112
|
-
|
|
113
|
-
#### 7. **RpcJobCancellationService** (anulowanie jobów RPC)
|
|
114
|
-
- **Odpowiedzialność**: Zarządzanie anulowaniem niepodjętych jobów RPC przy timeout
|
|
115
|
-
- **Kluczowe funkcje**:
|
|
116
|
-
- **markAsCancelled(correlationId)** - oznacza job jako anulowany w Redis
|
|
117
|
-
- **isCancelled(correlationId)** - sprawdza flagę cancellation
|
|
118
|
-
- **clearCancellation(correlationId)** - usuwa flagę po przetworzeniu/usunięciu
|
|
119
|
-
- **tryRemoveJob(jobId, queueName, callback)** - próba usunięcia joba z kolejki
|
|
120
|
-
- **TTL kluczy Redis**: 24 godziny (automatyczne wygaśnięcie)
|
|
121
|
-
- **Graceful degradation**: błędy Redis nie blokują przetwarzania (zwraca false)
|
|
122
|
-
- **Klucze Redis**: `rpc:cancelled:{correlationId}`
|
|
123
|
-
|
|
124
|
-
#### 8. **CommandLogger** (opcjonalne logowanie)
|
|
125
|
-
- **Odpowiedzialność**: Persystencja komend do plików JSONL
|
|
126
|
-
- **Format**: `{timestamp}.jsonl` - rotacja co godzinę
|
|
127
|
-
- **Zawartość**: pełny payload komendy z metadanymi
|
|
128
|
-
- **Aktywacja**: przez ENV `COMMAND_BUS_LOG=./command-logs`
|
|
129
|
-
|
|
130
|
-
#### 9. **AutoConfigOptimizer** (auto-optymalizacja)
|
|
131
|
-
- **Odpowiedzialność**: Obliczanie optymalnego concurrency na podstawie zasobów systemowych
|
|
132
|
-
- **Heurystyka**:
|
|
133
|
-
- I/O-heavy workload (Redis/BullMQ)
|
|
134
|
-
- `concurrency = CPU cores * 2 + (availableMemory / 512MB)`
|
|
135
|
-
- Zakładane 512MB RAM per worker
|
|
136
|
-
- **Aktywacja**: domyślnie włączone (ENV `COMMAND_BUS_AUTO_OPTIMIZE=false` wyłącza)
|
|
137
|
-
|
|
138
|
-
#### 10. **RedisConnectionFactory** (fabryka połączeń Redis)
|
|
139
|
-
- **Odpowiedzialność**: Tworzenie połączeń Redis z wbudowaną obsługą błędów i eventów
|
|
140
|
-
- **Zgodność z SRP**: CommandBus nie zarządza bezpośrednio połączeniami
|
|
141
|
-
- **Kluczowe funkcje**:
|
|
142
|
-
- `create(options, name)` - tworzy połączenie z pełną obsługą eventów
|
|
143
|
-
- `createForWorker(options, name)` - dodaje `maxRetriesPerRequest: null` dla BullMQ
|
|
144
|
-
- Obsługa eventów: `error`, `close`, `reconnecting`, `connect`, `ready`
|
|
145
|
-
- Formatowanie błędów `AggregateError` (Node.js dual-stack IPv4/IPv6)
|
|
146
|
-
- **Event Handlers**:
|
|
147
|
-
- `on('error')` - logowanie błędów połączenia (zapobiega `Unhandled error event`)
|
|
148
|
-
- `on('close')` - informacja o zamknięciu połączenia
|
|
149
|
-
- `on('reconnecting')` - informacja o ponownym łączeniu
|
|
150
|
-
- `on('connect')` / `on('ready')` - potwierdzenie nawiązania połączenia
|
|
151
|
-
|
|
152
|
-
### Flow Przepływu Danych
|
|
153
|
-
|
|
154
|
-
#### Flow 1: dispatch() - Fire-and-forget
|
|
86
|
+
interface IStreamConsumer {
|
|
87
|
+
consume(streamName: string, groupName: string, handler: ConsumerHandler): Promise<void>;
|
|
88
|
+
}
|
|
155
89
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
│
|
|
161
|
-
├─→ 2. PayloadCompressionService.compressCommand(command)
|
|
162
|
-
│ └─→ Jeśli payload >1KB → gzip → base64 → __compressed: true
|
|
163
|
-
│
|
|
164
|
-
├─→ 3. QueueManager.getOrCreateQueue(commandName)
|
|
165
|
-
│ └─→ Cache hit/miss → zwraca Queue
|
|
166
|
-
│
|
|
167
|
-
├─→ 4. queue.add(commandName, compressedCommand, options)
|
|
168
|
-
│ └─→ BullMQ dodaje job do Redis
|
|
169
|
-
│
|
|
170
|
-
└─→ 5. Promise<void> resolved (nie czekamy na wynik)
|
|
171
|
-
|
|
172
|
-
Worker Side (asynchronicznie)
|
|
173
|
-
│
|
|
174
|
-
├─→ 6. Worker pobiera job z kolejki
|
|
175
|
-
│
|
|
176
|
-
├─→ 7. JobProcessor.process(job)
|
|
177
|
-
│ ├─→ Dekompresja (jeśli __compressed)
|
|
178
|
-
│ ├─→ Rekonstrukcja Date
|
|
179
|
-
│ ├─→ Opcjonalne logowanie (CommandLogger)
|
|
180
|
-
│ └─→ Wykonanie handlera
|
|
181
|
-
│
|
|
182
|
-
└─→ 8. Worker kończy job (success/fail)
|
|
183
|
-
```
|
|
90
|
+
interface IRpcTransport {
|
|
91
|
+
rpcCall(commandName: string, data: Buffer, timeout: number): Promise<Buffer>;
|
|
92
|
+
rpcRespond(responseKey: string, data: Buffer, ttl: number): Promise<void>;
|
|
93
|
+
}
|
|
184
94
|
|
|
185
|
-
|
|
95
|
+
interface IClosable {
|
|
96
|
+
close(): Promise<void>;
|
|
97
|
+
}
|
|
186
98
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
│
|
|
190
|
-
├─→ 1. commandBus.call(command, timeout)
|
|
191
|
-
│
|
|
192
|
-
├─→ 2. RpcCoordinator.registerCall(correlationId, commandName, timeout)
|
|
193
|
-
│ ├─→ Oczekiwanie na gotowość shared subscriber (5s timeout)
|
|
194
|
-
│ ├─→ Utworzenie Promise<T> dla odpowiedzi
|
|
195
|
-
│ ├─→ Zapisanie pending call w Map
|
|
196
|
-
│ └─→ Zwrócenie responsePromise (bez blokowania)
|
|
197
|
-
│
|
|
198
|
-
├─→ 3. PayloadCompressionService.compressCommand(command)
|
|
199
|
-
│ └─→ Jeśli payload >1KB → gzip → __compressed: true
|
|
200
|
-
│
|
|
201
|
-
├─→ 4. RpcCoordinator.prepareRpcCommand(compressedCommand)
|
|
202
|
-
│ └─→ Dodaje __rpcMetadata: { correlationId, responseChannel, timestamp }
|
|
203
|
-
│
|
|
204
|
-
├─→ 5. QueueManager.getOrCreateQueue(commandName)
|
|
205
|
-
│ └─→ Cache hit/miss → zwraca Queue
|
|
206
|
-
│
|
|
207
|
-
├─→ 6. queue.add(commandName, commandWithMetadata, options)
|
|
208
|
-
│ └─→ BullMQ dodaje job do Redis
|
|
209
|
-
│
|
|
210
|
-
└─→ 7. await responsePromise (czeka na odpowiedź z Worker)
|
|
211
|
-
|
|
212
|
-
Worker Side
|
|
213
|
-
│
|
|
214
|
-
├─→ 8. Worker pobiera job z kolejki
|
|
215
|
-
│
|
|
216
|
-
├─→ 9. JobProcessor.process(job)
|
|
217
|
-
│ ├─→ Dekompresja payloadu (jeśli __compressed)
|
|
218
|
-
│ ├─→ Rekonstrukcja Date
|
|
219
|
-
│ ├─→ Wykonanie handlera → result
|
|
220
|
-
│ │
|
|
221
|
-
│ └─→ 10. JobProcessor.sendRpcResponse(rpcMetadata, result, null)
|
|
222
|
-
│ ├─→ PayloadCompressionService.compress({ correlationId, result, error })
|
|
223
|
-
│ ├─→ Wrapper: { data, compressed }
|
|
224
|
-
│ └─→ redis.publish(responseChannel, JSON.stringify(wrapper))
|
|
225
|
-
│
|
|
226
|
-
Shared Subscriber (RpcCoordinator)
|
|
227
|
-
│
|
|
228
|
-
├─→ 11. pmessage event → handleRpcMessage(channel, message)
|
|
229
|
-
│ ├─→ Ekstraktuj correlationId z channel
|
|
230
|
-
│ ├─→ Weryfikuj processInstanceId (process isolation)
|
|
231
|
-
│ ├─→ Znajdź pending call w Map
|
|
232
|
-
│ ├─→ PayloadCompressionService.decompress(wrapper.data, wrapper.compressed)
|
|
233
|
-
│ ├─→ resolve(result) lub reject(error)
|
|
234
|
-
│ └─→ Cleanup: clearTimeout, delete z Map
|
|
235
|
-
│
|
|
236
|
-
└─→ 12. User Code otrzymuje wynik → Promise<T> resolved
|
|
99
|
+
// Pełny interfejs transportu — kompozycja segregowanych interfejsów
|
|
100
|
+
interface ITransport extends IStreamProducer, IStreamConsumer, IRpcTransport, IClosable {}
|
|
237
101
|
```
|
|
238
102
|
|
|
239
103
|
## Instalacja
|
|
@@ -244,979 +108,404 @@ npm install pp-command-bus
|
|
|
244
108
|
|
|
245
109
|
### Wymagania
|
|
246
110
|
|
|
247
|
-
- Node.js >=
|
|
248
|
-
- Redis >=
|
|
249
|
-
- TypeScript >=
|
|
250
|
-
|
|
251
|
-
## Szybki start
|
|
111
|
+
- **Node.js** >= 18
|
|
112
|
+
- **Redis** >= 6.2 lub **DragonflyDB** >= 1.0
|
|
113
|
+
- **TypeScript** >= 5.0 (opcjonalnie, pełne typowanie)
|
|
252
114
|
|
|
253
|
-
###
|
|
115
|
+
### Zależności
|
|
254
116
|
|
|
255
|
-
|
|
256
|
-
|
|
117
|
+
| Pakiet | Opis |
|
|
118
|
+
|--------|------|
|
|
119
|
+
| `ioredis` | Klient Redis z pipelining, Cluster, Sentinel |
|
|
120
|
+
| `@msgpack/msgpack` | Binarna serializacja z extension types |
|
|
257
121
|
|
|
258
|
-
|
|
259
|
-
const config = new CommandBusConfig({
|
|
260
|
-
redisUrl: 'redis://localhost:6379',
|
|
261
|
-
logger: console, // ILogger interface
|
|
262
|
-
logLevel: 'log', // debug | log | warn | error
|
|
263
|
-
concurrency: 5,
|
|
264
|
-
maxAttempts: 1,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Utwórz instancję CommandBus
|
|
268
|
-
const commandBus = new CommandBus(config);
|
|
269
|
-
```
|
|
122
|
+
## Szybki start
|
|
270
123
|
|
|
271
|
-
###
|
|
124
|
+
### Definiowanie komendy
|
|
272
125
|
|
|
273
126
|
```typescript
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
super();
|
|
127
|
+
import { Command } from 'pp-command-bus';
|
|
128
|
+
|
|
129
|
+
class CreateUserCommand extends Command<{ name: string; email: string }> {
|
|
130
|
+
constructor(payload: { name: string; email: string }) {
|
|
131
|
+
super(payload);
|
|
280
132
|
}
|
|
281
133
|
}
|
|
282
134
|
```
|
|
283
135
|
|
|
284
|
-
###
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
commandBus.handle(CreateUserCommand, async (command) => {
|
|
288
|
-
console.log(`Creating user: ${command.name} (${command.email})`);
|
|
289
|
-
|
|
290
|
-
// Twoja logika biznesowa
|
|
291
|
-
const user = await createUser(command.email, command.name);
|
|
292
|
-
|
|
293
|
-
// Zwróć wynik (opcjonalnie)
|
|
294
|
-
return { userId: user.id };
|
|
295
|
-
});
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### 4. Wysyłanie komend (Fire-and-forget)
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
const command = new CreateUserCommand('jan.kowalski@example.com', 'Jan Kowalski');
|
|
302
|
-
|
|
303
|
-
// Wyślij komendę bez oczekiwania na wynik
|
|
304
|
-
await commandBus.dispatch(command);
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### 5. RPC - wywołania synchroniczne
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
// Wywołaj komendę i poczekaj na wynik
|
|
311
|
-
const result = await commandBus.call<{ userId: string }>(command, 5000); // timeout 5s
|
|
312
|
-
|
|
313
|
-
console.log(`User created with ID: ${result.userId}`);
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## Zmienne Środowiskowe
|
|
317
|
-
|
|
318
|
-
Biblioteka wspiera konfigurację poprzez zmienne środowiskowe z prefiksem `COMMAND_BUS_` (z fallbackiem do starszych nazw `EVENT_BUS_*`):
|
|
319
|
-
|
|
320
|
-
### Kompletna Lista Zmiennych
|
|
321
|
-
|
|
322
|
-
| Zmienna | Typ | Wartość Domyślna | Opis |
|
|
323
|
-
|---------|-----|------------------|------|
|
|
324
|
-
| `REDIS_URL` | string | `redis://localhost:6379` | URL połączenia Redis/DragonflyDB (wspiera username, password, db) |
|
|
325
|
-
| `REDIS_RETRY_DELAY` | number | `5000` | Opóźnienie między próbami reconnect do Redis w milisekundach |
|
|
326
|
-
| `REDIS_MAX_RETRIES` | number | `0` | Maksymalna liczba prób reconnect (0 = nieskończoność) |
|
|
327
|
-
| `LOG_LEVEL` | enum | `log` | Poziom logowania: `debug`, `log`, `warn`, `error` |
|
|
328
|
-
| `COMMAND_BUS_CONCURRENCY` | number | `1` (lub auto) | Liczba równoległych workerów do przetwarzania komend |
|
|
329
|
-
| `COMMAND_BUS_MAX_ATTEMPTS` | number | `1` | Maksymalna liczba prób przetworzenia zadania |
|
|
330
|
-
| `COMMAND_BUS_BACKOFF_DELAY` | number | `2000` | Opóźnienie między próbami w milisekundach |
|
|
331
|
-
| `COMMAND_BUS_QUEUE_MODE` | enum | `fifo` | Tryb przetwarzania kolejki: `fifo` (First In First Out) lub `lifo` (Last In First Out) |
|
|
332
|
-
| `COMMAND_BUS_LOG` | string | _(puste)_ | Ścieżka do katalogu logów komend (JSONL format, rotacja co godzinę) |
|
|
333
|
-
| `COMMAND_BUS_AUTO_OPTIMIZE` | boolean | `true` | Włącz auto-optymalizację concurrency na podstawie CPU/RAM |
|
|
334
|
-
| `COMMAND_BUS_COMPRESSION_THRESHOLD` | number | `1024` | Próg kompresji gzip dla payloadów RPC w bajtach (1KB domyślnie) |
|
|
335
|
-
|
|
336
|
-
### Fallback do Starszych Nazw
|
|
337
|
-
|
|
338
|
-
Dla kompatybilności wstecznej obsługiwane są również prefiksy `EVENT_BUS_*`:
|
|
339
|
-
|
|
340
|
-
- `EVENT_BUS_CONCURRENCY` → `COMMAND_BUS_CONCURRENCY`
|
|
341
|
-
- `EVENT_BUS_MAX_ATTEMPTS` → `COMMAND_BUS_MAX_ATTEMPTS`
|
|
342
|
-
- `EVENT_BUS_BACKOFF_DELAY` → `COMMAND_BUS_BACKOFF_DELAY`
|
|
343
|
-
- `EVENT_BUS_QUEUE_MODE` → `COMMAND_BUS_QUEUE_MODE`
|
|
344
|
-
- `EVENT_BUS_LOG` → `COMMAND_BUS_LOG`
|
|
345
|
-
|
|
346
|
-
### Przykład Konfiguracji .env
|
|
347
|
-
|
|
348
|
-
```bash
|
|
349
|
-
# Połączenie Redis
|
|
350
|
-
REDIS_URL=redis://username:password@localhost:6379/0
|
|
351
|
-
|
|
352
|
-
# Strategia reconnect Redis (stałe opóźnienie)
|
|
353
|
-
REDIS_RETRY_DELAY=5000 # 5 sekund między próbami (domyślnie)
|
|
354
|
-
REDIS_MAX_RETRIES=0 # 0 = nieskończoność (domyślnie)
|
|
355
|
-
|
|
356
|
-
# Poziom logowania
|
|
357
|
-
LOG_LEVEL=log # debug | log | warn | error
|
|
358
|
-
|
|
359
|
-
# Auto-optymalizacja (domyślnie włączona)
|
|
360
|
-
COMMAND_BUS_AUTO_OPTIMIZE=true
|
|
361
|
-
|
|
362
|
-
# Concurrency (opcjonalnie - auto-optymalizacja ustawi optymalną wartość)
|
|
363
|
-
# COMMAND_BUS_CONCURRENCY=10
|
|
364
|
-
|
|
365
|
-
# Retry i backoff
|
|
366
|
-
COMMAND_BUS_MAX_ATTEMPTS=1 # Maksymalna liczba prób
|
|
367
|
-
COMMAND_BUS_BACKOFF_DELAY=3000 # Opóźnienie między próbami (3s)
|
|
368
|
-
|
|
369
|
-
# Tryb kolejki
|
|
370
|
-
COMMAND_BUS_QUEUE_MODE=fifo # fifo lub lifo
|
|
371
|
-
|
|
372
|
-
# Kompresja payloadów (próg w bajtach)
|
|
373
|
-
COMMAND_BUS_COMPRESSION_THRESHOLD=2048 # 2KB (domyślnie 1KB)
|
|
374
|
-
|
|
375
|
-
# Logowanie komend do plików
|
|
376
|
-
COMMAND_BUS_LOG=./command-logs # Ścieżka do katalogu logów
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### Priorytety Konfiguracji
|
|
380
|
-
|
|
381
|
-
1. **Parametry konstruktora** - najwyższy priorytet
|
|
382
|
-
2. **Zmienne środowiskowe** - średni priorytet
|
|
383
|
-
3. **Wartości domyślne** - najniższy priorytet
|
|
136
|
+
### Konfiguracja i inicjalizacja
|
|
384
137
|
|
|
385
138
|
```typescript
|
|
386
|
-
|
|
387
|
-
const config = new CommandBusConfig({
|
|
388
|
-
redisUrl: 'redis://localhost:6379',
|
|
389
|
-
concurrency: 20, // Nadpisuje COMMAND_BUS_CONCURRENCY
|
|
390
|
-
autoOptimize: false, // Wyłącza auto-optymalizację
|
|
391
|
-
});
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
## Konfiguracja zaawansowana
|
|
395
|
-
|
|
396
|
-
### Auto-optymalizacja Concurrency
|
|
139
|
+
import { CommandBus, CommandBusConfig } from 'pp-command-bus';
|
|
397
140
|
|
|
398
|
-
Auto-optymalizacja automatycznie oblicza optymalną wartość concurrency na podstawie zasobów systemowych:
|
|
399
|
-
|
|
400
|
-
```typescript
|
|
401
|
-
// Auto-optymalizacja włączona (domyślnie)
|
|
402
|
-
const config = new CommandBusConfig({
|
|
403
|
-
redisUrl: 'redis://localhost:6379',
|
|
404
|
-
autoOptimize: true, // Domyślnie true
|
|
405
|
-
// concurrency zostanie obliczone jako: CPU cores * 2 + (availableMemory / 512MB)
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
// Wyłączenie auto-optymalizacji
|
|
409
|
-
const config2 = new CommandBusConfig({
|
|
410
|
-
redisUrl: 'redis://localhost:6379',
|
|
411
|
-
autoOptimize: false,
|
|
412
|
-
concurrency: 10, // Ręczna wartość
|
|
413
|
-
});
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
**Algorytm auto-optymalizacji**:
|
|
417
|
-
```
|
|
418
|
-
concurrency = (CPU cores * 2) + Math.floor(availableMemory / 512MB)
|
|
419
|
-
|
|
420
|
-
Przykład:
|
|
421
|
-
- 8 CPU cores
|
|
422
|
-
- 16GB RAM dostępne
|
|
423
|
-
- concurrency = (8 * 2) + Math.floor(16384 / 512) = 16 + 32 = 48
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### Kompresja Payloadów
|
|
427
|
-
|
|
428
|
-
Automatyczna kompresja gzip dla payloadów RPC większych niż threshold:
|
|
429
|
-
|
|
430
|
-
```typescript
|
|
431
|
-
const config = new CommandBusConfig({
|
|
432
|
-
redisUrl: 'redis://localhost:6379',
|
|
433
|
-
compressionThreshold: 2048, // 2KB (domyślnie 1KB)
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// Przykład: payload 3KB zostanie automatycznie skompresowany
|
|
437
|
-
const largeCommand = new ProcessReportCommand(largeData); // 3KB
|
|
438
|
-
const result = await commandBus.call(largeCommand); // Automatyczna kompresja/dekompresja
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
**Korzyści kompresji**:
|
|
442
|
-
- Redukcja transferu danych przez Redis
|
|
443
|
-
- Szybsze przesyłanie dużych payloadów
|
|
444
|
-
- Niższe zużycie pamięci Redis
|
|
445
|
-
- Transparent dla użytkownika (automatyczna dekompresja)
|
|
446
|
-
|
|
447
|
-
### Command Logging
|
|
448
|
-
|
|
449
|
-
Logowanie komend do plików JSONL (rotacja co godzinę):
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
const config = new CommandBusConfig({
|
|
453
|
-
redisUrl: 'redis://localhost:6379',
|
|
454
|
-
commandLog: './command-logs', // Ścieżka do katalogu logów
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
// Struktura plików:
|
|
458
|
-
// ./command-logs/2025-01-27T10.jsonl
|
|
459
|
-
// ./command-logs/2025-01-27T11.jsonl
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
**Format JSONL (JSON Lines)**:
|
|
463
|
-
```json
|
|
464
|
-
{"__name":"CreateUserCommand","__id":"uuid","__time":1706347200000,"email":"jan@example.com","name":"Jan"}
|
|
465
|
-
{"__name":"ProcessOrderCommand","__id":"uuid","__time":1706347201000,"orderId":"12345"}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
### Opcje Redis
|
|
469
|
-
|
|
470
|
-
```typescript
|
|
471
|
-
const config = new CommandBusConfig({
|
|
472
|
-
redisUrl: 'redis://username:password@localhost:6379/0',
|
|
473
|
-
// Parsuje się do:
|
|
474
|
-
// - host: localhost
|
|
475
|
-
// - port: 6379
|
|
476
|
-
// - username: username (opcjonalnie)
|
|
477
|
-
// - password: password (opcjonalnie)
|
|
478
|
-
// - db: 0 (opcjonalnie)
|
|
479
|
-
});
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### Concurrency i Retry
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
const config = new CommandBusConfig({
|
|
486
|
-
redisUrl: 'redis://localhost:6379',
|
|
487
|
-
concurrency: 10, // Liczba równoległych workerów (lub auto)
|
|
488
|
-
maxAttempts: 5, // Maksymalna liczba prób przetworzenia zadania
|
|
489
|
-
backoffDelay: 3000, // Opóźnienie między próbami (3s)
|
|
490
|
-
});
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
### Tryb kolejki
|
|
494
|
-
|
|
495
|
-
```typescript
|
|
496
|
-
const config = new CommandBusConfig({
|
|
497
|
-
redisUrl: 'redis://localhost:6379',
|
|
498
|
-
queueMode: 'fifo', // 'fifo' (First In First Out) lub 'lifo' (Last In First Out)
|
|
499
|
-
});
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
### Custom Logger
|
|
503
|
-
|
|
504
|
-
Logger jest automatycznie opakowywany przez wewnętrzny wrapper, który filtruje logi według `logLevel`:
|
|
505
|
-
|
|
506
|
-
```typescript
|
|
507
|
-
// Prosty logger - używa console
|
|
508
141
|
const config = new CommandBusConfig({
|
|
509
142
|
redisUrl: 'redis://localhost:6379',
|
|
510
143
|
logger: console,
|
|
511
|
-
logLevel: 'log', // debug | log | warn | error
|
|
512
144
|
});
|
|
513
145
|
|
|
514
|
-
|
|
515
|
-
class MyLogger {
|
|
516
|
-
log(message: string, ...args: unknown[]): void {
|
|
517
|
-
// Twoja implementacja
|
|
518
|
-
}
|
|
519
|
-
error(message: string, ...args: unknown[]): void {
|
|
520
|
-
// Twoja implementacja
|
|
521
|
-
}
|
|
522
|
-
warn(message: string, ...args: unknown[]): void {
|
|
523
|
-
// Twoja implementacja
|
|
524
|
-
}
|
|
525
|
-
debug(message: string, ...args: unknown[]): void {
|
|
526
|
-
// Twoja implementacja
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const config2 = new CommandBusConfig({
|
|
531
|
-
redisUrl: 'redis://localhost:6379',
|
|
532
|
-
logger: new MyLogger(),
|
|
533
|
-
logLevel: 'debug', // Wszystkie poziomy
|
|
534
|
-
});
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
## API Reference
|
|
538
|
-
|
|
539
|
-
### CommandBus
|
|
540
|
-
|
|
541
|
-
#### `dispatch(command: Command): Promise<void>`
|
|
542
|
-
|
|
543
|
-
Wysyła komendę do kolejki bez oczekiwania na wynik (fire-and-forget).
|
|
544
|
-
|
|
545
|
-
**Flow**:
|
|
546
|
-
1. Kompresja payloadu (jeśli >threshold)
|
|
547
|
-
2. Pobranie/utworzenie kolejki z cache
|
|
548
|
-
3. Dodanie job do BullMQ
|
|
549
|
-
4. Natychmiastowy return
|
|
550
|
-
|
|
551
|
-
```typescript
|
|
552
|
-
await commandBus.dispatch(new CreateUserCommand('email@example.com', 'Jan Kowalski'));
|
|
146
|
+
const bus = new CommandBus(config);
|
|
553
147
|
```
|
|
554
148
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
Wywołuje komendę synchronicznie i czeka na odpowiedź przez Redis Pub/Sub (RPC). Domyślny timeout: 30000ms (30s).
|
|
558
|
-
|
|
559
|
-
**Flow**:
|
|
560
|
-
1. Rejestracja pending call z timeoutem
|
|
561
|
-
2. Kompresja payloadu (jeśli >threshold)
|
|
562
|
-
3. Przygotowanie metadanych RPC (correlationId, responseChannel)
|
|
563
|
-
4. Dodanie job do BullMQ
|
|
564
|
-
5. Oczekiwanie na odpowiedź przez shared subscriber
|
|
565
|
-
6. Dekompresja odpowiedzi
|
|
566
|
-
7. Zwrócenie wyniku lub błędu
|
|
149
|
+
### Rejestracja handlera (worker)
|
|
567
150
|
|
|
568
151
|
```typescript
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
Rejestruje handler dla określonej klasy komendy. Tylko jeden handler per typ komendy.
|
|
575
|
-
|
|
576
|
-
**Automatyczne akcje**:
|
|
577
|
-
1. Rejestracja handlera w Map
|
|
578
|
-
2. Utworzenie workera BullMQ
|
|
579
|
-
3. Uruchomienie benchmarku dla optymalnego concurrency
|
|
580
|
-
4. Utworzenie metrics collector
|
|
581
|
-
5. Setup event handlers
|
|
582
|
-
|
|
583
|
-
```typescript
|
|
584
|
-
commandBus.handle(CreateUserCommand, async (command) => {
|
|
585
|
-
const user = await createUser(command.email, command.name);
|
|
586
|
-
return { userId: user.id };
|
|
152
|
+
bus.handle(CreateUserCommand, async (command) => {
|
|
153
|
+
const { name, email } = command.__payload;
|
|
154
|
+
// Logika biznesowa
|
|
155
|
+
await userService.create({ name, email });
|
|
156
|
+
return { userId: '123' };
|
|
587
157
|
});
|
|
588
158
|
```
|
|
589
159
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
Zamyka wszystkie połączenia i workery z graceful shutdown.
|
|
593
|
-
|
|
594
|
-
**Cleanup**:
|
|
595
|
-
1. Zamknięcie wszystkich workerów BullMQ
|
|
596
|
-
2. Zamknięcie wszystkich kolejek z cache
|
|
597
|
-
3. Zamknięcie RpcCoordinator (reject pending calls)
|
|
598
|
-
4. Zamknięcie 3 połączeń Redis (Queue, Worker, RPC)
|
|
599
|
-
|
|
600
|
-
```typescript
|
|
601
|
-
await commandBus.close();
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
### Command
|
|
605
|
-
|
|
606
|
-
Klasa bazowa dla wszystkich komend. Każda komenda dziedziczy po `Command`:
|
|
160
|
+
### Wysyłanie komendy (fire-and-forget)
|
|
607
161
|
|
|
608
162
|
```typescript
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
super();
|
|
612
|
-
}
|
|
613
|
-
}
|
|
163
|
+
const cmd = new CreateUserCommand({ name: 'Jan Kowalski', email: 'jan@example.com' });
|
|
164
|
+
await bus.dispatch(cmd);
|
|
614
165
|
```
|
|
615
166
|
|
|
616
|
-
|
|
617
|
-
- `__id` - unikalny UUID (randomUUID)
|
|
618
|
-
- `__name` - nazwa klasy komendy (constructor.name)
|
|
619
|
-
- `__time` - timestamp utworzenia (Date.now())
|
|
620
|
-
|
|
621
|
-
**Metody statyczne**:
|
|
622
|
-
- `reconstructDates(obj)` - rekonstrukcja obiektów Date z serializowanych danych
|
|
623
|
-
|
|
624
|
-
### CommandBusConfig
|
|
625
|
-
|
|
626
|
-
Konfiguracja CommandBus z opcjami:
|
|
167
|
+
### Wysyłanie batch
|
|
627
168
|
|
|
628
169
|
```typescript
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
maxAttempts?: number; // Maksymalna liczba prób (domyślnie 1)
|
|
635
|
-
backoffDelay?: number; // Opóźnienie między próbami w ms (domyślnie 2000)
|
|
636
|
-
queueMode?: 'fifo' | 'lifo'; // Tryb kolejki (domyślnie 'fifo')
|
|
637
|
-
commandLog?: string; // Ścieżka do katalogu logów komend (opcjonalnie)
|
|
638
|
-
autoOptimize?: boolean; // Auto-optymalizacja concurrency (domyślnie true)
|
|
639
|
-
compressionThreshold?: number; // Próg kompresji w bajtach (domyślnie 1024)
|
|
640
|
-
}
|
|
170
|
+
const commands = [
|
|
171
|
+
new CreateUserCommand({ name: 'Anna Nowak', email: 'anna@example.com' }),
|
|
172
|
+
new CreateUserCommand({ name: 'Piotr Wiśniewski', email: 'piotr@example.com' }),
|
|
173
|
+
];
|
|
174
|
+
await bus.dispatchBatch(commands);
|
|
641
175
|
```
|
|
642
176
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
### Podstawowy przykład z RPC
|
|
177
|
+
### Wywołanie RPC (request/response)
|
|
646
178
|
|
|
647
179
|
```typescript
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
// Definicja komendy
|
|
651
|
-
class CalculateCommand extends Command {
|
|
652
|
-
constructor(
|
|
653
|
-
public readonly a: number,
|
|
654
|
-
public readonly b: number,
|
|
655
|
-
public readonly operation: 'add' | 'multiply',
|
|
656
|
-
) {
|
|
657
|
-
super();
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Konfiguracja
|
|
662
|
-
const config = new CommandBusConfig({
|
|
663
|
-
redisUrl: 'redis://localhost:6379',
|
|
664
|
-
});
|
|
665
|
-
const commandBus = new CommandBus(config);
|
|
666
|
-
|
|
667
|
-
// Rejestracja handlera
|
|
668
|
-
commandBus.handle(CalculateCommand, async (command) => {
|
|
669
|
-
console.log(`Calculating: ${command.a} ${command.operation} ${command.b}`);
|
|
670
|
-
|
|
671
|
-
switch (command.operation) {
|
|
672
|
-
case 'add':
|
|
673
|
-
return command.a + command.b;
|
|
674
|
-
case 'multiply':
|
|
675
|
-
return command.a * command.b;
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
// Fire-and-forget
|
|
680
|
-
await commandBus.dispatch(new CalculateCommand(5, 3, 'add'));
|
|
681
|
-
|
|
682
|
-
// RPC - czekamy na wynik
|
|
683
|
-
const result = await commandBus.call<number>(
|
|
684
|
-
new CalculateCommand(5, 3, 'multiply'),
|
|
685
|
-
5000 // timeout 5s
|
|
686
|
-
);
|
|
687
|
-
console.log(`Result: ${result}`); // Result: 15
|
|
180
|
+
const result = await bus.call<{ userId: string }>(cmd, 5000); // timeout 5s
|
|
181
|
+
console.log(result.userId); // '123'
|
|
688
182
|
```
|
|
689
183
|
|
|
690
|
-
###
|
|
184
|
+
### Zamykanie
|
|
691
185
|
|
|
692
186
|
```typescript
|
|
693
|
-
//
|
|
694
|
-
const [result1, result2, result3] = await Promise.all([
|
|
695
|
-
commandBus.call<number>(new CalculateCommand(10, 5, 'add')),
|
|
696
|
-
commandBus.call<UserInfo>(new GetUserInfoCommand('user-1')),
|
|
697
|
-
commandBus.call<ValidationResult>(new ValidateUserCommand('jan@example.com', 30)),
|
|
698
|
-
]);
|
|
699
|
-
|
|
700
|
-
console.log('All results:', { result1, result2, result3 });
|
|
187
|
+
await bus.close(); // Graceful shutdown — czeka na aktywne zadania
|
|
701
188
|
```
|
|
702
189
|
|
|
703
|
-
|
|
190
|
+
## API
|
|
704
191
|
|
|
705
|
-
|
|
706
|
-
commandBus.handle(CreateUserCommand, async (command) => {
|
|
707
|
-
try {
|
|
708
|
-
// Walidacja
|
|
709
|
-
if (!command.email.includes('@')) {
|
|
710
|
-
throw new Error('Invalid email format');
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const user = await createUser(command.email, command.name);
|
|
714
|
-
return { userId: user.id };
|
|
715
|
-
} catch (error) {
|
|
716
|
-
// Loguj błąd
|
|
717
|
-
console.error('Failed to create user:', error);
|
|
718
|
-
|
|
719
|
-
// Rzuć błąd - BullMQ spróbuje ponownie (maxAttempts)
|
|
720
|
-
throw error;
|
|
721
|
-
}
|
|
722
|
-
});
|
|
192
|
+
### `CommandBus`
|
|
723
193
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
194
|
+
| Metoda | Opis |
|
|
195
|
+
|--------|------|
|
|
196
|
+
| `dispatch(command)` | Wysyła komendę fire-and-forget (`XADD`) |
|
|
197
|
+
| `dispatchBatch(commands)` | Wysyła wiele komend w jednym pipeline |
|
|
198
|
+
| `call<T>(command, timeout?)` | RPC — wysyła i czeka na wynik (`XADD` + `BRPOP`). Timeout per wywołanie w ms (domyślnie `30000`) |
|
|
199
|
+
| `handle(CommandClass, handler)` | Rejestruje handler dla komendy |
|
|
200
|
+
| `close()` | Graceful shutdown — zamyka połączenia i czeka na aktywne zadania |
|
|
731
201
|
|
|
732
|
-
###
|
|
202
|
+
### `Command<T>`
|
|
733
203
|
|
|
734
|
-
|
|
735
|
-
process.on('SIGTERM', async () => {
|
|
736
|
-
console.log('Shutting down CommandBus...');
|
|
737
|
-
await commandBus.close();
|
|
738
|
-
process.exit(0);
|
|
739
|
-
});
|
|
204
|
+
Bazowa klasa abstrakcyjna dla komend:
|
|
740
205
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
206
|
+
| Pole | Typ | Opis |
|
|
207
|
+
|------|-----|------|
|
|
208
|
+
| `__name` | `string` | Nazwa komendy (nazwa klasy) |
|
|
209
|
+
| `__id` | `string` | UUID v4 |
|
|
210
|
+
| `__time` | `number` | Timestamp utworzenia (ms) |
|
|
211
|
+
| `__payload` | `T` | Dane biznesowe komendy |
|
|
747
212
|
|
|
748
|
-
##
|
|
213
|
+
## Konfiguracja
|
|
749
214
|
|
|
750
|
-
###
|
|
215
|
+
### Zmienne środowiskowe
|
|
751
216
|
|
|
752
|
-
|
|
217
|
+
| Zmienna | Domyślna | Opis |
|
|
218
|
+
|---------|----------|------|
|
|
219
|
+
| `REDIS_URL` | `redis://localhost:6379` | URL połączenia Redis/DragonflyDB |
|
|
220
|
+
| `REDIS_RETRY_DELAY` | `5000` | Opóźnienie między próbami reconnect (ms) |
|
|
221
|
+
| `REDIS_MAX_RETRIES` | `0` | Maks. prób reconnect do Redis (`0` = nieskończoność) |
|
|
222
|
+
| `LOG_LEVEL` | `log` | Poziom logowania (`debug`, `log`, `warn`, `error`) |
|
|
223
|
+
| `COMMAND_BUS_CONCURRENCY` | `60 × CPU` | Maks. równoległych wiadomości per konsument (I/O-bound: default, CPU-bound: `availableParallelism()`) |
|
|
224
|
+
| `COMMAND_BUS_MAX_ATTEMPTS` | `3` | Maks. prób przetworzenia wiadomości |
|
|
225
|
+
| `COMMAND_BUS_LOG` | _(puste)_ | Ścieżka do katalogu logów komend |
|
|
226
|
+
| `COMMAND_BUS_POOL_SIZE` | `2 × CPU` | Rozmiar puli połączeń Redis |
|
|
227
|
+
| `COMMAND_BUS_MAX_CONCURRENT_RPC` | `50` | Maks. równoległych wywołań RPC |
|
|
228
|
+
| `COMMAND_BUS_BATCH_SIZE` | `100` | Wiadomości pobieranych w jednym `XREADGROUP` (mniej roundtripów = wyższy throughput) |
|
|
229
|
+
| `COMMAND_BUS_CLAIM_TIMEOUT` | `30000` | Czas (ms) po którym stalled wiadomość jest przejmowana |
|
|
230
|
+
| `COMMAND_BUS_MAX_RETAINED` | `10000` | Maks. wiadomości w strumieniu (`XTRIM ~`) |
|
|
753
231
|
|
|
754
|
-
|
|
755
|
-
- **Event-driven metrics** - zbieranie metryk z workerów
|
|
756
|
-
- **Dynamiczne dostosowanie** - +/-20% co 30s (cooldown)
|
|
757
|
-
- **Limity** - min 10, max 2000
|
|
232
|
+
### Konfiguracja programowa
|
|
758
233
|
|
|
759
234
|
```typescript
|
|
760
|
-
// Automatyczne - benchmark ustali optymalną wartość
|
|
761
235
|
const config = new CommandBusConfig({
|
|
762
236
|
redisUrl: 'redis://localhost:6379',
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
- Otrzymuje tylko swoje odpowiedzi
|
|
775
|
-
|
|
776
|
-
Process B (UUID: def-456):
|
|
777
|
-
- Kanał: rpc:response:def-456:*
|
|
778
|
-
- Otrzymuje tylko swoje odpowiedzi
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
### 3. Shared Subscriber Pattern
|
|
782
|
-
|
|
783
|
-
Jeden shared subscriber dla wszystkich RPC calls zamiast N subskrybentów:
|
|
784
|
-
|
|
785
|
-
**Tradycyjne podejście** (N subskrybentów):
|
|
786
|
-
```
|
|
787
|
-
1000 RPC calls → 1000 Redis subscriptions → duże obciążenie
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
**Shared Subscriber** (1 subskrybent):
|
|
791
|
-
```
|
|
792
|
-
1000 RPC calls → 1 Redis pattern subscription → multiplexing w pamięci
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
### 4. Automatyczna Kompresja
|
|
796
|
-
|
|
797
|
-
PayloadCompressionService automatycznie kompresuje duże payloady:
|
|
798
|
-
|
|
799
|
-
```typescript
|
|
800
|
-
// Mała komenda (<1KB) - brak kompresji
|
|
801
|
-
const smallCommand = new CreateUserCommand('jan@example.com', 'Jan');
|
|
802
|
-
await commandBus.call(smallCommand); // Bez kompresji
|
|
803
|
-
|
|
804
|
-
// Duża komenda (>1KB) - automatyczna kompresja
|
|
805
|
-
const largeCommand = new ProcessReportCommand(largeData); // 5KB
|
|
806
|
-
await commandBus.call(largeCommand); // Automatyczna kompresja gzip → base64
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
**Flagi kompresji**:
|
|
810
|
-
- `__compressed: true` - payload został skompresowany
|
|
811
|
-
- Automatyczna dekompresja w JobProcessor
|
|
812
|
-
- Transparent dla użytkownika
|
|
813
|
-
|
|
814
|
-
### 5. Command Logging
|
|
815
|
-
|
|
816
|
-
Persystencja wszystkich komend do plików JSONL:
|
|
817
|
-
|
|
818
|
-
```typescript
|
|
819
|
-
const config = new CommandBusConfig({
|
|
820
|
-
commandLog: './command-logs',
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
// Każda komenda jest logowana do pliku:
|
|
824
|
-
// ./command-logs/2025-01-27T10.jsonl
|
|
825
|
-
```
|
|
826
|
-
|
|
827
|
-
**Use cases**:
|
|
828
|
-
- Auditing i compliance
|
|
829
|
-
- Replay komend
|
|
830
|
-
- Debugging produkcyjnych problemów
|
|
831
|
-
- Analiza przepływu komend
|
|
832
|
-
|
|
833
|
-
### 6. RPC Job Cancellation
|
|
834
|
-
|
|
835
|
-
Automatyczne usuwanie niepodjętych jobów RPC przy timeout:
|
|
836
|
-
|
|
837
|
-
```
|
|
838
|
-
RPC Timeout Flow:
|
|
839
|
-
|
|
840
|
-
User Code RpcCoordinator Redis JobProcessor
|
|
841
|
-
│ │ │ │
|
|
842
|
-
│── call(cmd) ─────▶│ │ │
|
|
843
|
-
│ │── registerCall ──▶│ │
|
|
844
|
-
│ │── queue.add ─────▶│ │
|
|
845
|
-
│ │ │ │
|
|
846
|
-
│ [timeout] │ │ │
|
|
847
|
-
│ │ │ │
|
|
848
|
-
│ │── markCancelled ─▶│ SET rpc:cancelled:xxx
|
|
849
|
-
│ │── tryRemoveJob ──▶│ (próba usunięcia)
|
|
850
|
-
│◀── Error ─────────│ │ │
|
|
851
|
-
│ │ │ │
|
|
852
|
-
│ │ │ [job picked up] │
|
|
853
|
-
│ │ │──────────────────▶│
|
|
854
|
-
│ │ │ │── isCancelled?
|
|
855
|
-
│ │ │◀──────────────────│ → true
|
|
856
|
-
│ │ │ │── SKIP handler
|
|
857
|
-
│ │ │ │── clearCancellation
|
|
858
|
-
```
|
|
859
|
-
|
|
860
|
-
**Kluczowe cechy**:
|
|
861
|
-
- **Dwufazowe anulowanie**: Flaga Redis + próba usunięcia joba z kolejki
|
|
862
|
-
- **Graceful degradation**: Błędy Redis nie blokują przetwarzania
|
|
863
|
-
- **TTL 24h**: Automatyczne wygaśnięcie kluczy cancellation
|
|
864
|
-
- **Aktywne czyszczenie**: Klucze usuwane po przetworzeniu lub usunięciu joba
|
|
865
|
-
- **Kompatybilność wsteczna**: Funkcjonalność jest opcjonalna
|
|
866
|
-
|
|
867
|
-
**Korzyści**:
|
|
868
|
-
- Oszczędność zasobów - nieprzetworzone joby nie obciążają workerów
|
|
869
|
-
- Brak efektów ubocznych - handler nie wykonuje się dla timeout'owanych RPC
|
|
870
|
-
- Lepsza diagnostyka - logi pokazują pominięte joby
|
|
871
|
-
|
|
872
|
-
## Best Practices
|
|
873
|
-
|
|
874
|
-
### 1. Używaj TypeScript
|
|
875
|
-
|
|
876
|
-
Biblioteka jest napisana w TypeScript z strict mode. Wykorzystaj typy dla lepszego DX:
|
|
877
|
-
|
|
878
|
-
```typescript
|
|
879
|
-
interface UserCreatedResult {
|
|
880
|
-
userId: string;
|
|
881
|
-
createdAt: Date;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
const result = await commandBus.call<UserCreatedResult>(command);
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
### 2. Idempotentne handlery
|
|
888
|
-
|
|
889
|
-
Handlery powinny być idempotentne (wielokrotne wykonanie = ten sam rezultat):
|
|
890
|
-
|
|
891
|
-
```typescript
|
|
892
|
-
commandBus.handle(CreateUserCommand, async (command) => {
|
|
893
|
-
// Sprawdź czy użytkownik już istnieje
|
|
894
|
-
const existing = await findUserByEmail(command.email);
|
|
895
|
-
if (existing) {
|
|
896
|
-
return { userId: existing.id }; // Zwróć istniejącego
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// Utwórz nowego
|
|
900
|
-
const user = await createUser(command.email, command.name);
|
|
901
|
-
return { userId: user.id };
|
|
237
|
+
logger: console,
|
|
238
|
+
logLevel: 'log', // debug | log | warn | error
|
|
239
|
+
redisRetryDelay: 5000, // Opóźnienie reconnect (ms)
|
|
240
|
+
redisMaxRetries: 0, // 0 = nieskończoność
|
|
241
|
+
concurrency: 960, // I/O-bound: 60 × CPU, CPU-bound: availableParallelism()
|
|
242
|
+
poolSize: 32, // Rozmiar puli połączeń Redis
|
|
243
|
+
maxConcurrentRpc: 100, // Maks. równoległych BRPOP
|
|
244
|
+
batchSize: 100, // Wiadomości per XREADGROUP
|
|
245
|
+
claimTimeout: 60000, // Stalled message timeout (ms)
|
|
246
|
+
maxRetained: 50000, // XTRIM ~ limit
|
|
247
|
+
maxAttempts: 5, // Maks. prób przetworzenia
|
|
902
248
|
});
|
|
903
249
|
```
|
|
904
250
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
251
|
+
## Flow danych
|
|
252
|
+
|
|
253
|
+
### 1. Dispatch (fire-and-forget)
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
Producer Redis Worker
|
|
257
|
+
│ │ │
|
|
258
|
+
│ 1. serialize(command) │ │
|
|
259
|
+
│ → MessagePack encode │ │
|
|
260
|
+
│ │ │
|
|
261
|
+
│ 2. RedisCodec.encode(buffer) │ │
|
|
262
|
+
│ → base64 string │ │
|
|
263
|
+
│ │ │
|
|
264
|
+
│ 3. XADD cmd:CreateUser * ──→ │ stream: cmd:CreateUser │
|
|
265
|
+
│ data <base64> │ ┌─────────────────────┐ │
|
|
266
|
+
│ │ │ msg-1: data=<b64> │ │
|
|
267
|
+
│ │ └─────────────────────┘ │
|
|
268
|
+
│ │ │
|
|
269
|
+
│ │ ←── 4. XREADGROUP GROUP │
|
|
270
|
+
│ │ workers consumer-1 │
|
|
271
|
+
│ │ COUNT 100 BLOCK 5000 │
|
|
272
|
+
│ │ STREAMS cmd:CreateUser > │
|
|
273
|
+
│ │ │
|
|
274
|
+
│ │ ──→ [msg-1, [data, <b64>]] │
|
|
275
|
+
│ │ │
|
|
276
|
+
│ │ 5. RedisCodec.decode │
|
|
277
|
+
│ │ 6. deserialize(buffer) │
|
|
278
|
+
│ │ 7. handler(command) │
|
|
279
|
+
│ │ │
|
|
280
|
+
│ │ ←── 8. XACK cmd:CreateUser │
|
|
281
|
+
│ │ workers msg-1 │
|
|
282
|
+
│ │ │
|
|
283
|
+
│ │ ←── 9. XTRIM cmd:CreateUser │
|
|
284
|
+
│ │ MAXLEN ~ 10000 │
|
|
285
|
+
│ │ (co batchSize wiadomości)│
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 2. RPC (request/response)
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
Caller Redis Worker
|
|
292
|
+
│ │ │
|
|
293
|
+
│ 1. serialize(command) │ │
|
|
294
|
+
│ 2. Wrap w RpcEnvelope: │ │
|
|
295
|
+
│ { commandData, rpc: { │ │
|
|
296
|
+
│ correlationId, │ │
|
|
297
|
+
│ responseKey │ │
|
|
298
|
+
│ }} │ │
|
|
299
|
+
│ │ │
|
|
300
|
+
│ 3. XADD cmd:GetUser * ────→ │ stream: cmd:GetUser │
|
|
301
|
+
│ data <envelope> │ ┌─────────────────────┐ │
|
|
302
|
+
│ rpc 1 │ │ msg-1: data=<env> │ │
|
|
303
|
+
│ │ │ rpc=1 │ │
|
|
304
|
+
│ 4. BRPOP rpc:res:{uuid} ──→ │ └─────────────────────┘ │
|
|
305
|
+
│ timeout 30s │ │
|
|
306
|
+
│ (dedykowane połączenie │ ←── 5. XREADGROUP │
|
|
307
|
+
│ z RpcConnectionPool) │ │
|
|
308
|
+
│ │ 6. Wykryj marker rpc=1 │
|
|
309
|
+
│ │ 7. Rozpakuj envelope │
|
|
310
|
+
│ │ 8. handler(commandData) │
|
|
311
|
+
│ │ 9. serialize({result}) │
|
|
312
|
+
│ │ │
|
|
313
|
+
│ │ ←── 10. LPUSH rpc:res:{uuid}│
|
|
314
|
+
│ │ <result> │
|
|
315
|
+
│ │ EXPIRE rpc:res:{uuid} 60│
|
|
316
|
+
│ │ │
|
|
317
|
+
│ ←── result z BRPOP │ │
|
|
318
|
+
│ 11. deserialize(result) │ │
|
|
319
|
+
│ 12. DEL rpc:res:{uuid} │ │
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 3. Recovery + Dead Letter Queue
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
PendingRecovery Redis
|
|
326
|
+
│ │
|
|
327
|
+
│ (setInterval co claimTimeout) │
|
|
328
|
+
│ │
|
|
329
|
+
│ 1. XPENDING cmd:CreateUser ─→│ Pending entries:
|
|
330
|
+
│ workers - + 10 │ [msg-5, consumer-1,
|
|
331
|
+
│ │ idle: 45000, delivery: 2]
|
|
332
|
+
│ │
|
|
333
|
+
│ if idle > claimTimeout │
|
|
334
|
+
│ AND delivery < maxAttempts: │
|
|
335
|
+
│ │
|
|
336
|
+
│ 2. XCLAIM cmd:CreateUser ──→ │ Przejęcie wiadomości
|
|
337
|
+
│ workers consumer-2 │
|
|
338
|
+
│ 30000 msg-5 │
|
|
339
|
+
│ │
|
|
340
|
+
│ 3. processMessage(msg-5) │ → handler() → XACK
|
|
341
|
+
│ │
|
|
342
|
+
│ if delivery >= maxAttempts: │
|
|
343
|
+
│ │
|
|
344
|
+
│ 4. XCLAIM msg-5 ───────────→ │
|
|
345
|
+
│ 5. XADD dlq:cmd:CreateUser → │ Dead Letter Queue
|
|
346
|
+
│ * ...fields │ ┌──────────────────────┐
|
|
347
|
+
│ original_stream │ │ msg: data + metadata │
|
|
348
|
+
│ cmd:CreateUser │ │ original_stream │
|
|
349
|
+
│ delivery_count 3 │ │ delivery_count: 3 │
|
|
350
|
+
│ 6. XACK cmd:CreateUser ────→ │ └──────────────────────┘
|
|
351
|
+
│ workers msg-5 │
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Komponenty
|
|
355
|
+
|
|
356
|
+
### RedisStreamsTransport (fasada)
|
|
357
|
+
|
|
358
|
+
Kompozycja 4 komponentów implementująca `ITransport`:
|
|
359
|
+
|
|
360
|
+
| Komponent | Odpowiedzialność | Komendy Redis |
|
|
361
|
+
|-----------|------------------|---------------|
|
|
362
|
+
| **StreamProducer** | Enqueue / enqueueBatch | `XADD`, pipeline |
|
|
363
|
+
| **StreamConsumer** | Cykl życia konsumenta, deduplicacja | `XGROUP CREATE` |
|
|
364
|
+
| **RpcHandler** | Request/response RPC | `XADD`, `BRPOP`, `LPUSH`, `DEL` |
|
|
365
|
+
| **PendingRecovery** | Automatyczny retry + DLQ | `XPENDING`, `XCLAIM`, `XACK` |
|
|
366
|
+
|
|
367
|
+
### StreamConsumer (koordynator)
|
|
368
|
+
|
|
369
|
+
StreamConsumer komponuje dwa podkomponenty:
|
|
370
|
+
|
|
371
|
+
| Podkomponent | Odpowiedzialność | LOC |
|
|
372
|
+
|--------------|------------------|-----|
|
|
373
|
+
| **MessageProcessor** | Parsowanie fields, walidacja, RPC detection, handler, XACK/XTRIM | ~147 |
|
|
374
|
+
| **ConsumerLoop** | while(running) loop, XREADGROUP BLOCK, concurrency limiter, backoff | ~120 |
|
|
375
|
+
| **StreamConsumer** | Koordynacja: compose, deduplicacja (Map z TTL sweep), lifecycle | ~188 |
|
|
376
|
+
|
|
377
|
+
### Pule połączeń
|
|
378
|
+
|
|
379
|
+
| Pula | Wzorzec | Tworzenie | Przeznaczenie |
|
|
380
|
+
|------|---------|-----------|---------------|
|
|
381
|
+
| **RedisConnectionPool** | Round-robin, eager | Przy starcie (`size` połączeń) | Operacje nieblokujące: XADD, XACK, DEL, pipeline |
|
|
382
|
+
| **RpcConnectionPool** | Bounded, lazy | Przy `acquire()` (max `maxSize`) | Operacje blokujące: BRPOP |
|
|
917
383
|
|
|
918
|
-
|
|
919
|
-
commandBus.handle(CreateUserCommand, async (command) => {
|
|
920
|
-
// Walidacja na początku
|
|
921
|
-
if (!command.email || !command.email.includes('@')) {
|
|
922
|
-
throw new Error('Invalid email');
|
|
923
|
-
}
|
|
384
|
+
**RedisConnectionPool** dodatkowo udostępnia `createDedicated()` — tworzy izolowane połączenie poza pulą, używane przez `ConsumerLoop` (XREADGROUP BLOCK blokuje socket).
|
|
924
385
|
|
|
925
|
-
|
|
926
|
-
throw new Error('Invalid name');
|
|
927
|
-
}
|
|
386
|
+
### Serializacja
|
|
928
387
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
388
|
+
| Komponent | Warstwa | Opis |
|
|
389
|
+
|-----------|---------|------|
|
|
390
|
+
| **MsgpackSerializer** | Aplikacja | MessagePack z Date extension type — 1 krok zamiast 7 |
|
|
391
|
+
| **RedisCodec** | Transport | Base64 encode/decode — chroni binarne dane przed korupcją UTF-8 przez ioredis |
|
|
392
|
+
|
|
393
|
+
### Concurrency Limiter (ConsumerLoop)
|
|
394
|
+
|
|
395
|
+
```
|
|
396
|
+
┌─ slot 1: handler(msg-1) ──→ done → slot wolny
|
|
397
|
+
XREADGROUP ────→├─ slot 2: handler(msg-2) ──→ done → slot wolny
|
|
398
|
+
(COUNT=N) └─ slot 3: handler(msg-3) ──→ (trwa...)
|
|
399
|
+
│
|
|
400
|
+
czekam (Promise.race) ←────┘
|
|
401
|
+
aż zwolni się slot
|
|
932
402
|
```
|
|
933
403
|
|
|
934
|
-
|
|
404
|
+
- `concurrency` kontroluje liczbę slotów (równoległych handlerów)
|
|
405
|
+
- `batchSize` kontroluje ile wiadomości czytanych na raz
|
|
406
|
+
- Dostępne sloty = `concurrency - active.size`
|
|
407
|
+
- Nowy batch = `Math.min(batchSize, availableSlots)`
|
|
935
408
|
|
|
936
|
-
|
|
937
|
-
commandBus.handle(CreateUserCommand, async (command) => {
|
|
938
|
-
console.log('Processing CreateUserCommand', {
|
|
939
|
-
commandId: command.__id,
|
|
940
|
-
timestamp: command.__time,
|
|
941
|
-
email: command.email,
|
|
942
|
-
});
|
|
409
|
+
### Deduplicacja (StreamConsumer)
|
|
943
410
|
|
|
944
|
-
|
|
945
|
-
const user = await createUser(command.email, command.name);
|
|
411
|
+
`Map<string, number>` — messageId → timestamp:
|
|
946
412
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
});
|
|
413
|
+
- Przed przetworzeniem: `sweepStaleProcessing()` usuwa wpisy starsze niż `staleThreshold` (5 min)
|
|
414
|
+
- Duplikat: `processing.has(messageId) → return` (skip)
|
|
415
|
+
- Po przetworzeniu: `processing.delete(messageId)`
|
|
951
416
|
|
|
952
|
-
|
|
953
|
-
});
|
|
954
|
-
```
|
|
417
|
+
Chroni przed podwójnym przetworzeniem gdy `PendingRecovery` przejmuje wiadomość która jest jeszcze aktywna w consumer loop.
|
|
955
418
|
|
|
956
|
-
|
|
419
|
+
### XTRIM (MessageProcessor)
|
|
957
420
|
|
|
958
|
-
|
|
421
|
+
Throttled per-stream: counter wiadomości na strumień, co `batchSize` wiadomości pipeline `XACK + XTRIM ~ maxRetained`.
|
|
959
422
|
|
|
960
|
-
```typescript
|
|
961
|
-
import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
|
|
962
|
-
|
|
963
|
-
describe('User Commands', () => {
|
|
964
|
-
let commandBus: CommandBus;
|
|
965
|
-
|
|
966
|
-
beforeAll(async () => {
|
|
967
|
-
const config = new CommandBusConfig({
|
|
968
|
-
redisUrl: 'redis://localhost:6379',
|
|
969
|
-
logger: console,
|
|
970
|
-
logLevel: 'error', // Tylko błędy w testach
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
commandBus = new CommandBus(config);
|
|
974
|
-
|
|
975
|
-
// Zarejestruj handler
|
|
976
|
-
commandBus.handle(CreateUserCommand, async (command) => {
|
|
977
|
-
return { userId: 'test-user-id' };
|
|
978
|
-
});
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
afterAll(async () => {
|
|
982
|
-
await commandBus.close();
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
it('should create user', async () => {
|
|
986
|
-
const command = new CreateUserCommand('test@example.com', 'Test User');
|
|
987
|
-
const result = await commandBus.call<{ userId: string }>(command, 5000);
|
|
988
|
-
|
|
989
|
-
expect(result.userId).toBeDefined();
|
|
990
|
-
expect(result.userId).toBe('test-user-id');
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
it('should handle multiple commands in parallel', async () => {
|
|
994
|
-
const commands = [
|
|
995
|
-
new CreateUserCommand('user1@example.com', 'User 1'),
|
|
996
|
-
new CreateUserCommand('user2@example.com', 'User 2'),
|
|
997
|
-
new CreateUserCommand('user3@example.com', 'User 3'),
|
|
998
|
-
];
|
|
999
|
-
|
|
1000
|
-
const results = await Promise.all(
|
|
1001
|
-
commands.map((cmd) => commandBus.call<{ userId: string }>(cmd, 5000))
|
|
1002
|
-
);
|
|
1003
|
-
|
|
1004
|
-
expect(results).toHaveLength(3);
|
|
1005
|
-
results.forEach((result) => {
|
|
1006
|
-
expect(result.userId).toBeDefined();
|
|
1007
|
-
});
|
|
1008
|
-
});
|
|
1009
|
-
});
|
|
1010
423
|
```
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
### Connection refused (Redis)
|
|
1015
|
-
|
|
1016
|
-
Upewnij się że Redis działa:
|
|
1017
|
-
|
|
1018
|
-
```bash
|
|
1019
|
-
redis-cli ping
|
|
1020
|
-
# Powinno zwrócić: PONG
|
|
424
|
+
Strumień A: [1] [2] [3] ... [10] → pipeline XACK + XTRIM → reset counter
|
|
425
|
+
Strumień B: [1] [2] [3] → zwykły XACK (nie osiągnął batchSize)
|
|
1021
426
|
```
|
|
1022
427
|
|
|
1023
|
-
###
|
|
1024
|
-
|
|
1025
|
-
Zwiększ timeout lub sprawdź czy handler został zarejestrowany:
|
|
1026
|
-
|
|
1027
|
-
```typescript
|
|
1028
|
-
// Zwiększ timeout
|
|
1029
|
-
const result = await commandBus.call(command, 60000); // 60s
|
|
1030
|
-
|
|
1031
|
-
// Sprawdź czy handler został zarejestrowany PRZED wywołaniem
|
|
1032
|
-
commandBus.handle(MyCommand, async (command) => {
|
|
1033
|
-
// Handler implementation
|
|
1034
|
-
return { result: 'success' };
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
// Teraz możesz wywołać komendę
|
|
1038
|
-
const result = await commandBus.call(new MyCommand());
|
|
1039
|
-
```
|
|
1040
|
-
|
|
1041
|
-
### Handler nie został wywołany
|
|
1042
|
-
|
|
1043
|
-
Upewnij się że handler został zarejestrowany przed wysłaniem komendy:
|
|
1044
|
-
|
|
1045
|
-
```typescript
|
|
1046
|
-
// ❌ ŹLE - handler po dispatch
|
|
1047
|
-
await commandBus.dispatch(new MyCommand());
|
|
1048
|
-
commandBus.handle(MyCommand, async (cmd) => { ... }); // Za późno!
|
|
428
|
+
### Graceful Shutdown
|
|
1049
429
|
|
|
1050
|
-
// ✅ DOBRZE - handler przed dispatch
|
|
1051
|
-
commandBus.handle(MyCommand, async (cmd) => { ... });
|
|
1052
|
-
await commandBus.dispatch(new MyCommand()); // Teraz OK
|
|
1053
430
|
```
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
```typescript
|
|
1062
|
-
const config = new CommandBusConfig({
|
|
1063
|
-
commandLog: undefined, // Wyłącz logging
|
|
1064
|
-
concurrency: 5, // Zmniejsz concurrency
|
|
1065
|
-
compressionThreshold: 512, // Kompresuj już od 512B
|
|
1066
|
-
});
|
|
431
|
+
close():
|
|
432
|
+
1. consumer.running = false → propaguje do wszystkich ConsumerLoop
|
|
433
|
+
2. recovery.stop() → clearInterval
|
|
434
|
+
3. conn.disconnect() → przerywa XREADGROUP BLOCK
|
|
435
|
+
4. await consumerLoopPromises → czeka na aktywne handlery
|
|
436
|
+
5. pool.close() → quit() na connection pool
|
|
437
|
+
6. rpcPool.close() → quit() available, disconnect() borrowed
|
|
1067
438
|
```
|
|
1068
439
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
Worker został zatrzymany (prawdopodobnie crashed). BullMQ automatycznie przeniesie job do innego workera.
|
|
1072
|
-
|
|
1073
|
-
**Przyczyny**:
|
|
1074
|
-
- Out of memory
|
|
1075
|
-
- Uncaught exception w handlerze
|
|
1076
|
-
- Timeout w handlerze
|
|
1077
|
-
|
|
1078
|
-
**Rozwiązanie**:
|
|
1079
|
-
- Dodaj try/catch w handlerze
|
|
1080
|
-
- Zwiększ pamięć dla procesu
|
|
1081
|
-
- Zmniejsz concurrency
|
|
1082
|
-
|
|
1083
|
-
## Struktura Projektu
|
|
440
|
+
## Struktura projektu
|
|
1084
441
|
|
|
1085
442
|
```
|
|
1086
443
|
src/
|
|
1087
|
-
├──
|
|
1088
|
-
|
|
1089
|
-
│
|
|
1090
|
-
│
|
|
1091
|
-
│ ├──
|
|
1092
|
-
│ │
|
|
1093
|
-
│
|
|
1094
|
-
│ ├──
|
|
1095
|
-
│ │
|
|
1096
|
-
│ ├──
|
|
1097
|
-
│ │
|
|
1098
|
-
│ ├──
|
|
1099
|
-
│ │ ├──
|
|
1100
|
-
│ │ ├──
|
|
1101
|
-
│ │
|
|
1102
|
-
│
|
|
1103
|
-
│
|
|
1104
|
-
│ │ ├──
|
|
1105
|
-
│ │ └──
|
|
1106
|
-
│ ├──
|
|
1107
|
-
│ │ └──
|
|
1108
|
-
│
|
|
1109
|
-
│
|
|
1110
|
-
├── shared/
|
|
1111
|
-
│ ├──
|
|
1112
|
-
│
|
|
1113
|
-
│ ├──
|
|
1114
|
-
│ │ ├──
|
|
1115
|
-
│ │ └──
|
|
1116
|
-
│ ├──
|
|
1117
|
-
│ │ ├──
|
|
1118
|
-
│ │
|
|
1119
|
-
│
|
|
1120
|
-
│
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
```
|
|
1128
|
-
|
|
1129
|
-
## Migracja z pp-event-bus 1.x
|
|
1130
|
-
|
|
1131
|
-
Jeśli używałeś Command Bus z pakietu `pp-event-bus` w wersji 1.x:
|
|
1132
|
-
|
|
1133
|
-
### Przed:
|
|
1134
|
-
|
|
1135
|
-
```typescript
|
|
1136
|
-
import { CommandBus, Command, CommandBusConfig } from 'pp-event-bus';
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1139
|
-
### Po:
|
|
444
|
+
├── index.ts # Eksporty publiczne
|
|
445
|
+
├── command-bus/
|
|
446
|
+
│ ├── index.ts # CommandBus (fasada wyższego poziomu)
|
|
447
|
+
│ ├── command.ts # Bazowa klasa Command<T>
|
|
448
|
+
│ ├── config/
|
|
449
|
+
│ │ └── command-bus-config.ts # Konfiguracja (env + params)
|
|
450
|
+
│ ├── transport/
|
|
451
|
+
│ │ ├── transport.interface.ts # Segregowane interfejsy ISP
|
|
452
|
+
│ │ ├── redis-streams-transport.ts # Fasada transportu (kompozycja 4 komponentów)
|
|
453
|
+
│ │ ├── stream-producer.ts # XADD enqueue / enqueueBatch
|
|
454
|
+
│ │ ├── stream-consumer.ts # Koordynator: lifecycle, dedup, compose
|
|
455
|
+
│ │ ├── message-processor.ts # Parse, validate, XACK/XTRIM, RPC detect
|
|
456
|
+
│ │ ├── consumer-loop.ts # XREADGROUP loop, concurrency limiter
|
|
457
|
+
│ │ ├── rpc-handler.ts # BRPOP/LPUSH request/response
|
|
458
|
+
│ │ ├── pending-recovery.ts # XPENDING/XCLAIM + Dead Letter Queue
|
|
459
|
+
│ │ └── redis-codec.ts # Base64 encode/decode
|
|
460
|
+
│ ├── serialization/
|
|
461
|
+
│ │ ├── serializer.interface.ts # ISerializer
|
|
462
|
+
│ │ └── msgpack-serializer.ts # MessagePack z Date extension
|
|
463
|
+
│ ├── logging/
|
|
464
|
+
│ │ └── command-logger.ts # Opcjonalny logger komend do plików
|
|
465
|
+
│ └── types/
|
|
466
|
+
│ └── index.ts # Typy (CommandPayload, CommandHandler)
|
|
467
|
+
├── shared/
|
|
468
|
+
│ ├── types.ts # ILogger, TDict, TCallableAsync
|
|
469
|
+
│ ├── redis/
|
|
470
|
+
│ │ ├── connection-pool.ts # Round-robin eager pool
|
|
471
|
+
│ │ ├── rpc-connection-pool.ts # Bounded lazy pool (BRPOP)
|
|
472
|
+
│ │ └── redis-error-formatter.ts # Formatowanie błędów Redis
|
|
473
|
+
│ ├── logging/
|
|
474
|
+
│ │ ├── logger.ts # Logger implementacja
|
|
475
|
+
│ │ └── log-level.ts # Poziomy logowania
|
|
476
|
+
│ └── utils/
|
|
477
|
+
│ └── error-utils.ts # getErrorMessage()
|
|
478
|
+
└── examples/
|
|
479
|
+
├── rpc.demo.ts # Demo RPC call
|
|
480
|
+
└── rpc-throughput.demo.ts # Demo throughput
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Testowanie
|
|
1140
484
|
|
|
1141
485
|
```bash
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
```typescript
|
|
1146
|
-
import { CommandBus, Command, CommandBusConfig } from 'pp-command-bus';
|
|
1147
|
-
```
|
|
486
|
+
# Wszystkie testy
|
|
487
|
+
npm test
|
|
1148
488
|
|
|
1149
|
-
|
|
489
|
+
# Z coverage
|
|
490
|
+
npm run test:coverage
|
|
1150
491
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
Projekt używa [Semantic Versioning](https://semver.org/lang/pl/) oraz automatycznych release'ów dzięki [semantic-release](https://github.com/semantic-release/semantic-release).
|
|
1154
|
-
|
|
1155
|
-
### Konwencja commitów
|
|
1156
|
-
|
|
1157
|
-
Używamy [Conventional Commits](https://www.conventionalcommits.org/):
|
|
1158
|
-
|
|
1159
|
-
```bash
|
|
1160
|
-
# Nowa funkcjonalność (minor release)
|
|
1161
|
-
feat: dodano wsparcie dla delayed commands
|
|
492
|
+
# Lint
|
|
493
|
+
npm run lint
|
|
1162
494
|
|
|
1163
|
-
#
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
# Breaking change (major release)
|
|
1167
|
-
feat!: zmieniono API CommandBusConfig
|
|
1168
|
-
|
|
1169
|
-
# Inne typy (patch release)
|
|
1170
|
-
docs: zaktualizowano dokumentację API
|
|
1171
|
-
perf: zoptymalizowano przetwarzanie komend
|
|
1172
|
-
refactor: uproszczono kod RPC handler
|
|
1173
|
-
test: dodano testy dla command logging
|
|
495
|
+
# Format
|
|
496
|
+
npm run format:check
|
|
1174
497
|
```
|
|
1175
498
|
|
|
1176
|
-
###
|
|
1177
|
-
|
|
1178
|
-
- `feat:` - nowa funkcjonalność (→ minor release)
|
|
1179
|
-
- `fix:` - poprawka błędu (→ patch release)
|
|
1180
|
-
- `perf:` - optymalizacja wydajności (→ patch release)
|
|
1181
|
-
- `docs:` - zmiany w dokumentacji (→ patch release)
|
|
1182
|
-
- `style:` - formatowanie kodu (→ patch release)
|
|
1183
|
-
- `refactor:` - refaktoryzacja (→ patch release)
|
|
1184
|
-
- `test:` - dodanie/poprawka testów (→ patch release)
|
|
1185
|
-
- `build:` - zmiany w buildzie/zależnościach (→ patch release)
|
|
1186
|
-
- `ci:` - zmiany w CI/CD (→ patch release)
|
|
1187
|
-
- `chore:` - inne zmiany (bez release)
|
|
1188
|
-
- `!` lub `BREAKING CHANGE:` - breaking change (→ major release)
|
|
1189
|
-
|
|
1190
|
-
## Dokumentacja
|
|
1191
|
-
|
|
1192
|
-
### Architektura
|
|
1193
|
-
|
|
1194
|
-
Szczegółowa dokumentacja architektury systemu, wzorców projektowych i zasad SOLID:
|
|
1195
|
-
- [ARCHITECTURE.md](src/command-bus/docs/ARCHITECTURE.md) - Kompletny opis architektury, diagramy komponentów, przepływy danych
|
|
1196
|
-
|
|
1197
|
-
### Komponenty
|
|
1198
|
-
|
|
1199
|
-
- **QueueManager** - zarządzanie kolejkami BullMQ i cache kolejek
|
|
1200
|
-
- **WorkerOrchestrator** - orkiestracja workerów, dynamiczne concurrency, benchmark
|
|
1201
|
-
- **JobProcessor** - przetwarzanie jobów i wykonanie handlerów
|
|
1202
|
-
- **RpcCoordinator** - zarządzanie wywołaniami RPC przez Redis Pub/Sub
|
|
1203
|
-
- **RpcJobCancellationService** - anulowanie niepodjętych jobów RPC przy timeout
|
|
1204
|
-
- **JobOptionsBuilder** - konfiguracja opcji dla jobów BullMQ
|
|
1205
|
-
- **CommandLogger** - persystencja komend do plików JSONL
|
|
1206
|
-
- **PayloadCompressionService** - automatyczna kompresja gzip
|
|
1207
|
-
- **AutoConfigOptimizer** - optymalizacja concurrency na podstawie zasobów
|
|
1208
|
-
|
|
1209
|
-
## Licencja
|
|
499
|
+
### Architektura testów
|
|
1210
500
|
|
|
1211
|
-
|
|
501
|
+
| Typ | Pliki | Opis |
|
|
502
|
+
|-----|-------|------|
|
|
503
|
+
| Unit | `*.spec.ts` per komponent | Izolowane testy każdego komponentu |
|
|
504
|
+
| Integracja | `redis-streams-transport.spec.ts` | Testy fasady z mockami |
|
|
505
|
+
| Integracja | `command-bus.spec.ts` | Testy CommandBus end-to-end z mockami |
|
|
1212
506
|
|
|
1213
|
-
|
|
1214
|
-
- Mariusz Lejkowski <m.lejkowski@polskiepolisy.pl>
|
|
507
|
+
Pokrycie: **193 testów**, zero timing-dependent `setTimeout`, `jest.useFakeTimers()` dla backoff.
|
|
1215
508
|
|
|
1216
|
-
##
|
|
509
|
+
## Changelog
|
|
1217
510
|
|
|
1218
|
-
|
|
1219
|
-
- [Issue Tracker](https://gitlab.polskiepolisy.pl/lib/pp-command-bus/issues)
|
|
1220
|
-
- [BullMQ Documentation](https://docs.bullmq.io/)
|
|
1221
|
-
- [Semantic Versioning](https://semver.org/lang/pl/)
|
|
1222
|
-
- [Conventional Commits](https://www.conventionalcommits.org/)
|
|
511
|
+
Pełny changelog dostępny w [CHANGELOG.md](CHANGELOG.md).
|