From 045b3d167bb0c64e3b59247d23bd4c58c5f6b467 Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sun, 17 Dec 2023 07:11:19 -0500 Subject: [PATCH 1/2] SLSCMN-10 middyfy lambda to lambda --- src/utils/__tests__/middyfy.test.ts | 26 ++++++++++++++- src/utils/index.ts | 5 +++ src/utils/middyfy.ts | 49 +++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/utils/__tests__/middyfy.test.ts b/src/utils/__tests__/middyfy.test.ts index 1d0df4b..80b48cc 100644 --- a/src/utils/__tests__/middyfy.test.ts +++ b/src/utils/__tests__/middyfy.test.ts @@ -8,7 +8,15 @@ import { ScheduledEvent, } from 'aws-lambda'; -import { middyfyAPIGateway, middyfySNS, middyfySQS, middyfyScheduled } from '../middyfy'; +import { + LambdaEvent, + LambdaResult, + middyfyAPIGateway, + middyfyLambda, + middyfySNS, + middyfySQS, + middyfyScheduled, +} from '../middyfy'; describe('middyfyAPIGateway', () => { const handlerFn = async ( @@ -58,3 +66,19 @@ describe('middyfySQS', () => { expect(handler).toBeDefined(); }); }); + +describe('middyfyLambda', () => { + const handlerFn = async (event: LambdaEvent, context: Context): Promise => { + return { + status: 200, + statusText: 'OK', + data: 'test', + }; + }; + + it('should middyfyLambda', () => { + const handler = middyfyLambda({ handler: handlerFn }); + + expect(handler).toBeDefined(); + }); +}); diff --git a/src/utils/index.ts b/src/utils/index.ts index 36f7e66..7a6d171 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,11 +1,16 @@ export { + LambdaEvent, + LambdaResult, + LambdaHandler, APIGatewayHandlerFn, APIGatewayMiddyfyOptions, middyfyAPIGateway, + middyfyLambda, middyfyScheduled, middyfySNS, middyfySQS, MiddyfyOptions, + LambdaMiddyfyOptions, ScheduledHandlerFn, ScheduledMiddyfyOptions, SNSMiddyfyOptions, diff --git a/src/utils/middyfy.ts b/src/utils/middyfy.ts index 8b2107a..f4d622e 100644 --- a/src/utils/middyfy.ts +++ b/src/utils/middyfy.ts @@ -29,6 +29,31 @@ export type APIGatewayHandlerFn = ( */ export type ScheduledHandlerFn = (event: ScheduledEvent, context: Context) => Promise; +/** + * A Lambda function for invocation by another Lambda function. + */ +export type LambdaEvent = TEvent; + +/** + * A Lambda function result for invocation by another Lambda function. + */ +export type LambdaResult = { + status: number; + statusText: string; + data: TResult; +}; + +/** + * The AWS Lambda handler function signature for Lambda events, i.e. Lambda to + * Lambda function invocations. + * Note: This is generally considered an anti-pattern. Search for another + * design pattern before utilizing Lambda-to-Lambda. + */ +export type LambdaHandler = ( + event: LambdaEvent, + context: Context, +) => Promise>; + /** * Base options for `middyfy` functions. */ @@ -67,6 +92,15 @@ export type SQSMiddyfyOptions = MiddyfyOptions & { eventSchema?: ObjectSchema; }; +/** + * Options for middyfied Lambda event handler functions. + */ +export type LambdaMiddyfyOptions = MiddyfyOptions< + LambdaHandler +> & { + eventSchema?: ObjectSchema; +}; + /** * Wraps an AWS Gateway proxy event handler function in middleware, returning the * AWS Lambda handler function. @@ -122,3 +156,18 @@ export const middyfySQS = (options: SQSMiddyfyOptions) => { .use(eventNormalizer()) .use(validator({ eventSchema: options.eventSchema, logger: options.logger })); }; + +/** + * Wraps a Lambda event handler function in middleware, returning the AWS + * Lambda handler function. + * Note: Lambda-to-Lambda invocations are considered an anti-pattern. Search + * for an alternative approach. + * @param options - The `LambdaMiddyfyOptions` object. + * @returns A middyfied handler function. + * @see {@link https://docs.aws.amazon.com/lambda/latest/operatorguide/functions-calling-functions.html} + */ +export const middyfyLambda = (options: LambdaMiddyfyOptions) => { + return middy(options.handler).use( + validator({ eventSchema: options.eventSchema, logger: options.logger }), + ); +}; From 398a6be4419b99935d50e00129248d26aa02ef2e Mon Sep 17 00:00:00 2001 From: Matthew Warman Date: Sun, 17 Dec 2023 07:27:00 -0500 Subject: [PATCH 2/2] SLSCMN-10 docs --- docs/utils/MIDDYFY.md | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/docs/utils/MIDDYFY.md b/docs/utils/MIDDYFY.md index e064b6a..b58a8b5 100644 --- a/docs/utils/MIDDYFY.md +++ b/docs/utils/MIDDYFY.md @@ -269,3 +269,78 @@ 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. + +## Creating a Lambda event handler + +Use a Lambda event handler to process events from direct invocations from another Lambda function. + +> **NOTE:** This is generally considered to be an anti-pattern in serverless application design. See [Lambda functions calling Lambda functions](https://docs.aws.amazon.com/lambda/latest/operatorguide/functions-calling-functions.html) in the AWS Lambda guide. Consider SQS queues or Step Functions as an alternative. + +To create a Lambda 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 `LambdaEvent`. + +Create the AWS Lambda Handler function with the usual signature... + +_/handlers/task-lambda/handler.ts_ + +```ts +import { Context } from 'aws-lambda'; +import { LambdaEvent, LambdaResult, middyfyLambda } from '@leanstacks/serverless-common'; +... + +type FindTaskEvent = { + taskId: string; +}; + +type FindTaskResult = { + task?: Task; +}; + +export const handler = async ( + event: LambdaEvent, + context: Context, +): Promise> => { + try { + // handle request + const { taskId } = event; + const task = await TaskService.findById(taskId); + + // format and return response + if (task) { + return { + status: 200, + statusText: 'OK', + data: { task }, + }; + } else { + return { + status: 404, + statusText: 'Not Found', + data: {}, + }; + } + } catch (error) { + console.warn(`Failed to find Task. Detail: ${error}`); + return { + status: 500, + statusText: `${error}`, + data: {}, + }; + } +}; +``` + +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-lambda/index.ts_ + +```ts +import { middyfyLambda } from '@leanstacks/serverless-common'; + +import { handler } from './handler'; + +export const handle = middyfyLambda({ handler, logger: console.log }); +``` + +The handler is wrapped with one middleware. + +1. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.