Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLSCMN-12 middyfy SQS events #9

Merged
merged 4 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions docs/utils/MIDDYFY.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,145 @@ export const handle = middyfyAPIGateway({ handler, eventSchema });
When an `eventSchema` is present in the middleware options, the Joi Validator middleware ensures that the event is valid. The middleware throws a ValidationError if it is invalid. The HTTP Error Handler knows how to handle a ValidationError, returning a response with status code 400 and an informative message.

Notice that nothing changed with the handler function itself!

## Creating a Scheduled event handler

Use a Scheduled event handler when you need to run a Lambda function on a cron schedule.

To create a Scheduled event handler, you will need to create two modules: the handler function and the middyfy wrapper.

Create the AWS Lambda Handler function with the usual signature...

_/handlers/task-scheduled/handler.ts_

```ts
import { Context, ScheduledEvent } from 'aws-lambda';
...

export const handler = async (event: ScheduledEvent, context: Context): Promise<void> => {
try {
// perform business logic
await TaskService.sendReminders();

} catch (error) {
console.error('Failed to send task reminders. Detail:', error);
throw new ServiceError(error);
}
};
```

Next, we need to _"middyfy"_ the handler function. This is just a fancy way of saying we are wrapping the handler function with middleware.

_/handlers/task-scheduled/index.ts_

```ts
import { middyfyScheduled } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfyScheduled({ handler, logger: console.log });
```

## Creating a SNS event handler

Use a SNS event handler to process events from an AWS Simple Notification Service (SNS) topic.

To create a SNS event handler, you will need to create two modules: the handler function and the middyfy wrapper.

Create the AWS Lambda Handler function with the usual signature...

_/handlers/task-sns/handler.ts_

```ts
import { Context, SNSEvent } from 'aws-lambda';
...

export const handler = async (event: SNSEvent, context: Context): Promise<void> => {
try {
// perform business logic
const promises = map(event.Records, (record) => {
const taskToCreate = record.Sns.Message as Task;
return TaskService.create(taskToCreate);
});
const tasks:Task[] = await Promise.all(promises);

} catch (error) {
console.error('Failed to create tasks. Detail:', error);
throw new ServiceError(error);
}
};
```

Next, we need to _"middyfy"_ the handler function. This is just a fancy way of saying we are wrapping the handler function with middleware.

_/handlers/task-sns/index.ts_

```ts
import { middyfySNS } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfySNS({ handler, logger: console.log });
```

The handler is wrapped with two middlewares.

1. The `event-normalizer` middleware performs a JSON parse on the `Message`.
2. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.

## Creating a SQS event handler

Use a SQS event handler to process events from an AWS Simple Queue Service (SQS) topic.

To create a SQS event handler, you will need to create two modules: the handler function and the middyfy wrapper. You may optionally create a `schema.ts` module which provides a Joi validation schema for the `SQSEvent`.

Create the AWS Lambda Handler function with the usual signature...

_/handlers/task-sqs/handler.ts_

```ts
import { Context, SQSEvent, SQSBatchResponse } from 'aws-lambda';
...

export const handler = async (event: SNSEvent, context: Context): Promise<SQSBatchResponse | void> => {
try {
// perform business logic
const promises = map(event.Records, (record) => {
const taskToCreate = record.body as Task;
return TaskService.create(taskToCreate);
});

const response:SQSBatchResponse = {};
Promise.allSettled(promises)
.then((values) => {
response.batchItemFailures = map(values, (value) => {
if (value.status === 'rejected') {
return { itemIdentifier: value.reason }
}
})
});

return response;
} catch (error) {
console.error('Failed to create tasks. Detail:', error);
throw new ServiceError(error);
}
};
```

Next, we need to _"middyfy"_ the handler function. This is just a fancy way of saying we are wrapping the handler function with middleware.

_/handlers/task-sqs/index.ts_

```ts
import { middyfySQS } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfySQS({ handler, logger: console.log });
```

The handler is wrapped with two middlewares.

