Skip to content

Commit 46d16f1

Browse files
authored
Fix: [AEA-5173] - handle large batches (#1721)
## Summary - Routine Change ### Details - handle updates of more than 100 items
1 parent 69d372d commit 46d16f1

3 files changed

Lines changed: 140 additions & 15 deletions

File tree

packages/updatePrescriptionStatus/src/updatePrescriptionStatus.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,17 +191,17 @@ export function validateEntries(requestEntries: Array<BundleEntry>, responseEntr
191191
let valid = true
192192
for (const entry of requestEntries) {
193193
const fullUrl = entry.fullUrl!
194-
logger.info("Validating entry.", {entry: entry, id: entry.fullUrl})
194+
logger.debug("Validating entry.", {entry: entry, id: entry.fullUrl})
195195

196196
const validationOutcome = validateEntry(entry)
197197

198198
let responseEntry: BundleEntry
199199
if (validationOutcome.valid) {
200-
logger.info("Entry validated successfully.", {entry: entry, id: entry.fullUrl})
200+
logger.debug("Entry validated successfully.", {entry: entry, id: entry.fullUrl})
201201
responseEntry = accepted(fullUrl)
202202
} else {
203203
const errorMessage = validationOutcome.issues!
204-
logger.info(`Entry failed validation. ${errorMessage}`, {entry: entry, id: entry.fullUrl})
204+
logger.warn(`Entry failed validation. ${errorMessage}`, {entry: entry, id: entry.fullUrl})
205205
valid = false
206206
responseEntry = badRequest(errorMessage, fullUrl)
207207
}
@@ -252,7 +252,7 @@ export function buildDataItems(
252252

253253
for (const requestEntry of requestEntries) {
254254
const task = requestEntry.resource as Task
255-
logger.info("Building data item for task.", {task: task, id: task.id})
255+
logger.debug("Building data item for task.", {task: task, id: task.id})
256256

257257
const repeatNo = task.input?.[0]?.valueInteger
258258

packages/updatePrescriptionStatus/src/utils/databaseClient.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,41 @@ function createTransactionCommand(dataItems: Array<PSUDataItem>, logger: Logger)
3333
}
3434

3535
export async function persistDataItems(dataItems: Array<PSUDataItem>, logger: Logger): Promise<boolean | Timeout> {
36-
const transactionCommand = createTransactionCommand(dataItems, logger)
37-
try {
38-
logger.info("Sending TransactWriteItemsCommand to DynamoDB.", {command: transactionCommand})
39-
await client.send(transactionCommand)
40-
logger.info("TransactWriteItemsCommand sent to DynamoDB successfully.", {command: transactionCommand})
41-
return true
42-
} catch (e) {
43-
if (e instanceof TransactionCanceledException) {
44-
logger.error("DynamoDB transaction cancelled due to conditional check failure.", {reasons: e.CancellationReasons})
45-
throw e
36+
// break the array of data items into batches less than 100
37+
// to prevent dynamodb error with too many items
38+
const chunkSize = 99
39+
const transactionCommands = []
40+
for (let i = 0; i < dataItems.length; i += chunkSize) {
41+
const chunk = dataItems.slice(i, i + chunkSize)
42+
const transactionCommand = createTransactionCommand(chunk, logger)
43+
transactionCommands.push(transactionCommand)
44+
}
45+
const results = await Promise.all(transactionCommands.map(async transactionCommand => {
46+
try {
47+
logger.info("Sending TransactWriteItemsCommand to DynamoDB.", {command: transactionCommand})
48+
await client.send(transactionCommand)
49+
logger.info("TransactWriteItemsCommand sent to DynamoDB successfully.", {command: transactionCommand})
50+
return {success: true}
51+
} catch (e) {
52+
if (e instanceof TransactionCanceledException) {
53+
logger.error(
54+
"DynamoDB transaction cancelled due to conditional check failure.", {reasons: e.CancellationReasons})
55+
return {success: false, errorMessage: "conditional check failure", error: e}
56+
} else {
57+
logger.error("Error sending TransactWriteItemsCommand to DynamoDB.", {error: e})
58+
}
59+
return {success: false, errorMessage: "other error", error: e}
60+
}
61+
}))
62+
const failed = results.filter(r => !r.success)
63+
if (failed.length > 0) {
64+
const conditionalCheckFailures = failed.filter(r=> r.errorMessage === "conditional check failure")
65+
if (conditionalCheckFailures.length > 0) {
66+
throw(conditionalCheckFailures[0].error)
4667
}
47-
logger.error("Error sending TransactWriteItemsCommand to DynamoDB.", {error: e})
4868
return false
69+
} else {
70+
return true
4971
}
5072
}
5173

packages/updatePrescriptionStatus/tests/testDatabaseClient.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ describe("Unit test persistDataItems", () => {
1818
beforeEach(() => {
1919
jest.resetModules()
2020
jest.clearAllMocks()
21+
jest.resetAllMocks()
2122
})
23+
2224
it("when the conditional check fails, an error is thrown", async () => {
2325
const dataItems = [
2426
{
@@ -70,4 +72,105 @@ describe("Unit test persistDataItems", () => {
7072
reasons: [{Code: "ConditionalCheckFailedException"}]
7173
})
7274
})
75+
76+
it("returns success when everything works", async () => {
77+
const dataItems = [
78+
{
79+
LastModified: "2023-01-01T00:00:00Z",
80+
LineItemID: "LineItemID_1",
81+
PatientNHSNumber: "PatientNHSNumber_1",
82+
PharmacyODSCode: "PharmacyODSCode_1",
83+
PrescriptionID: "PrescriptionID_1",
84+
RequestID: "RequestID_1",
85+
Status: "Status_1",
86+
TaskID: "TaskID_1",
87+
TerminalStatus: "TerminalStatus_1",
88+
ApplicationName: "name",
89+
ExpiryTime: 10
90+
},
91+
{
92+
LastModified: "2023-01-02T00:00:00Z",
93+
LineItemID: "LineItemID_2",
94+
PatientNHSNumber: "PatientNHSNumber_2",
95+
PharmacyODSCode: "PharmacyODSCode_2",
96+
PrescriptionID: "PrescriptionID_1",
97+
RequestID: "RequestID_2",
98+
Status: "Status_2",
99+
TaskID: "TaskID_1",
100+
TerminalStatus: "TerminalStatus_2",
101+
ApplicationName: "name",
102+
ExpiryTime: 10
103+
}
104+
]
105+
106+
const loggerSpy = jest.spyOn(logger, "error")
107+
108+
const result = await persistDataItems(dataItems, logger)
109+
expect(result).toBe(true)
110+
expect(loggerSpy).not.toBeCalled()
111+
})
112+
113+
it("call dynamo update twice when there is a large batch", async () => {
114+
const dataItem = {
115+
LastModified: "2023-01-01T00:00:00Z",
116+
LineItemID: "LineItemID_1",
117+
PatientNHSNumber: "PatientNHSNumber_1",
118+
PharmacyODSCode: "PharmacyODSCode_1",
119+
PrescriptionID: "PrescriptionID_1",
120+
RequestID: "RequestID_1",
121+
Status: "Status_1",
122+
TaskID: "TaskID_1",
123+
TerminalStatus: "TerminalStatus_1",
124+
ApplicationName: "name",
125+
ExpiryTime: 10
126+
}
127+
const dataItems = Array(150).fill(dataItem)
128+
129+
const loggerSpy = jest.spyOn(logger, "error")
130+
131+
const result = await persistDataItems(dataItems, logger)
132+
expect(result).toBe(true)
133+
expect(loggerSpy).not.toBeCalled()
134+
expect(mockSend).toBeCalledTimes(2)
135+
})
136+
137+
it("returns false when there is a general error", async () => {
138+
const dataItems = [
139+
{
140+
LastModified: "2023-01-01T00:00:00Z",
141+
LineItemID: "LineItemID_1",
142+
PatientNHSNumber: "PatientNHSNumber_1",
143+
PharmacyODSCode: "PharmacyODSCode_1",
144+
PrescriptionID: "PrescriptionID_1",
145+
RequestID: "RequestID_1",
146+
Status: "Status_1",
147+
TaskID: "TaskID_1",
148+
TerminalStatus: "TerminalStatus_1",
149+
ApplicationName: "name",
150+
ExpiryTime: 10
151+
},
152+
{
153+
LastModified: "2023-01-02T00:00:00Z",
154+
LineItemID: "LineItemID_2",
155+
PatientNHSNumber: "PatientNHSNumber_2",
156+
PharmacyODSCode: "PharmacyODSCode_2",
157+
PrescriptionID: "PrescriptionID_1",
158+
RequestID: "RequestID_2",
159+
Status: "Status_2",
160+
TaskID: "TaskID_1",
161+
TerminalStatus: "TerminalStatus_2",
162+
ApplicationName: "name",
163+
ExpiryTime: 10
164+
}
165+
]
166+
167+
const loggerSpy = jest.spyOn(logger, "error")
168+
mockSend.mockRejectedValue(
169+
new Error("General error") as never
170+
)
171+
172+
const result = await persistDataItems(dataItems, logger)
173+
expect(result).toBe(false)
174+
expect(loggerSpy).toBeCalled()
175+
})
73176
})

0 commit comments

Comments
 (0)