@@ -6,6 +6,7 @@ import type {
6
6
ConsumerMessageMetadataType ,
7
7
DomainEventEmitter ,
8
8
} from '@message-queue-toolkit/core'
9
+ import { PromisePool } from '@supercharge/promise-pool'
9
10
import { uuidv7 } from 'uuidv7'
10
11
11
12
/**
@@ -28,6 +29,51 @@ export type OutboxEntry<SupportedEvent extends CommonEventDefinition> = {
28
29
retryCount : number
29
30
}
30
31
32
+ export interface OutboxAccumulator < SupportedEvents extends CommonEventDefinition [ ] > {
33
+ add ( outboxEntry : OutboxEntry < SupportedEvents [ number ] > ) : Promise < void >
34
+
35
+ addFailure ( outboxEntry : OutboxEntry < SupportedEvents [ number ] > ) : Promise < void >
36
+
37
+ getEntries ( ) : Promise < OutboxEntry < SupportedEvents [ number ] > [ ] >
38
+
39
+ getFailedEntries ( ) : Promise < OutboxEntry < SupportedEvents [ number ] > [ ] >
40
+
41
+ clear ( ) : Promise < void >
42
+ }
43
+
44
+ export class InMemoryOutboxAccumulator < SupportedEvents extends CommonEventDefinition [ ] >
45
+ implements OutboxAccumulator < SupportedEvents >
46
+ {
47
+ private entries : OutboxEntry < SupportedEvents [ number ] > [ ] = [ ]
48
+ private failedEntries : OutboxEntry < SupportedEvents [ number ] > [ ] = [ ]
49
+
50
+ public add ( outboxEntry : OutboxEntry < SupportedEvents [ number ] > ) {
51
+ this . entries = [ ...this . entries , outboxEntry ]
52
+
53
+ return Promise . resolve ( )
54
+ }
55
+
56
+ public addFailure ( outboxEntry : OutboxEntry < SupportedEvents [ number ] > ) {
57
+ this . failedEntries = [ ...this . failedEntries , outboxEntry ]
58
+
59
+ return Promise . resolve ( )
60
+ }
61
+
62
+ getEntries ( ) : Promise < OutboxEntry < SupportedEvents [ number ] > [ ] > {
63
+ return Promise . resolve ( this . entries )
64
+ }
65
+
66
+ getFailedEntries ( ) : Promise < OutboxEntry < SupportedEvents [ number ] > [ ] > {
67
+ return Promise . resolve ( this . failedEntries )
68
+ }
69
+
70
+ public clear ( ) : Promise < void > {
71
+ this . entries = [ ]
72
+ this . failedEntries = [ ]
73
+ return Promise . resolve ( )
74
+ }
75
+ }
76
+
31
77
/**
32
78
* Takes care of persisting and retrieving outbox entries.
33
79
*
@@ -41,6 +87,8 @@ export interface OutboxStorage<SupportedEvents extends CommonEventDefinition[]>
41
87
outboxEntry : OutboxEntry < SupportedEvents [ number ] > ,
42
88
) : Promise < OutboxEntry < SupportedEvents [ number ] > >
43
89
90
+ flush ( outboxAccumulator : OutboxAccumulator < SupportedEvents > ) : Promise < void >
91
+
44
92
update (
45
93
outboxEntry : OutboxEntry < SupportedEvents [ number ] > ,
46
94
) : Promise < OutboxEntry < SupportedEvents [ number ] > >
@@ -61,35 +109,29 @@ export interface OutboxStorage<SupportedEvents extends CommonEventDefinition[]>
61
109
export class OutboxProcessor < SupportedEvents extends CommonEventDefinition [ ] > {
62
110
constructor (
63
111
private readonly outboxStorage : OutboxStorage < SupportedEvents > ,
112
+ private readonly outboxAccumulator : OutboxAccumulator < SupportedEvents > ,
64
113
private readonly eventEmitter : DomainEventEmitter < SupportedEvents > ,
65
114
private readonly maxRetryCount : number ,
115
+ private readonly emitBatchSize : number ,
66
116
) { }
67
117
68
118
public async processOutboxEntries ( context : JobExecutionContext ) {
69
119
const entries = await this . outboxStorage . getEntries ( this . maxRetryCount )
70
120
71
- for ( const entry of entries ) {
72
- try {
73
- const updatedEntry = await this . outboxStorage . update ( {
74
- ...entry ,
75
- updated : new Date ( ) ,
76
- status : 'ACKED' ,
77
- } )
78
-
79
- await this . eventEmitter . emit ( entry . event , entry . data , entry . precedingMessageMetadata )
80
-
81
- await this . outboxStorage . update ( { ...updatedEntry , updated : new Date ( ) , status : 'SUCCESS' } )
82
- } catch ( e ) {
83
- context . logger . error ( { error : e } , 'Failed to process outbox entry.' )
84
-
85
- await this . outboxStorage . update ( {
86
- ...entry ,
87
- updated : new Date ( ) ,
88
- status : 'FAILED' ,
89
- retryCount : entry . retryCount + 1 ,
90
- } )
91
- }
92
- }
121
+ await PromisePool . for ( entries )
122
+ . withConcurrency ( this . emitBatchSize )
123
+ . process ( async ( entry ) => {
124
+ try {
125
+ await this . eventEmitter . emit ( entry . event , entry . data , entry . precedingMessageMetadata )
126
+ await this . outboxAccumulator . add ( entry )
127
+ } catch ( e ) {
128
+ context . logger . error ( { error : e } , 'Failed to process outbox entry.' )
129
+
130
+ await this . outboxAccumulator . addFailure ( entry )
131
+ }
132
+ } )
133
+
134
+ await this . outboxStorage . flush ( this . outboxAccumulator )
93
135
}
94
136
}
95
137
@@ -107,9 +149,11 @@ export class OutboxPeriodicJob<
107
149
108
150
constructor (
109
151
outboxStorage : OutboxStorage < SupportedEvents > ,
152
+ outboxAccumulator : OutboxAccumulator < SupportedEvents > ,
110
153
eventEmitter : DomainEventEmitter < SupportedEvents > ,
111
154
dependencies : PeriodicJobDependencies ,
112
155
maxRetryCount : number ,
156
+ emitBatchSize : number ,
113
157
intervalInMs : number ,
114
158
) {
115
159
super (
@@ -133,8 +177,10 @@ export class OutboxPeriodicJob<
133
177
134
178
this . outboxProcessor = new OutboxProcessor < SupportedEvents > (
135
179
outboxStorage ,
180
+ outboxAccumulator ,
136
181
eventEmitter ,
137
182
maxRetryCount ,
183
+ emitBatchSize ,
138
184
)
139
185
}
140
186
0 commit comments