├── .gitignore
├── calculate.js
├── error.js
├── init.js
├── package-lock.json
├── package.json
├── readme.md
└── serverless.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | # package directories
2 | node_modules
3 | jspm_packages
4 |
5 | # Serverless directories
6 | .serverless
7 |
8 | secrets.json
9 | .vscode
--------------------------------------------------------------------------------
/calculate.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports.handler = async (event) => {
4 | const { number } = JSON.parse(event.Records[0].Sns.Message)
5 | const factorial = (x) => x === 0 ? 1 : x * factorial(x - 1)
6 | const result = factorial(number)
7 |
8 | console.log(`The factorial of ${number} is ${result}.`)
9 | return result
10 | }
11 |
--------------------------------------------------------------------------------
/error.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports.handler = async (event) => {
4 | console.error(event)
5 | }
6 |
--------------------------------------------------------------------------------
/init.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const aws = require('aws-sdk')
4 | const sns = new aws.SNS({ region: 'eu-central-1' })
5 |
6 | function generateResponse (code, payload) {
7 | console.log(payload)
8 | return {
9 | statusCode: code,
10 | body: JSON.stringify(payload)
11 | }
12 | }
13 | function generateError (code, err) {
14 | console.error(err)
15 | return generateResponse(code, {
16 | message: err.message
17 | })
18 | }
19 | async function publishSnsTopic (data) {
20 | const params = {
21 | Message: JSON.stringify(data),
22 | TopicArn: `arn:aws:sns:${process.env.region}:${process.env.accountId}:calculate-topic`
23 | }
24 | return sns.publish(params).promise()
25 | }
26 |
27 | module.exports.handler = async (event) => {
28 | const data = JSON.parse(event.body)
29 | if (typeof data.number !== 'number') {
30 | return generateError(400, new Error('Invalid number.'))
31 | }
32 |
33 | try {
34 | const metadata = await publishSnsTopic(data)
35 | return generateResponse(200, {
36 | message: 'Successfully added the calculation.',
37 | data: metadata
38 | })
39 | } catch (err) {
40 | return generateError(500, new Error('Couldn\'t add the calculation due an internal error. Please try again later.'))
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lambda-sns-dlq-error-handling",
3 | "version": "0.0.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "serverless-pseudo-parameters": {
8 | "version": "2.2.0",
9 | "resolved": "https://registry.npmjs.org/serverless-pseudo-parameters/-/serverless-pseudo-parameters-2.2.0.tgz",
10 | "integrity": "sha512-eGgKkpOwnqnrmTZH87xnK+cVs9sz5d7fQnIR1K+BE1AobMd24oz4jswCkmhavYI6QF8Z3XL1Fekx+dfDn7ki+w=="
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lambda-sns-dlq-error-handling",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "handler.js",
6 | "dependencies": {
7 | "serverless-pseudo-parameters": "^2.2.0"
8 | },
9 | "devDependencies": {},
10 | "scripts": {
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC"
16 | }
17 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Trigger AWS Lambda with SNS with DLQ error handling
4 | Sample project for showing the ability to publish an SNS topic and trigger a function from the topic. Code is structured to create a timeout/crash so the dead letter queue SNS topic gets published, in turn triggering the error handler function.
5 |
6 | ### Explanation
7 |
8 | - The `init` function is the only exposed function, which is hooked up to API Gateway. It takes a single `number` parameter which it validates, upon success it publishes an SNS topic and sends along the `number` value.
9 | - The SNS topic will trigger a second function called `calculate`. This function will perform the calculation and log out the result to the console. This mimics a heavy computational background task such as data processing, image manipulation or machine learning calculations.
10 | - If either of the two functions fail the dead letter queue SNS topic will receive a message and trigger the `error` function.
11 |
12 | Every function will try to retry its execution upon failure a total of 3 times. Using the dead letter queue as a pool for your error logs is a smart use-case.
13 |
14 | ### Causing a crash
15 | Adding a crazy huge number will trigger a factorial calculation that'll crash because of a `Maximum call stack size exceeded` rangeError.
16 |
17 | ```bash
18 | curl -H "Content-Type: application/json" \
19 | -d '{"number":10000000}' \
20 | https://.execute-api..amazonaws.com/dev/init
21 | ```
22 |
23 | This will trigger the `error` function where you can handle the error as you wish.
24 |
25 | ---
26 |
27 | The service is only a basic proof of concept which shows the use case of SNS triggering lambda functions and dead letter queues. The actual code is trivial and not exciting at all, but it serves the sole purpose of explaining what the *@$# is going on. :smile:
28 |
29 | ---
30 |
31 | ### TODO
32 | - add SQS resources
33 | - create setup where DLQ uses SQS
34 | - create stream between `init` and `calculate` with SQS
35 | - add SSM params instead of `secrets.json`
36 |
--------------------------------------------------------------------------------
/serverless.yml:
--------------------------------------------------------------------------------
1 | service: lambda-sns-dlq-error-handling
2 |
3 | custom:
4 | secrets: ${file(secrets.json)}
5 |
6 | plugins:
7 | - serverless-pseudo-parameters
8 |
9 | provider:
10 | name: aws
11 | runtime: nodejs8.10
12 | stage: dev
13 | region: eu-central-1
14 | profile: ${self:custom.secrets.profile, 'dev'}
15 | memorySize: 128
16 | environment:
17 | accountId: '#{AWS::AccountId}'
18 | region: ${self:provider.region}
19 | iamRoleStatements:
20 | - Effect: "Allow"
21 | Resource: "*"
22 | Action:
23 | - "sns:*"
24 |
25 | functions:
26 | init:
27 | handler: init.handler
28 | events:
29 | - http:
30 | path: init
31 | method: post
32 | cors: true
33 | calculate:
34 | handler: calculate.handler
35 | events:
36 | - sns: calculate-topic
37 | onError: arn:aws:sns:#{AWS::Region}:#{AWS::AccountId}:dlq-topic
38 | error:
39 | handler: error.handler
40 | events:
41 | - sns: dlq-topic
42 |
43 | # TODO - ADD SQS QUEUE
44 | # resources:
45 | # Resources:
46 | # SQSDLQStream:
--------------------------------------------------------------------------------