Reputation: 11
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:
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.
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
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