Lambda Result
With respect to API Gateway Lambda Proxy results, there are certain conventions we need to provide from Lambda response detailed at: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
In order to achieve this a simple lambda should always return success and error response in the following format:
Success
statusCode: result.statusCode,
body: JSON.stringify({
status: result.status,
data: result.content,
error: result.error,
}),
Error
statusCode: 401,
body: JSON.stringify({
errorType: 'Anauthorized',
errorMessage: 'No Access',
}),
This can be achieved by wrapping the service layer methods with a ServiceResult
entity influenced by Java’s Optional pattern which is used for explicitly expressing the possible result of an action and keeping Exceptions
as exceptional as possible at infrastructure level or new runtime issues that needs fixing.
A simple ServiceResult
wrapper could be like below:
export interface ErrorResponse {
errorMessage: string;
errorType: string;
details?: any;
}
export class ServiceResult<T> {
public readonly success: boolean;
public readonly failure: boolean;
public readonly statusCode: number;
public readonly status: string;
public readonly content: T | null;
public readonly error: ErrorResponse | null;
private constructor(content: T | null, error: ErrorResponse | null, success: boolean) {
this.content = content;
this.error = error;
this.success = success;
this.failure = !success;
this.statusCode = success ? 200 : 400;
this.status = success ? "Success" : "Error";
}
public static Succeeded<T>(content: T): ServiceResult<T> {
return new ServiceResult<T>(content, null, true);
}
public static Failed<T>(error: ErrorResponse): ServiceResult<T> {
return new ServiceResult<T>(null, error, false);
}
}
Recently, I have used this pattern a lot and am keen to provide this as a public npm module for your Serverless API layer.
Benefits
- Human readable error codes returned by conventions
- AWS CloudWatch and API Gateway compatible conventions
- Developer productivity
Usage
import { APIGatewayProxyEvent } from 'aws-lambda';
import { SuccessResult, ErrorResult } from 'lambda-result';
import { Service } from './service';
import { ServiceValidator } from './validator';
exports.handler = async (event: APIGatewayProxyEvent) => {
// Validate incoming payload and convert to request
const request = ServiceValidator.validate(event);
if (request.failure) {
return ErrorResult.responseFromFailure(request.error);
}
// Run request via server (Adaptor pattern)
const result = Service.doSomething(request.content!);
// If service results a failure/error, return with API Gateway Proxy compatible result
if (result.failure) {
console.error(result);
return ErrorResult.responseFromFailure(result.error);
}
// Return an API Gateway Proxy compatible result
return SuccessResult.response(result);
};
The same simplicity will be achieved at the service layer with something like this:
import { ServiceResult } from 'lambda-result';
import { ServiceRequest, ServiceResponse } from './service-types';
export class Service {
public static doSomething(request: ServiceRequest): ServiceResult<ServiceResponse> {
const message: ServiceResponse = {
message: `Hello ${request.name}!`,
};
return ServiceResult.Succeeded(message);
}
}