pp-command-bus 1.3.2 → 1.5.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 +242 -72
- package/dist/command-bus/command-bus.spec.js +24 -29
- package/dist/command-bus/command-bus.spec.js.map +1 -1
- package/dist/command-bus/command.d.ts +22 -3
- package/dist/command-bus/command.js +17 -2
- package/dist/command-bus/command.js.map +1 -1
- package/dist/command-bus/index.d.ts +10 -0
- package/dist/command-bus/index.js +43 -5
- package/dist/command-bus/index.js.map +1 -1
- package/dist/command-bus/job/job-processor.d.ts +3 -1
- package/dist/command-bus/job/job-processor.js +17 -1
- package/dist/command-bus/job/job-processor.js.map +1 -1
- package/dist/command-bus/job/job-processor.spec.js +152 -12
- package/dist/command-bus/job/job-processor.spec.js.map +1 -1
- package/dist/command-bus/logging/command-logger.spec.js +13 -14
- package/dist/command-bus/logging/command-logger.spec.js.map +1 -1
- package/dist/command-bus/rpc/index.d.ts +3 -0
- package/dist/command-bus/rpc/index.js +4 -1
- package/dist/command-bus/rpc/index.js.map +1 -1
- package/dist/command-bus/rpc/payload-compression.service.d.ts +3 -4
- package/dist/command-bus/rpc/payload-compression.service.js +17 -20
- package/dist/command-bus/rpc/payload-compression.service.js.map +1 -1
- package/dist/command-bus/rpc/payload-compression.service.spec.js +20 -23
- package/dist/command-bus/rpc/payload-compression.service.spec.js.map +1 -1
- package/dist/command-bus/rpc/rpc-coordinator.d.ts +27 -1
- package/dist/command-bus/rpc/rpc-coordinator.js +113 -3
- package/dist/command-bus/rpc/rpc-coordinator.js.map +1 -1
- package/dist/command-bus/rpc/rpc-coordinator.spec.js +209 -5
- package/dist/command-bus/rpc/rpc-coordinator.spec.js.map +1 -1
- package/dist/command-bus/rpc/rpc-job-cancellation.service.d.ts +82 -0
- package/dist/command-bus/rpc/rpc-job-cancellation.service.js +180 -0
- package/dist/command-bus/rpc/rpc-job-cancellation.service.js.map +1 -0
- package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.d.ts +1 -0
- package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js +286 -0
- package/dist/command-bus/rpc/rpc-job-cancellation.service.spec.js.map +1 -0
- package/dist/command-bus/types/index.d.ts +18 -1
- package/dist/command-bus/worker/worker-benchmark.js +3 -4
- package/dist/command-bus/worker/worker-benchmark.js.map +1 -1
- package/dist/examples/auto-config.demo.js +8 -8
- package/dist/examples/auto-config.demo.js.map +1 -1
- package/dist/examples/rpc-compression.demo.js +32 -37
- package/dist/examples/rpc-compression.demo.js.map +1 -1
- package/dist/examples/rpc-resilience.demo.d.ts +8 -4
- package/dist/examples/rpc-resilience.demo.js +31 -33
- package/dist/examples/rpc-resilience.demo.js.map +1 -1
- 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/pp-command-bus-1.5.0.tgz +0 -0
- package/dist/shared/config/base-config.d.ts +11 -0
- package/dist/shared/config/base-config.js +14 -0
- package/dist/shared/config/base-config.js.map +1 -1
- package/dist/shared/config/base-config.spec.js +102 -16
- package/dist/shared/config/base-config.spec.js.map +1 -1
- package/dist/shared/redis/redis-connection-factory.d.ts +5 -0
- package/dist/shared/redis/redis-connection-factory.js +19 -5
- package/dist/shared/redis/redis-connection-factory.js.map +1 -1
- package/package.json +1 -1
- package/dist/pp-command-bus-1.3.2.tgz +0 -0
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
|
|
|
18
18
|
- ✅ **TypeScript** - pełne wsparcie typów i strict mode
|
|
19
19
|
- ✅ **Memory leak protection** - zaawansowana diagnostyka i cleanup
|
|
20
20
|
- ✅ **Command logging** - opcjonalne logowanie komend do plików JSONL
|
|
21
|
+
- ✅ **RPC Job Cancellation** - automatyczne usuwanie niepodjętych jobów RPC przy timeout
|
|
21
22
|
- ✅ **Modularna architektura** - komponenty zgodne z zasadami SOLID, DDD i SRP
|
|
22
23
|
|
|
23
24
|
## Architektura Systemu
|
|
@@ -67,6 +68,7 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
|
|
|
67
68
|
- **Shared Subscriber** - jeden subscriber dla wszystkich RPC calls (pattern matching)
|
|
68
69
|
- **Process Isolation** - UUID procesu Node.js dla izolacji odpowiedzi między procesami
|
|
69
70
|
- **Timeout Management** - automatyczne cleanup po timeout
|
|
71
|
+
- **Job Cancellation** - usuwanie niepodjętych jobów przy timeout (optymalizacja zasobów)
|
|
70
72
|
- **Multiplexing** - routing odpowiedzi do odpowiednich promises
|
|
71
73
|
- **Kanały**: `rpc:response:{processId}:{correlationId}`
|
|
72
74
|
|
|
@@ -83,11 +85,13 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
|
|
|
83
85
|
- **Odpowiedzialność**: Wykonywanie handlerów komend i obsługa odpowiedzi RPC
|
|
84
86
|
- **Flow przetwarzania**:
|
|
85
87
|
1. Dekompresja payloadu (jeśli skompresowany)
|
|
86
|
-
2.
|
|
87
|
-
3.
|
|
88
|
-
4.
|
|
89
|
-
5.
|
|
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)
|
|
90
93
|
- **Kompresja odpowiedzi**: automatyczna kompresja przez PayloadCompressionService
|
|
94
|
+
- **RPC Cancellation**: pomijanie jobów oznaczonych jako anulowane (timeout)
|
|
91
95
|
|
|
92
96
|
#### 5. **QueueManager** (zarządzanie kolejkami)
|
|
93
97
|
- **Odpowiedzialność**: Cache kolejek BullMQ dla optymalizacji pamięci
|
|
@@ -106,13 +110,24 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
|
|
|
106
110
|
- `decompress(data, compressed)` - generyczna dekompresja
|
|
107
111
|
- **Współdzielony serwis**: jedna instancja dla całego CommandBus
|
|
108
112
|
|
|
109
|
-
#### 7. **
|
|
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)
|
|
110
125
|
- **Odpowiedzialność**: Persystencja komend do plików JSONL
|
|
111
126
|
- **Format**: `{timestamp}.jsonl` - rotacja co godzinę
|
|
112
127
|
- **Zawartość**: pełny payload komendy z metadanymi
|
|
113
128
|
- **Aktywacja**: przez ENV `COMMAND_BUS_LOG=./command-logs`
|
|
114
129
|
|
|
115
|
-
####
|
|
130
|
+
#### 9. **AutoConfigOptimizer** (auto-optymalizacja)
|
|
116
131
|
- **Odpowiedzialność**: Obliczanie optymalnego concurrency na podstawie zasobów systemowych
|
|
117
132
|
- **Heurystyka**:
|
|
118
133
|
- I/O-heavy workload (Redis/BullMQ)
|
|
@@ -120,7 +135,7 @@ Distributed Command Bus library supporting RPC and job queuing with BullMQ for R
|
|
|
120
135
|
- Zakładane 512MB RAM per worker
|
|
121
136
|
- **Aktywacja**: domyślnie włączone (ENV `COMMAND_BUS_AUTO_OPTIMIZE=false` wyłącza)
|
|
122
137
|
|
|
123
|
-
####
|
|
138
|
+
#### 10. **RedisConnectionFactory** (fabryka połączeń Redis)
|
|
124
139
|
- **Odpowiedzialność**: Tworzenie połączeń Redis z wbudowaną obsługą błędów i eventów
|
|
125
140
|
- **Zgodność z SRP**: CommandBus nie zarządza bezpośrednio połączeniami
|
|
126
141
|
- **Kluczowe funkcje**:
|
|
@@ -256,12 +271,9 @@ const commandBus = new CommandBus(config);
|
|
|
256
271
|
### 2. Definiowanie komend
|
|
257
272
|
|
|
258
273
|
```typescript
|
|
259
|
-
class CreateUserCommand extends Command {
|
|
260
|
-
constructor(
|
|
261
|
-
|
|
262
|
-
public readonly name: string,
|
|
263
|
-
) {
|
|
264
|
-
super();
|
|
274
|
+
class CreateUserCommand extends Command<{ email: string; name: string }> {
|
|
275
|
+
constructor(payload: { email: string; name: string }) {
|
|
276
|
+
super(payload);
|
|
265
277
|
}
|
|
266
278
|
}
|
|
267
279
|
```
|
|
@@ -270,10 +282,11 @@ class CreateUserCommand extends Command {
|
|
|
270
282
|
|
|
271
283
|
```typescript
|
|
272
284
|
commandBus.handle(CreateUserCommand, async (command) => {
|
|
273
|
-
|
|
285
|
+
const { name, email } = command.__payload;
|
|
286
|
+
console.log(`Creating user: ${name} (${email})`);
|
|
274
287
|
|
|
275
288
|
// Twoja logika biznesowa
|
|
276
|
-
const user = await createUser(
|
|
289
|
+
const user = await createUser(email, name);
|
|
277
290
|
|
|
278
291
|
// Zwróć wynik (opcjonalnie)
|
|
279
292
|
return { userId: user.id };
|
|
@@ -283,7 +296,10 @@ commandBus.handle(CreateUserCommand, async (command) => {
|
|
|
283
296
|
### 4. Wysyłanie komend (Fire-and-forget)
|
|
284
297
|
|
|
285
298
|
```typescript
|
|
286
|
-
const command = new CreateUserCommand(
|
|
299
|
+
const command = new CreateUserCommand({
|
|
300
|
+
email: 'jan.kowalski@example.com',
|
|
301
|
+
name: 'Jan Kowalski'
|
|
302
|
+
});
|
|
287
303
|
|
|
288
304
|
// Wyślij komendę bez oczekiwania na wynik
|
|
289
305
|
await commandBus.dispatch(command);
|
|
@@ -307,6 +323,8 @@ Biblioteka wspiera konfigurację poprzez zmienne środowiskowe z prefiksem `COMM
|
|
|
307
323
|
| Zmienna | Typ | Wartość Domyślna | Opis |
|
|
308
324
|
|---------|-----|------------------|------|
|
|
309
325
|
| `REDIS_URL` | string | `redis://localhost:6379` | URL połączenia Redis/DragonflyDB (wspiera username, password, db) |
|
|
326
|
+
| `REDIS_RETRY_DELAY` | number | `5000` | Opóźnienie między próbami reconnect do Redis w milisekundach |
|
|
327
|
+
| `REDIS_MAX_RETRIES` | number | `0` | Maksymalna liczba prób reconnect (0 = nieskończoność) |
|
|
310
328
|
| `LOG_LEVEL` | enum | `log` | Poziom logowania: `debug`, `log`, `warn`, `error` |
|
|
311
329
|
| `COMMAND_BUS_CONCURRENCY` | number | `1` (lub auto) | Liczba równoległych workerów do przetwarzania komend |
|
|
312
330
|
| `COMMAND_BUS_MAX_ATTEMPTS` | number | `1` | Maksymalna liczba prób przetworzenia zadania |
|
|
@@ -332,6 +350,10 @@ Dla kompatybilności wstecznej obsługiwane są również prefiksy `EVENT_BUS_*`
|
|
|
332
350
|
# Połączenie Redis
|
|
333
351
|
REDIS_URL=redis://username:password@localhost:6379/0
|
|
334
352
|
|
|
353
|
+
# Strategia reconnect Redis (stałe opóźnienie)
|
|
354
|
+
REDIS_RETRY_DELAY=5000 # 5 sekund między próbami (domyślnie)
|
|
355
|
+
REDIS_MAX_RETRIES=0 # 0 = nieskończoność (domyślnie)
|
|
356
|
+
|
|
335
357
|
# Poziom logowania
|
|
336
358
|
LOG_LEVEL=log # debug | log | warn | error
|
|
337
359
|
|
|
@@ -528,7 +550,10 @@ Wysyła komendę do kolejki bez oczekiwania na wynik (fire-and-forget).
|
|
|
528
550
|
4. Natychmiastowy return
|
|
529
551
|
|
|
530
552
|
```typescript
|
|
531
|
-
await commandBus.dispatch(new CreateUserCommand(
|
|
553
|
+
await commandBus.dispatch(new CreateUserCommand({
|
|
554
|
+
email: 'email@example.com',
|
|
555
|
+
name: 'Jan Kowalski'
|
|
556
|
+
}));
|
|
532
557
|
```
|
|
533
558
|
|
|
534
559
|
#### `call<T>(command: Command, timeout?: number): Promise<T>`
|
|
@@ -561,7 +586,8 @@ Rejestruje handler dla określonej klasy komendy. Tylko jeden handler per typ ko
|
|
|
561
586
|
|
|
562
587
|
```typescript
|
|
563
588
|
commandBus.handle(CreateUserCommand, async (command) => {
|
|
564
|
-
const
|
|
589
|
+
const { email, name } = command.__payload;
|
|
590
|
+
const user = await createUser(email, name);
|
|
565
591
|
return { userId: user.id };
|
|
566
592
|
});
|
|
567
593
|
```
|
|
@@ -582,23 +608,105 @@ await commandBus.close();
|
|
|
582
608
|
|
|
583
609
|
### Command
|
|
584
610
|
|
|
585
|
-
Klasa bazowa dla wszystkich komend. Każda komenda dziedziczy po `Command
|
|
611
|
+
Klasa bazowa dla wszystkich komend. Każda komenda dziedziczy po `Command<T>` gdzie `T` to typ danych biznesowych:
|
|
586
612
|
|
|
587
613
|
```typescript
|
|
588
|
-
class MyCommand extends Command {
|
|
589
|
-
constructor(
|
|
590
|
-
super();
|
|
614
|
+
class MyCommand extends Command<{ data: string }> {
|
|
615
|
+
constructor(payload: { data: string }) {
|
|
616
|
+
super(payload);
|
|
591
617
|
}
|
|
592
618
|
}
|
|
619
|
+
|
|
620
|
+
// Uzycie
|
|
621
|
+
const cmd = new MyCommand({ data: 'hello' });
|
|
622
|
+
console.log(cmd.__payload.data); // 'hello'
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
#### Struktura Komendy
|
|
626
|
+
|
|
627
|
+
Kazda komenda po serializacji ma nastepujaca strukture:
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
{
|
|
631
|
+
"__name": "MyCommand", // Nazwa klasy komendy
|
|
632
|
+
"__id": "550e8400-e29b-41d4-a716-446655440000", // UUID komendy
|
|
633
|
+
"__time": 1706347200000, // Timestamp utworzenia (ms)
|
|
634
|
+
"__payload": { // Dane biznesowe komendy
|
|
635
|
+
"data": "hello"
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
#### Opis Pol Komendy
|
|
641
|
+
|
|
642
|
+
| Pole | Typ | Opis |
|
|
643
|
+
|------|-----|------|
|
|
644
|
+
| `__name` | `string` | Nazwa klasy komendy (automatycznie ustawiana z `constructor.name`). Uzywana do routowania do odpowiedniego handlera. |
|
|
645
|
+
| `__id` | `string` | Unikalny identyfikator komendy (UUID v4). Generowany automatycznie przy tworzeniu komendy. Uzywany do korelacji RPC i logowania. |
|
|
646
|
+
| `__time` | `number` | Timestamp utworzenia komendy w milisekundach (`Date.now()`). Uzywany do audytu i debugowania. |
|
|
647
|
+
| `__payload` | `T` | Dane biznesowe komendy. Wszystkie dane specyficzne dla komendy powinny byc przechowywane tutaj. Typ `T` jest generyczny i definiowany przy tworzeniu klasy komendy. |
|
|
648
|
+
|
|
649
|
+
#### Przyklad Definicji Komendy
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
// Komenda z prostym payloadem
|
|
653
|
+
class CreateUserCommand extends Command<{ name: string; email: string }> {
|
|
654
|
+
constructor(payload: { name: string; email: string }) {
|
|
655
|
+
super(payload);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Komenda ze zlozonym payloadem
|
|
660
|
+
class ProcessOrderCommand extends Command<{
|
|
661
|
+
orderId: string;
|
|
662
|
+
items: Array<{ productId: string; quantity: number }>;
|
|
663
|
+
shippingAddress: {
|
|
664
|
+
street: string;
|
|
665
|
+
city: string;
|
|
666
|
+
postalCode: string;
|
|
667
|
+
};
|
|
668
|
+
}> {
|
|
669
|
+
constructor(payload: {
|
|
670
|
+
orderId: string;
|
|
671
|
+
items: Array<{ productId: string; quantity: number }>;
|
|
672
|
+
shippingAddress: { street: string; city: string; postalCode: string };
|
|
673
|
+
}) {
|
|
674
|
+
super(payload);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Uzycie
|
|
679
|
+
const createUser = new CreateUserCommand({
|
|
680
|
+
name: 'Jan Kowalski',
|
|
681
|
+
email: 'jan@example.com'
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
console.log(createUser.__name); // 'CreateUserCommand'
|
|
685
|
+
console.log(createUser.__id); // '550e8400-e29b-41d4-...'
|
|
686
|
+
console.log(createUser.__time); // 1706347200000
|
|
687
|
+
console.log(createUser.__payload.name); // 'Jan Kowalski'
|
|
688
|
+
console.log(createUser.__payload.email); // 'jan@example.com'
|
|
593
689
|
```
|
|
594
690
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
691
|
+
#### Dostep do Danych w Handlerze
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
commandBus.handle(CreateUserCommand, async (command) => {
|
|
695
|
+
// Dostep do danych biznesowych przez __payload
|
|
696
|
+
const { name, email } = command.__payload;
|
|
697
|
+
|
|
698
|
+
// Dostep do metadanych komendy
|
|
699
|
+
console.log(`Processing command ${command.__id} (${command.__name})`);
|
|
700
|
+
console.log(`Created at: ${new Date(command.__time).toISOString()}`);
|
|
701
|
+
|
|
702
|
+
// Logika biznesowa
|
|
703
|
+
const user = await createUser(name, email);
|
|
704
|
+
return { userId: user.id };
|
|
705
|
+
});
|
|
706
|
+
```
|
|
599
707
|
|
|
600
708
|
**Metody statyczne**:
|
|
601
|
-
- `reconstructDates(obj)` - rekonstrukcja
|
|
709
|
+
- `reconstructDates(obj)` - rekonstrukcja obiektow Date z serializowanych danych (uzywane wewnetrznie przez JobProcessor)
|
|
602
710
|
|
|
603
711
|
### CommandBusConfig
|
|
604
712
|
|
|
@@ -621,19 +729,19 @@ interface CommandBusConfigOptions {
|
|
|
621
729
|
|
|
622
730
|
## Przykłady Użycia
|
|
623
731
|
|
|
624
|
-
### Podstawowy
|
|
732
|
+
### Podstawowy przyklad z RPC
|
|
625
733
|
|
|
626
734
|
```typescript
|
|
627
735
|
import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
|
|
628
736
|
|
|
629
|
-
// Definicja komendy
|
|
630
|
-
class CalculateCommand extends Command
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
) {
|
|
636
|
-
super();
|
|
737
|
+
// Definicja komendy z payloadem
|
|
738
|
+
class CalculateCommand extends Command<{
|
|
739
|
+
a: number;
|
|
740
|
+
b: number;
|
|
741
|
+
operation: 'add' | 'multiply';
|
|
742
|
+
}> {
|
|
743
|
+
constructor(payload: { a: number; b: number; operation: 'add' | 'multiply' }) {
|
|
744
|
+
super(payload);
|
|
637
745
|
}
|
|
638
746
|
}
|
|
639
747
|
|
|
@@ -645,64 +753,69 @@ const commandBus = new CommandBus(config);
|
|
|
645
753
|
|
|
646
754
|
// Rejestracja handlera
|
|
647
755
|
commandBus.handle(CalculateCommand, async (command) => {
|
|
648
|
-
|
|
756
|
+
const { a, b, operation } = command.__payload;
|
|
757
|
+
console.log(`Calculating: ${a} ${operation} ${b}`);
|
|
649
758
|
|
|
650
|
-
switch (
|
|
759
|
+
switch (operation) {
|
|
651
760
|
case 'add':
|
|
652
|
-
return
|
|
761
|
+
return a + b;
|
|
653
762
|
case 'multiply':
|
|
654
|
-
return
|
|
763
|
+
return a * b;
|
|
655
764
|
}
|
|
656
765
|
});
|
|
657
766
|
|
|
658
767
|
// Fire-and-forget
|
|
659
|
-
await commandBus.dispatch(new CalculateCommand(5, 3, 'add'));
|
|
768
|
+
await commandBus.dispatch(new CalculateCommand({ a: 5, b: 3, operation: 'add' }));
|
|
660
769
|
|
|
661
770
|
// RPC - czekamy na wynik
|
|
662
771
|
const result = await commandBus.call<number>(
|
|
663
|
-
new CalculateCommand(5, 3, 'multiply'),
|
|
772
|
+
new CalculateCommand({ a: 5, b: 3, operation: 'multiply' }),
|
|
664
773
|
5000 // timeout 5s
|
|
665
774
|
);
|
|
666
775
|
console.log(`Result: ${result}`); // Result: 15
|
|
667
776
|
```
|
|
668
777
|
|
|
669
|
-
###
|
|
778
|
+
### Rownolegle wywolania RPC
|
|
670
779
|
|
|
671
780
|
```typescript
|
|
672
|
-
// Wiele
|
|
781
|
+
// Wiele rownoległych RPC calls
|
|
673
782
|
const [result1, result2, result3] = await Promise.all([
|
|
674
|
-
commandBus.call<number>(new CalculateCommand(10, 5, 'add')),
|
|
675
|
-
commandBus.call<UserInfo>(new GetUserInfoCommand('user-1')),
|
|
676
|
-
commandBus.call<ValidationResult>(new ValidateUserCommand('jan@example.com', 30)),
|
|
783
|
+
commandBus.call<number>(new CalculateCommand({ a: 10, b: 5, operation: 'add' })),
|
|
784
|
+
commandBus.call<UserInfo>(new GetUserInfoCommand({ userId: 'user-1' })),
|
|
785
|
+
commandBus.call<ValidationResult>(new ValidateUserCommand({ email: 'jan@example.com', age: 30 })),
|
|
677
786
|
]);
|
|
678
787
|
|
|
679
788
|
console.log('All results:', { result1, result2, result3 });
|
|
680
789
|
```
|
|
681
790
|
|
|
682
|
-
###
|
|
791
|
+
### Obsluga bledow
|
|
683
792
|
|
|
684
793
|
```typescript
|
|
685
794
|
commandBus.handle(CreateUserCommand, async (command) => {
|
|
686
795
|
try {
|
|
796
|
+
const { email, name } = command.__payload;
|
|
797
|
+
|
|
687
798
|
// Walidacja
|
|
688
|
-
if (!
|
|
799
|
+
if (!email.includes('@')) {
|
|
689
800
|
throw new Error('Invalid email format');
|
|
690
801
|
}
|
|
691
802
|
|
|
692
|
-
const user = await createUser(
|
|
803
|
+
const user = await createUser(email, name);
|
|
693
804
|
return { userId: user.id };
|
|
694
805
|
} catch (error) {
|
|
695
|
-
// Loguj
|
|
806
|
+
// Loguj blad
|
|
696
807
|
console.error('Failed to create user:', error);
|
|
697
808
|
|
|
698
|
-
//
|
|
809
|
+
// Rzuc blad - BullMQ sprobuje ponownie (maxAttempts)
|
|
699
810
|
throw error;
|
|
700
811
|
}
|
|
701
812
|
});
|
|
702
813
|
|
|
703
|
-
//
|
|
814
|
+
// Obsluga bledow w RPC
|
|
704
815
|
try {
|
|
705
|
-
const result = await commandBus.call(
|
|
816
|
+
const result = await commandBus.call(
|
|
817
|
+
new CreateUserCommand({ email: 'invalid-email', name: 'Jan' })
|
|
818
|
+
);
|
|
706
819
|
} catch (error) {
|
|
707
820
|
console.error('RPC failed:', error.message);
|
|
708
821
|
}
|
|
@@ -809,6 +922,45 @@ const config = new CommandBusConfig({
|
|
|
809
922
|
- Debugging produkcyjnych problemów
|
|
810
923
|
- Analiza przepływu komend
|
|
811
924
|
|
|
925
|
+
### 6. RPC Job Cancellation
|
|
926
|
+
|
|
927
|
+
Automatyczne usuwanie niepodjętych jobów RPC przy timeout:
|
|
928
|
+
|
|
929
|
+
```
|
|
930
|
+
RPC Timeout Flow:
|
|
931
|
+
|
|
932
|
+
User Code RpcCoordinator Redis JobProcessor
|
|
933
|
+
│ │ │ │
|
|
934
|
+
│── call(cmd) ─────▶│ │ │
|
|
935
|
+
│ │── registerCall ──▶│ │
|
|
936
|
+
│ │── queue.add ─────▶│ │
|
|
937
|
+
│ │ │ │
|
|
938
|
+
│ [timeout] │ │ │
|
|
939
|
+
│ │ │ │
|
|
940
|
+
│ │── markCancelled ─▶│ SET rpc:cancelled:xxx
|
|
941
|
+
│ │── tryRemoveJob ──▶│ (próba usunięcia)
|
|
942
|
+
│◀── Error ─────────│ │ │
|
|
943
|
+
│ │ │ │
|
|
944
|
+
│ │ │ [job picked up] │
|
|
945
|
+
│ │ │──────────────────▶│
|
|
946
|
+
│ │ │ │── isCancelled?
|
|
947
|
+
│ │ │◀──────────────────│ → true
|
|
948
|
+
│ │ │ │── SKIP handler
|
|
949
|
+
│ │ │ │── clearCancellation
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
**Kluczowe cechy**:
|
|
953
|
+
- **Dwufazowe anulowanie**: Flaga Redis + próba usunięcia joba z kolejki
|
|
954
|
+
- **Graceful degradation**: Błędy Redis nie blokują przetwarzania
|
|
955
|
+
- **TTL 24h**: Automatyczne wygaśnięcie kluczy cancellation
|
|
956
|
+
- **Aktywne czyszczenie**: Klucze usuwane po przetworzeniu lub usunięciu joba
|
|
957
|
+
- **Kompatybilność wsteczna**: Funkcjonalność jest opcjonalna
|
|
958
|
+
|
|
959
|
+
**Korzyści**:
|
|
960
|
+
- Oszczędność zasobów - nieprzetworzone joby nie obciążają workerów
|
|
961
|
+
- Brak efektów ubocznych - handler nie wykonuje się dla timeout'owanych RPC
|
|
962
|
+
- Lepsza diagnostyka - logi pokazują pominięte joby
|
|
963
|
+
|
|
812
964
|
## Best Practices
|
|
813
965
|
|
|
814
966
|
### 1. Używaj TypeScript
|
|
@@ -826,18 +978,20 @@ const result = await commandBus.call<UserCreatedResult>(command);
|
|
|
826
978
|
|
|
827
979
|
### 2. Idempotentne handlery
|
|
828
980
|
|
|
829
|
-
Handlery powinny
|
|
981
|
+
Handlery powinny byc idempotentne (wielokrotne wykonanie = ten sam rezultat):
|
|
830
982
|
|
|
831
983
|
```typescript
|
|
832
984
|
commandBus.handle(CreateUserCommand, async (command) => {
|
|
833
|
-
|
|
834
|
-
|
|
985
|
+
const { email, name } = command.__payload;
|
|
986
|
+
|
|
987
|
+
// Sprawdz czy uzytkownik juz istnieje
|
|
988
|
+
const existing = await findUserByEmail(email);
|
|
835
989
|
if (existing) {
|
|
836
|
-
return { userId: existing.id }; //
|
|
990
|
+
return { userId: existing.id }; // Zwroc istniejacego
|
|
837
991
|
}
|
|
838
992
|
|
|
839
|
-
//
|
|
840
|
-
const user = await createUser(
|
|
993
|
+
// Utworz nowego
|
|
994
|
+
const user = await createUser(email, name);
|
|
841
995
|
return { userId: user.id };
|
|
842
996
|
});
|
|
843
997
|
```
|
|
@@ -857,17 +1011,19 @@ const result2 = await commandBus.call(longRunningCommand, 60000); // 60s dla dł
|
|
|
857
1011
|
|
|
858
1012
|
```typescript
|
|
859
1013
|
commandBus.handle(CreateUserCommand, async (command) => {
|
|
860
|
-
|
|
861
|
-
|
|
1014
|
+
const { email, name } = command.__payload;
|
|
1015
|
+
|
|
1016
|
+
// Walidacja na poczatku
|
|
1017
|
+
if (!email || !email.includes('@')) {
|
|
862
1018
|
throw new Error('Invalid email');
|
|
863
1019
|
}
|
|
864
1020
|
|
|
865
|
-
if (!
|
|
1021
|
+
if (!name || name.length < 2) {
|
|
866
1022
|
throw new Error('Invalid name');
|
|
867
1023
|
}
|
|
868
1024
|
|
|
869
1025
|
// Logika biznesowa
|
|
870
|
-
return await createUser(
|
|
1026
|
+
return await createUser(email, name);
|
|
871
1027
|
});
|
|
872
1028
|
```
|
|
873
1029
|
|
|
@@ -875,14 +1031,16 @@ commandBus.handle(CreateUserCommand, async (command) => {
|
|
|
875
1031
|
|
|
876
1032
|
```typescript
|
|
877
1033
|
commandBus.handle(CreateUserCommand, async (command) => {
|
|
1034
|
+
const { email, name } = command.__payload;
|
|
1035
|
+
|
|
878
1036
|
console.log('Processing CreateUserCommand', {
|
|
879
1037
|
commandId: command.__id,
|
|
880
1038
|
timestamp: command.__time,
|
|
881
|
-
email:
|
|
1039
|
+
email: email,
|
|
882
1040
|
});
|
|
883
1041
|
|
|
884
1042
|
// Logika biznesowa
|
|
885
|
-
const user = await createUser(
|
|
1043
|
+
const user = await createUser(email, name);
|
|
886
1044
|
|
|
887
1045
|
console.log('User created successfully', {
|
|
888
1046
|
commandId: command.__id,
|
|
@@ -900,6 +1058,13 @@ commandBus.handle(CreateUserCommand, async (command) => {
|
|
|
900
1058
|
```typescript
|
|
901
1059
|
import { CommandBus, CommandBusConfig, Command } from 'pp-command-bus';
|
|
902
1060
|
|
|
1061
|
+
// Definicja komendy testowej
|
|
1062
|
+
class CreateUserCommand extends Command<{ email: string; name: string }> {
|
|
1063
|
+
constructor(payload: { email: string; name: string }) {
|
|
1064
|
+
super(payload);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
903
1068
|
describe('User Commands', () => {
|
|
904
1069
|
let commandBus: CommandBus;
|
|
905
1070
|
|
|
@@ -907,7 +1072,7 @@ describe('User Commands', () => {
|
|
|
907
1072
|
const config = new CommandBusConfig({
|
|
908
1073
|
redisUrl: 'redis://localhost:6379',
|
|
909
1074
|
logger: console,
|
|
910
|
-
logLevel: 'error', // Tylko
|
|
1075
|
+
logLevel: 'error', // Tylko bledy w testach
|
|
911
1076
|
});
|
|
912
1077
|
|
|
913
1078
|
commandBus = new CommandBus(config);
|
|
@@ -923,7 +1088,10 @@ describe('User Commands', () => {
|
|
|
923
1088
|
});
|
|
924
1089
|
|
|
925
1090
|
it('should create user', async () => {
|
|
926
|
-
const command = new CreateUserCommand(
|
|
1091
|
+
const command = new CreateUserCommand({
|
|
1092
|
+
email: 'test@example.com',
|
|
1093
|
+
name: 'Test User'
|
|
1094
|
+
});
|
|
927
1095
|
const result = await commandBus.call<{ userId: string }>(command, 5000);
|
|
928
1096
|
|
|
929
1097
|
expect(result.userId).toBeDefined();
|
|
@@ -932,9 +1100,9 @@ describe('User Commands', () => {
|
|
|
932
1100
|
|
|
933
1101
|
it('should handle multiple commands in parallel', async () => {
|
|
934
1102
|
const commands = [
|
|
935
|
-
new CreateUserCommand('user1@example.com', 'User 1'),
|
|
936
|
-
new CreateUserCommand('user2@example.com', 'User 2'),
|
|
937
|
-
new CreateUserCommand('user3@example.com', 'User 3'),
|
|
1103
|
+
new CreateUserCommand({ email: 'user1@example.com', name: 'User 1' }),
|
|
1104
|
+
new CreateUserCommand({ email: 'user2@example.com', name: 'User 2' }),
|
|
1105
|
+
new CreateUserCommand({ email: 'user3@example.com', name: 'User 3' }),
|
|
938
1106
|
];
|
|
939
1107
|
|
|
940
1108
|
const results = await Promise.all(
|
|
@@ -1037,7 +1205,8 @@ src/
|
|
|
1037
1205
|
│ │ └── queue-manager.ts
|
|
1038
1206
|
│ ├── rpc/ # RPC coordination
|
|
1039
1207
|
│ │ ├── rpc-coordinator.ts
|
|
1040
|
-
│ │
|
|
1208
|
+
│ │ ├── payload-compression.service.ts
|
|
1209
|
+
│ │ └── rpc-job-cancellation.service.ts # Anulowanie jobów RPC przy timeout
|
|
1041
1210
|
│ ├── worker/ # Worker orchestration
|
|
1042
1211
|
│ │ ├── worker-orchestrator.ts
|
|
1043
1212
|
│ │ ├── worker-benchmark.ts
|
|
@@ -1139,6 +1308,7 @@ Szczegółowa dokumentacja architektury systemu, wzorców projektowych i zasad S
|
|
|
1139
1308
|
- **WorkerOrchestrator** - orkiestracja workerów, dynamiczne concurrency, benchmark
|
|
1140
1309
|
- **JobProcessor** - przetwarzanie jobów i wykonanie handlerów
|
|
1141
1310
|
- **RpcCoordinator** - zarządzanie wywołaniami RPC przez Redis Pub/Sub
|
|
1311
|
+
- **RpcJobCancellationService** - anulowanie niepodjętych jobów RPC przy timeout
|
|
1142
1312
|
- **JobOptionsBuilder** - konfiguracja opcji dla jobów BullMQ
|
|
1143
1313
|
- **CommandLogger** - persystencja komend do plików JSONL
|
|
1144
1314
|
- **PayloadCompressionService** - automatyczna kompresja gzip
|