1. The `event-normalizer` middleware performs a JSON parse on the `body`.
2. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@aws-sdk/client-dynamodb": "^3.454.0",
"@aws-sdk/lib-dynamodb": "^3.454.0",
"@middy/core": "^4.7.0",
"@middy/event-normalizer": "^4.7.0",
"@middy/http-event-normalizer": "^4.7.0",
"@middy/http-json-body-parser": "^4.7.0",
"@rollup/plugin-commonjs": "^25.0.4",
Expand Down Expand Up @@ -61,6 +62,7 @@
"@aws-sdk/client-dynamodb": "^3.454.0",
"@aws-sdk/lib-dynamodb": "^3.454.0",
"@middy/core": "^4.7.0",
"@middy/event-normalizer": "^4.7.0",
"@middy/http-event-normalizer": "^4.7.0",
"@middy/http-json-body-parser": "^4.7.0",
"http-status": "^1.7.3",
Expand Down
18 changes: 17 additions & 1 deletion src/utils/__tests__/middyfy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
APIGatewayProxyResult,
Context,
SNSEvent,
SQSBatchResponse,
SQSEvent,
ScheduledEvent,
} from 'aws-lambda';

import { middyfyAPIGateway, middyfySNS, middyfyScheduled } from '../middyfy';
import { middyfyAPIGateway, middyfySNS, middyfySQS, middyfyScheduled } from '../middyfy';

describe('middyfyAPIGateway', () => {
const handlerFn = async (
Expand Down Expand Up @@ -42,3 +44,17 @@ describe('middyfySNS', () => {
expect(handler).toBeDefined();
});
});

describe('middyfySQS', () => {
const handlerFn = async (event: SQSEvent, context: Context): Promise<SQSBatchResponse> => {
return {
batchItemFailures: [{ itemIdentifier: 'messageId' }],
};
};

it('should middyfySQS', () => {
const handler = middyfySQS({ handler: handlerFn });

expect(handler).toBeDefined();
});
});
3 changes: 2 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ export {
middyfyAPIGateway,
middyfyScheduled,
middyfySNS,
middyfySQS,
MiddyfyOptions,
ScheduledHandlerFn,
ScheduledMiddyfyOptions,
SNSHandlerFn,
SNSMiddyfyOptions,
SQSMiddyfyOptions,
} from './middyfy';

export { default as ID } from './id';
36 changes: 26 additions & 10 deletions src/utils/middyfy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import middy from '@middy/core';
import eventNormalizer from '@middy/event-normalizer';
import httpEventNormalizer from '@middy/http-event-normalizer';
import jsonBodyParser from '@middy/http-json-body-parser';
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
SNSEvent,
ScheduledEvent,
SQSHandler,
SNSHandler,
} from 'aws-lambda';
import { ObjectSchema } from 'joi';

Expand All @@ -27,11 +29,6 @@ export type APIGatewayHandlerFn = (
*/
export type ScheduledHandlerFn = (event: ScheduledEvent, context: Context) => Promise<void>;

/**
* The AWS Lambda handler function signature for SNS events.
*/
export type SNSHandlerFn = (event: SNSEvent, Context: Context) => Promise<void>;

/**
* Base options for `middyfy` functions.
*/
Expand Down Expand Up @@ -59,7 +56,14 @@ export type ScheduledMiddyfyOptions = MiddyfyOptions<ScheduledHandlerFn> & {
/**
* Options for middyfied SNS event handler functions.
*/
export type SNSMiddyfyOptions = MiddyfyOptions<SNSHandlerFn> & {
export type SNSMiddyfyOptions = MiddyfyOptions<SNSHandler> & {
eventSchema?: ObjectSchema;
};

/**
* Options for middyfied SQS event handler functions.
*/
export type SQSMiddyfyOptions = MiddyfyOptions<SQSHandler> & {
eventSchema?: ObjectSchema;
};

Expand Down Expand Up @@ -102,7 +106,19 @@ export const middyfyScheduled = (options: ScheduledMiddyfyOptions) => {
* @returns A middyfied handler function.
*/
export const middyfySNS = (options: SNSMiddyfyOptions) => {
return middy(options.handler).use(
validator({ eventSchema: options.eventSchema, logger: options.logger }),
);
return middy(options.handler)
.use(eventNormalizer())
.use(validator({ eventSchema: options.eventSchema, logger: options.logger }));
};

/**
* Wraps a SQS event handler function in middleware, returning the AWS Lambda
* handler function.
* @param options - The `SQSMiddyfyOptions` object.
* @returns A middyfied handler function.
*/
export const middyfySQS = (options: SQSMiddyfyOptions) => {
return middy(options.handler)
.use(eventNormalizer())
.use(validator({ eventSchema: options.eventSchema, logger: options.logger }));
};