├── .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: --------------------------------------------------------------------------------