Skip to content

Commit 691917d

Browse files
authored
feat(batch): Implement SQS FIFO processor class (#1606)
* Added SQS FIFO processor and unit tests * Added docstring for pbatch processing function
1 parent cf499e1 commit 691917d

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

Diff for: packages/batch/src/SqsFifoPartialProcessor.ts

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { BatchProcessor, EventType, FailureResponse, SuccessResponse } from '.';
2+
3+
/**
4+
* Process native partial responses from SQS FIFO queues
5+
* Stops processing records when the first record fails
6+
* The remaining records are reported as failed items
7+
*/
8+
class SqsFifoPartialProcessor extends BatchProcessor {
9+
public constructor() {
10+
super(EventType.SQS);
11+
}
12+
13+
/**
14+
* Call instance's handler for each record.
15+
* When the first failed message is detected, the process is short-circuited
16+
* And the remaining messages are reported as failed items
17+
* TODO: change to synchronous execution if possible
18+
*/
19+
public async process(): Promise<(SuccessResponse | FailureResponse)[]> {
20+
this.prepare();
21+
22+
const processedRecords: (SuccessResponse | FailureResponse)[] = [];
23+
let currentIndex = 0;
24+
for (const record of this.records) {
25+
// If we have any failed messages, it means the last message failed
26+
// We should then short circuit the process and fail remaining messages
27+
if (this.failureMessages.length != 0) {
28+
return this.shortCircuitProcessing(currentIndex, processedRecords);
29+
}
30+
31+
processedRecords.push(await this.processRecord(record));
32+
currentIndex++;
33+
}
34+
35+
this.clean();
36+
37+
return processedRecords;
38+
}
39+
40+
/**
41+
* Starting from the first failure index, fail all remaining messages and append them to the result list
42+
* @param firstFailureIndex Index of first message that failed
43+
* @param result List of success and failure responses with remaining messages failed
44+
*/
45+
public shortCircuitProcessing(
46+
firstFailureIndex: number,
47+
processedRecords: (SuccessResponse | FailureResponse)[]
48+
): (SuccessResponse | FailureResponse)[] {
49+
const remainingRecords = this.records.slice(firstFailureIndex);
50+
51+
for (const record of remainingRecords) {
52+
const data = this.toBatchType(record, this.eventType);
53+
processedRecords.push(
54+
this.failureHandler(
55+
data,
56+
new Error('A previous record failed processing')
57+
)
58+
);
59+
}
60+
61+
this.clean();
62+
63+
return processedRecords;
64+
}
65+
}
66+
67+
export { SqsFifoPartialProcessor };

Diff for: packages/batch/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './BasePartialProcessor';
55
export * from './BasePartialBatchProcessor';
66
export * from './BatchProcessor';
77
export * from './processPartialResponse';
8+
export * from './SqsFifoPartialProcessor';

Diff for: packages/batch/src/processPartialResponse.ts

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import {
55
PartialItemFailureResponse,
66
} from '.';
77

8+
/**
9+
* Higher level function to handle batch event processing
10+
* @param event Lambda's original event
11+
* @param recordHandler Callable function to process each record from the batch
12+
* @param processor Batch processor to handle partial failure cases
13+
* @returns Lambda Partial Batch Response
14+
*/
815
const processPartialResponse = async (
916
event: { Records: BaseRecord[] },
1017
recordHandler: CallableFunction,
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Test SqsFifoBatchProcessor class
3+
*
4+
* @group unit/batch/class/sqsfifobatchprocessor
5+
*/
6+
7+
import { SqsFifoPartialProcessor, processPartialResponse } from '../../src';
8+
import { sqsRecordFactory } from '../../tests/helpers/factories';
9+
import {
10+
asyncSqsRecordHandler,
11+
sqsRecordHandler,
12+
} from '../../tests/helpers/handlers';
13+
14+
describe('Class: SqsFifoBatchProcessor', () => {
15+
const ENVIRONMENT_VARIABLES = process.env;
16+
17+
beforeEach(() => {
18+
jest.clearAllMocks();
19+
jest.resetModules();
20+
process.env = { ...ENVIRONMENT_VARIABLES };
21+
});
22+
23+
afterAll(() => {
24+
process.env = ENVIRONMENT_VARIABLES;
25+
});
26+
27+
describe('Synchronous SQS FIFO batch processing', () => {
28+
test('SQS FIFO Batch processor with no failures', async () => {
29+
// Prepare
30+
const firstRecord = sqsRecordFactory('success');
31+
const secondRecord = sqsRecordFactory('success');
32+
const event = { Records: [firstRecord, secondRecord] };
33+
const processor = new SqsFifoPartialProcessor();
34+
35+
// Act
36+
const result = await processPartialResponse(
37+
event,
38+
sqsRecordHandler,
39+
processor
40+
);
41+
42+
// Assess
43+
expect(result['batchItemFailures']).toStrictEqual([]);
44+
});
45+
46+
test('SQS FIFO Batch processor with failures', async () => {
47+
// Prepare
48+
const firstRecord = sqsRecordFactory('success');
49+
const secondRecord = sqsRecordFactory('fail');
50+
const thirdRecord = sqsRecordFactory('success');
51+
const event = { Records: [firstRecord, secondRecord, thirdRecord] };
52+
const processor = new SqsFifoPartialProcessor();
53+
54+
// Act
55+
const result = await processPartialResponse(
56+
event,
57+
sqsRecordHandler,
58+
processor
59+
);
60+
61+
// Assess
62+
expect(result['batchItemFailures'].length).toBe(2);
63+
expect(result['batchItemFailures'][0]['itemIdentifier']).toBe(
64+
secondRecord.messageId
65+
);
66+
expect(result['batchItemFailures'][1]['itemIdentifier']).toBe(
67+
thirdRecord.messageId
68+
);
69+
});
70+
});
71+
72+
describe('Asynchronous SQS FIFO batch processing', () => {
73+
test('SQS FIFO Batch processor with no failures', async () => {
74+
// Prepare
75+
const firstRecord = sqsRecordFactory('success');
76+
const secondRecord = sqsRecordFactory('success');
77+
const event = { Records: [firstRecord, secondRecord] };
78+
const processor = new SqsFifoPartialProcessor();
79+
80+
// Act
81+
const result = await processPartialResponse(
82+
event,
83+
asyncSqsRecordHandler,
84+
processor
85+
);
86+
87+
// Assess
88+
expect(result['batchItemFailures']).toStrictEqual([]);
89+
});
90+
91+
test('SQS FIFO Batch processor with failures', async () => {
92+
// Prepare
93+
const firstRecord = sqsRecordFactory('success');
94+
const secondRecord = sqsRecordFactory('fail');
95+
const thirdRecord = sqsRecordFactory('success');
96+
const event = { Records: [firstRecord, secondRecord, thirdRecord] };
97+
const processor = new SqsFifoPartialProcessor();
98+
99+
// Act
100+
const result = await processPartialResponse(
101+
event,
102+
asyncSqsRecordHandler,
103+
processor
104+
);
105+
106+
// Assess
107+
expect(result['batchItemFailures'].length).toBe(2);
108+
expect(result['batchItemFailures'][0]['itemIdentifier']).toBe(
109+
secondRecord.messageId
110+
);
111+
expect(result['batchItemFailures'][1]['itemIdentifier']).toBe(
112+
thirdRecord.messageId
113+
);
114+
});
115+
});
116+
});

0 commit comments

Comments
 (0)