WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit c66ca1e

Browse files
committed
refactor: simplify deletion logic using SQS built-in retry
Removed manual state tracking and DLQ handling in favor of SQS's built-in retry mechanism. Both mParticle and Braze APIs are idempotent (404 = success), so we can safely retry without tracking which services have succeeded.
1 parent 2daadda commit c66ca1e

File tree

9 files changed

+108
-920
lines changed

9 files changed

+108
-920
lines changed

handlers/mparticle-api/src/apis/dataSubjectRequests/deleteUser.ts

Lines changed: 33 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -3,115 +3,65 @@ import type { BrazeClient } from '../../services/brazeClient';
33
import { deleteBrazeUser } from '../../services/brazeClient';
44
import type { MParticleClient } from '../../services/mparticleClient';
55
import { deleteMParticleUser } from '../../services/mparticleDeletion';
6-
import { SQSService } from '../../services/sqsService';
7-
import type {
8-
DeletionRequestBody,
9-
DeletionStatus,
10-
MessageAttributes,
11-
} from '../../types/deletionMessage';
126

137
/**
148
* Process a user deletion request
159
*
1610
* This function implements idempotent deletion logic:
17-
* 1. Reads message attributes to see which APIs have already succeeded
18-
* 2. Only calls APIs that haven't succeeded yet
19-
* 3. Treats 404 responses as success (user already deleted)
20-
* 4. Updates message attributes based on results
21-
* 5. If all succeed: returns success
22-
* 6. If any fail: sends updated message to DLQ for retry
11+
* 1. Calls both mParticle and Braze deletion APIs
12+
* 2. Treats 404 responses as success (user already deleted - idempotent)
13+
* 3. If both succeed: message removed from queue
14+
* 4. If either fails with retryable error: throws to trigger SQS retry
15+
* 5. SQS automatically retries up to maxReceiveCount, then moves to DLQ
2316
*
24-
* @param body - The deletion request containing userId
25-
* @param attributes - Message attributes tracking deletion status
17+
* @param userId - The user ID to delete
2618
* @param mParticleClient - Client for mParticle API
2719
* @param brazeClient - Client for Braze API
28-
* @param dlqUrl - URL of the dead letter queue for failed deletions
29-
* @returns DeletionStatus indicating which APIs succeeded
3020
*/
3121
export async function processUserDeletion(
32-
body: DeletionRequestBody,
33-
attributes: MessageAttributes,
22+
userId: string,
3423
mParticleClient: MParticleClient,
3524
brazeClient: BrazeClient,
36-
dlqUrl: string,
37-
): Promise<DeletionStatus> {
38-
const { userId } = body;
39-
const attemptCount = attributes.attemptCount + 1;
25+
): Promise<void> {
26+
logger.log(`Processing deletion for user ${userId}`);
4027

41-
logger.log(
42-
`Processing deletion for user ${userId}, attempt ${attemptCount}. Current status: mParticle=${attributes.mParticleDeleted}, Braze=${attributes.brazeDeleted}`,
43-
);
28+
// Call both APIs - they're idempotent (404 = success)
29+
const mParticleResult = await deleteMParticleUser(mParticleClient, userId);
30+
const brazeResult = await deleteBrazeUser(brazeClient, userId);
4431

45-
// Track current deletion status
46-
let mParticleDeleted = attributes.mParticleDeleted;
47-
let brazeDeleted = attributes.brazeDeleted;
48-
49-
// Only call mParticle API if not already deleted
50-
if (!mParticleDeleted) {
51-
logger.log(`Calling mParticle deletion API for user ${userId}`);
52-
const mParticleResult = await deleteMParticleUser(mParticleClient, userId);
53-
54-
if (mParticleResult.success) {
55-
mParticleDeleted = true;
56-
logger.log(`mParticle deletion succeeded for user ${userId}`);
32+
// If mParticle failed with retryable error, throw to trigger SQS retry
33+
if (!mParticleResult.success) {
34+
if (mParticleResult.retryable) {
35+
logger.error(
36+
`mParticle deletion failed for user ${userId} - will retry`,
37+
mParticleResult.error,
38+
);
39+
throw mParticleResult.error;
5740
} else {
5841
logger.error(
59-
`mParticle deletion failed for user ${userId}. Retryable: ${mParticleResult.retryable}`,
42+
`Non-retryable mParticle error for user ${userId} - giving up`,
6043
mParticleResult.error,
6144
);
45+
// Don't throw - message will be removed from queue
6246
}
63-
} else {
64-
logger.log(
65-
`Skipping mParticle deletion - already deleted for user ${userId}`,
66-
);
6747
}
6848

69-
// Only call Braze API if not already deleted
70-
if (!brazeDeleted) {
71-
logger.log(`Calling Braze deletion API for user ${userId}`);
72-
const brazeResult = await deleteBrazeUser(brazeClient, userId);
73-
74-
if (brazeResult.success) {
75-
brazeDeleted = true;
76-
logger.log(`Braze deletion succeeded for user ${userId}`);
49+
// If Braze failed with retryable error, throw to trigger SQS retry
50+
if (!brazeResult.success) {
51+
if (brazeResult.retryable) {
52+
logger.error(
53+
`Braze deletion failed for user ${userId} - will retry`,
54+
brazeResult.error,
55+
);
56+
throw brazeResult.error;
7757
} else {
7858
logger.error(
79-
`Braze deletion failed for user ${userId}. Retryable: ${brazeResult.retryable}`,
59+
`Non-retryable Braze error for user ${userId} - giving up`,
8060
brazeResult.error,
8161
);
62+
// Don't throw - message will be removed from queue
8263
}
83-
} else {
84-
logger.log(`Skipping Braze deletion - already deleted for user ${userId}`);
85-
}
86-
87-
const allSucceeded = mParticleDeleted && brazeDeleted;
88-
89-
// If not all succeeded, send updated message to DLQ for retry
90-
if (!allSucceeded) {
91-
logger.log(
92-
`Deletion incomplete for user ${userId}. Sending to DLQ with updated attributes.`,
93-
);
94-
95-
const updatedAttributes: MessageAttributes = {
96-
mParticleDeleted,
97-
brazeDeleted,
98-
attemptCount,
99-
};
100-
101-
const sqsService = new SQSService();
102-
await sqsService.sendToDLQ(dlqUrl, body, updatedAttributes);
103-
104-
logger.log(
105-
`Message sent to DLQ for user ${userId} with attributes:`,
106-
updatedAttributes,
107-
);
108-
} else {
109-
logger.log(`Successfully deleted user ${userId} from all services`);
11064
}
11165

112-
return {
113-
mParticleDeleted,
114-
brazeDeleted,
115-
allSucceeded,
116-
};
66+
logger.log(`Successfully deleted user ${userId} from all services`);
11767
}

handlers/mparticle-api/src/index.ts

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ import { BrazeClient } from './services/brazeClient';
1515
import type { AppConfig } from './services/config';
1616
import { getAppConfig, getEnv } from './services/config';
1717
import { MParticleClient } from './services/mparticleClient';
18-
import {
19-
DeletionRequestBodySchema,
20-
parseMessageAttributes,
21-
} from './types/deletionMessage';
18+
import { DeletionRequestBodySchema } from './types/deletionMessage';
2219

2320
export const handlerHttp: Handler<
2421
APIGatewayProxyEvent,
@@ -86,7 +83,7 @@ export const handlerDeletion: Handler<SQSEvent, void> = async (
8683
// Process each record independently
8784
// SQS will retry failed messages automatically
8885
for (const record of event.Records) {
89-
await processSQSRecord(record, mParticleClient, brazeClient, config);
86+
await processSQSRecord(record, mParticleClient, brazeClient);
9087
}
9188

9289
logger.log('Finished processing deletion messages');
@@ -103,48 +100,18 @@ async function processSQSRecord(
103100
record: SQSRecord,
104101
mParticleClient: MParticleClient,
105102
brazeClient: BrazeClient,
106-
config: AppConfig,
107103
): Promise<void> {
108-
try {
109-
logger.log(`Processing message ${record.messageId}`);
110-
111-
// Parse the message body
112-
const body = DeletionRequestBodySchema.parse(JSON.parse(record.body));
104+
logger.log(`Processing message ${record.messageId}`);
113105

114-
// Parse message attributes to track deletion status
115-
const attributes = parseMessageAttributes(record.messageAttributes);
106+
// Parse the message body
107+
const body = DeletionRequestBodySchema.parse(JSON.parse(record.body));
116108

117-
logger.log(
118-
`Deletion request for user ${body.userId}. Attributes:`,
119-
attributes,
120-
);
121-
122-
// Process the deletion
123-
const result = await processUserDeletion(
124-
body,
125-
attributes,
126-
mParticleClient,
127-
brazeClient,
128-
config.mmaUserDeletionDlqUrl,
129-
);
109+
// Process the deletion - throws on retryable failure
110+
await processUserDeletion(body.userId, mParticleClient, brazeClient);
130111

131-
if (result.allSucceeded) {
132-
logger.log(
133-
`Successfully processed deletion for user ${body.userId}. Message ${record.messageId} will be deleted from queue.`,
134-
);
135-
} else {
136-
logger.log(
137-
`Partial success for user ${body.userId}. Message sent to DLQ for retry. mParticle=${result.mParticleDeleted}, Braze=${result.brazeDeleted}`,
138-
);
139-
}
140-
} catch (error) {
141-
logger.error(
142-
`Error processing message ${record.messageId}. Will be retried by SQS.`,
143-
error,
144-
);
145-
// Re-throw to let SQS handle retry logic
146-
throw error;
147-
}
112+
logger.log(
113+
`Successfully processed deletion for user ${body.userId}. Message ${record.messageId} will be deleted from queue.`,
114+
);
148115
}
149116

150117
async function services() {

handlers/mparticle-api/src/services/config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export const ConfigSchema = z.object({
1717
apiUrl: z.string(),
1818
apiKey: z.string(),
1919
}),
20-
mmaUserDeletionDlqUrl: z.string(),
2120
});
2221
export type AppConfig = z.infer<typeof ConfigSchema>;
2322

handlers/mparticle-api/src/services/sqsService.ts

Lines changed: 0 additions & 88 deletions
This file was deleted.

0 commit comments

Comments
 (0)