Rsrvrdog
Rsrvrdog

Reputation: 11

AWS Lambda to be invoked directly from a step function (without invoking the API) and also within an API context

I am struggling to understand a basic aspect of Lambda implementation.

Problem: how to use a lambda both inside and outside of an API context?

I have a lambda (nodejs) with an API gateway in front of it:

 MyFunction:
    Type: AWS::Serverless::Function
    Properties:
        CodeUri: functions/myfunction/
        Handler: app.lambdaHandler
        Runtime: nodejs14.x
        Timeout: 4
        Policies:
            - DynamoDBCrudPolicy:
                  TableName: MyTable
        Events:
            ApiEvent:
                Type: Api
                Properties:
                    Path: /myfunc
                    Method: any

The handler is used to read (GET) or write (POST) into a a DynamoDB table and returns accordingly. If no method is passed it assumes GET.

exports.lambdaHandler = async (event) => {

    const method = event.httpMethod ? event.httpMethod.toUpperCase() : "GET";
    
    try {
        switch (method) {
            case "GET":
                // assuming an API context event.queryStringParameters might have request params. If not the parameters will be on the event
                const params = event.queryStringParameters ? event.queryStringParameters : event;
                // read from dynamo db table then return an API response. what if outside an API context?
                return {
                    statusCode: 200,
                    body: JSON.stringify({ message: "..." })
                };
            case "POST":
                // similarly the body will be defined in an API context
                const body = typeof event.body === "string" ? JSON.parse(event.body) : event.body;
                // write to dynamo db table
                return {
                    statusCode: 200,
                    body: JSON.stringify({ message: "..." })
                };
            default:
                return {
                    statusCode: 400,
                    body: JSON.stringify({ error: "method not supported" })
                };
        }
    } catch (error) {
        // this should throw an Error outside an API context
        return {
            statusCode: 400,
            body: JSON.stringify({ error: `${typeof error === "string" ? error : JSON.stringify(error)}` })
        };
    }
}

Is there an easy way to refactor this code to support both scenarios? For example a step function could call this lambda as well. I know I can have the step function invoking an API but I think this is overkill as step functions support invoking lambdas directly.

I see 2 ways I can go about this:

  1. The lambda has to be aware of whether it is being invoked within an API context or not. It needs to check if there's a http method, queryStringParameters and build its input from these. Then it needs to return a response accordingly as well. A stringified JSON with statusCode or something else, including throwing an Error if outside an API call.

  2. The lambda assumes it is being called from an API. Then the step function needs to format the input to simulate the API call. The problem is that the response will be a string which makes it difficult to process inside a step function. For example assigning it to a ResultPath or trying to decide if there was an error or not inside a choice.

Additionally I could have the step function calling an API directly or the last resort would be to have 2 separate lambdas where the API lambda calls the other one but this will incur additional costs.

Thoughts? Thanks.

Upvotes: 1

Views: 937

Answers (1)

Balu Vyamajala
Balu Vyamajala

Reputation: 10393

This is where middlewares like MIDDY come into picture.

All the logic to determine event type, event source and parsing will be abstracted out and actual business logic always coded to use standard input. we can add as many layers as we need and send standard schema as lambda input. Typically, events may come from Api Gateway, Step function, Kinesis, SQS, etc and same lambda works for any event source.

export const handler = middy((event, context, callback) => {
    const mainProcess = async () => {
        const response = {}
        // Busines Logic using event
        return response;
    };

    mainProcess()
        .then((result) => {
            callback(null, result);
        })
        .catch((error) => {
            callback(error);
        });
})
    .use({
        before: (hndlr, next) => {
            const parsedEvent = parseApiGatewayRequest(hndlr.event);
            if (parsedEvent) {
                hndlr.event = parsedEvent;
            }
            next();
        },
    })
    .use({
        before: (hndlr, next) => {
            const parsedEvent = parseStepFuncRequest(hndlr.event);
            if (parsedEvent) {
                hndlr.event = parsedEvent;
            }
            next();
        },
    });

Upvotes: 0

Related Questions