Implement OpenAPI for AWS Lambda in Quarkus Using API Gateway Integrator
About OpenAPI
It is a specification that allows to describe REST API in a standard way. The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for REST APIs. This allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic.
About Swagger
Swagger is a set of open-source tools built around the OpenAPI Specification that can help software professionals design, build, document, and consume REST APIs. Where OpenAPI deals with specification, Swagger implements the specification to describe a REST API .
Briefing of OpenAPI Specification
Purpose
- API, which is defined using the OpenAPI standard, can be discovered by both humans and computers and can be understood the capabilities of the service without access to source code, documentation, or through network traffic inspection.
- When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.
- An OpenAPI definition can then be used by documentation generation tools to display the API, code generation tools to generate servers and clients in various programming languages, testing tools, and many other use cases.
Main Concepts of Open API
OpenAPI Document
A document (or set of documents) that defines/describes an API. An OpenAPI definition uses and conforms to the OpenAPI Specification.
Path Templating
Path templating refers to the usage of template expressions, delimited by curly braces ({}), to mark a section of a URL path as replaceable using path parameters.
Each template expression in the path MUST correspond to a path parameter that is included in the Path Item itself and/or in each of the Path Item's Operations.
For example, booklist/{author}.
Media Types
Media type definitions are spread across several resources. The media type definitions should be in compliance with RFC6838.
Some examples of possible media type definitions:
- Application/json
- Test/plain;chartset-uff-8
HTTP Status Codes
The HTTP Status Codes are used to indicate the status of the executed operation. The available status codes are defined by RFC7231, and registered status codes are listed in the IANA Status Code Registry.
Reference Implementation of Open API for AWS Lambda Function
Reference Architecture
The reference implementation consists of the following components:
- AWS Lambda function to realize the business requirement/logic
- API Gateway to expose functionality as API
- Amazon API gateway integrator to integrate Open API specification and to expose Lambda function action as API
See the subsequent sections for more details on each architecture component.
About AWS Lambda Function
Lambda is a compute service that allows to run code without provisioning or managing servers. Lambda runs the code on a high-availability compute infrastructure and performs all of the administration of the compute resources, including server and operating system maintenance, capacity provisioning and automatic scaling, and logging. With Lambda, it is possible to run code for virtually any type of application or backend service. All it needs is to supply code in one of the languages that Lambda supports.
Lambda runs function only when needed and scales automatically, from a few requests per day to thousands per second. It saves compute cost as it is needed to pay only for the compute time that the function consumes during runtime — there is no charge when the code is not running. The lambda function can be invoked using the Lambda API or events from other AWS services, like as and when the object is created in the S3 bucket.
About AWS API Gateway
Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. APIs act as the "front door" for applications to access data, business logic, or functionality from backend services.
About AWS API Integration Function
This type of integration lets an API expose AWS service actions. In AWS integration, it is required to configure both the integration request and integration response and set up necessary data mappings from the method request to the integration request and from the integration response to the method response.
It allows specified details of the backend integration used for this method. This extension is an extended property of the OpenAPI Operation object. The result is an API Gateway integration object.
Important Property
The type of integration with the specified backend. Valid values are:
- http or http_proxy, for integration with an HTTP backend.
- aws_proxy, for proxy integration with AWS Lambda functions
- aws, for custom integration with AWS Lambda functions, or for integration to other AWS services, such as Amazon DynamoDB, Amazon Simple Notification Service, or Amazon Simple Queue Service.
- mock, for integration with API Gateway without invoking any backend. This type of integration lets API Gateway return a response without sending the request further to the backend.
The Lambda proxy integration supports a streamlined integration setup for a single Lambda function. The setup is simple and can evolve with the backend without having to tear down the already-created setup. For these reasons, it is highly recommended for integration with a Lambda function.
In contrast, the Lambda custom integration allows for the reuse of configured mapping templates for various integration endpoints that have similar requirements of the input and output data formats. The setup is more involved and is recommended for more advanced application scenarios.
x-amazon-apigateway-integrations
Example
The following example defines HTTP APIs (One GET and One POST) using the OpenAPI standard and uses one API gateway integration per API to integrate with Lambda Function (By referring to the ARN of function). It uses AWS Identity and Access Management (IAM) Role as a credential for integration. The below format is in YAML.
openapi: 3.0.1
info:
description: "This is a definition of Proxy Pattern Service. The service has 2 APIs."
version: v1
title: "Proxy Pattern Service"
paths:
/v1/proxypattern/employee:
options:
summary: CORS support
description: |
Enable CORS by returning correct headers
consumes:
- application/json
produces:
- application/json
tags:
- CORS
security:
- NONE: []
x-amazon-apigateway-integration:
type: mock
requestTemplates:
application/json: |
{
"statusCode" : 200
}
responses:
"default":
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Headers : "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,x-apigw-api-id,X-Amz-Security-Token,Cache-Control'"
method.response.header.Access-Control-Allow-Methods : "'*'"
method.response.header.Access-Control-Allow-Origin : "'*'"
responseTemplates:
application/json: |
{}
responses:
200:
description: Default response for CORS method
headers:
Access-Control-Allow-Headers:
type: "string"
Access-Control-Allow-Methods:
type: "string"
Access-Control-Allow-Origin:
type: "string"
post:
summary: "Save Employee"
operationId: "saveemployees"
tags:
- saveemployees
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/employee'
responses:
'200':
description: "Saved Employee Successfully"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
content:
application/json:
schema:
$ref: "#/components/schemas/employee"
'400':
description: "Application Errors"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: "Other unspecified Errors"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-amazon-apigateway-integration:
credentials:
Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/delegate-admin-lambda-proxy-pattern-role
httpMethod: "POST"
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ProxyPatternService.Arn}:live/invocations
responses:
"default":
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Origin : "'*'"
"BAD.*":
statusCode: "400"
responseParameters:
method.response.header.Access-Control-Allow-Origin : "'*'"
"INT.*":
statusCode: "500"
responseParameters:
method.response.header.Access-Control-Allow-Origin : "'*'"
type: "aws_proxy"
get:
summary: "Get All the Employees"
operationId: "getemployees"
tags:
- getemployees
responses:
'200':
description: "All The Employees retrieved successfully"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
content:
application/json:
schema:
type: "array"
items:
$ref: "#/components/schemas/employee"
'204':
description: "Employees not found"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
'500':
description: "Other unspecified Errors"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-amazon-apigateway-integration:
credentials:
Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/delegate-admin-lambda-proxy-pattern-role
httpMethod: "POST"
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ProxyPatternService.Arn}:live/invocations
responses:
"default":
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Origin : "'*'"
"BAD.*":
statusCode: "204"
responseParameters:
method.response.header.Access-Control-Allow-Origin : "'*'"
"INT.*":
statusCode: "500"
responseParameters:
method.response.header.Access-Control-Allow-Origin : "'*'"
type: "aws_proxy"
components:
schemas:
Employee:
type: object
description: EMPLOYEE
properties:
employeeId:
type: string
description: Id of the Employee
employeeName:
type: string
description: Name of the Employee
required:
- employeeId
ErrorResponse:
type: "object"
properties:
errorCode:
type: string
description: |
indicates an error in processing.
XXXX - Error in saving message
errorMessage:
type: string
description: "message description of error."
Below is the Cloud Formation Template for AWS Lambda Function, which is to be exposed through AWS API Gateway. The format is YAML. Here, the OpenAPI definition file has been referenced (/openapi-apigateway-PxyPtrnSvc.yaml
) so that API Gateway is aware of the integration points defined via the OpenAPI definition file.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS Serverless Quarkus HTTP - Proxy Pattern Poc Service
Globals:
Api:
EndpointConfiguration:
Type: PRIVATE
Auth:
ResourcePolicy:
CustomStatements: {
Effect: 'Allow',
Action: 'execute-api:Invoke',
Resource: ['execute-api:/*/*/*'],
Principal: '*'
}
Resources:
ProxyPatternServiceAWSApiGateway:
Type: AWS::Serverless::Api
Properties:
TracingEnabled: true
Name: ProxyPatternServiceSvcApiGateway
StageName: dev
DefinitionBody:
'Fn::Transform':
Name: 'AWS::Include'
Parameters:
Location: './openapi-apigateway-PxyPtrnSvc.yaml'
ProxyPatternServiceLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: '/aws/lambda/ProxyPatternService'
RetentionInDays: 30
ProxyPatternService:
Type: AWS::Serverless::Function
Properties:
Handler: not.used.in.provided.runtime
Runtime: provided
CodeUri: component1/function.zip
MemorySize: 512
Timeout: 900
FunctionName: ProxyPatternService
Environment:
Variables:
LOG_LEVEL: INFO
DISABLE_SIGNAL_HANDLERS: true
Role: !Sub "arn:aws:iam::${AWS::AccountId}:role/delegate-admin-lambda-proxy-pattern-role"
Tracing: Active
Outputs:
ProxyPatternServiceAWSApiGateway:
Description: 'API Gateway endpoint URL for dev stage for Proxy Pattern Service Template'
Value: !Sub 'https://${ProxyPatternServiceAWSApiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev/'
ProxyPatternServiceAWSApiGatewayRestApiId:
Description: 'API Gateway ARN for Basic AWS API Gateway'
Value: !Ref ProxyPatternServiceAWSApiGateway
Export:
Name: ProxyPatternServiceAWSApiGateway-RestApiId
ProxyPatternServiceAWSApiGatewayRootResourceId:
Value: !GetAtt ProxyPatternServiceAWSApiGateway.RootResourceId
Export:
Name: ProxyPatternServiceAWSApiGateway-RootResourceId
The example API definition can be related to below Controller interface which uses Java and can be implemented in Java Quarkus:
package com.example.proxypattern.controller;
import com.example.proxypattern.exception.model.ErrorResponse;
import com.example.proxypattern.model.Employee;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("v1/proxypattern/")
public interface IProxyPatternController {
@GET
@Path("/employee")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Get All the Employees", description = "getEmployees")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Employees not found"),
@APIResponse(responseCode = "200", description = "All The Employees retrieved successfully", content = @Content(schema = @Schema(implementation = Employee.class))),
@APIResponse(responseCode = "500", description = "Other unspecified Errors", content = @Content(schema = @Schema(implementation = ErrorResponse.class))) })
public Response getAllEmployee();
@POST
@Path("/employee")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Save Employee", description = "saveEmployees")
@APIResponses(value = {
@APIResponse(responseCode = "400", description = "Application Errors", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@APIResponse(responseCode = "200", description = "Saved Employee Successfully", content = @Content(schema = @Schema(implementation = Employee.class))),
@APIResponse(responseCode = "500", description = "Other unspecified Errors", content = @Content(schema = @Schema(implementation = ErrorResponse.class))) })
public Response saveEmployee(Employee employee);
}
The implementation language can vary. Java Quarkus can be selected as the implementation language for the Lambda function if the technical decision is to use Java as the coding language . Lambda supports custom runtime, and hence Quarkus code can be built natively to resolve response time issues related to Lambda Cold start.
The controller implementation code can look like the below. Here, the business logic has been abstracted via a business service class:
package com.example.proxypattern.controller.impl;
import com.example.proxypattern.controller.IProxyPatternController;
import com.example.proxypattern.model.Employee;
import com.example.proxypattern.service.impl.EmployeeServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.opentracing.Traced;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.core.Response;import java.util.List;
@Slf4j
@Traced
@Singleton
public class ProxyPatternControllerImpl implements IProxyPatternController {
@Inject
EmployeeServiceImpl employeeService;
@Override
public Response getAllEmployee() {
Response.Status status = Response.Status.OK;
//The business logic is abstracted here in Service code
List<Employee> listOfEmployee = employeeService.getAllEmployee();
if(listOfEmployee.isEmpty()){
status = Response.Status.NO_CONTENT;
}
log.info("Sending employee {}", listOfEmployee);
return Response.status(status).entity(listOfEmployee).build();
}
@Override
public Response saveEmployee(Employee employee) {
//The business logic is abstracted here in Service code
Employee employeeResponse = employeeService.saveEmployee(employee);
return Response.status(Response.Status.CREATED).entity(employeeResponse).build();
}
}
Maven can be used to build the project, and the below dependencies can also be used to build the project:
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-amazon-lambda-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-logging-json</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.slf4j</groupId>
<artifactId>slf4j-jboss-logmanager</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-opentracing</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<classifier>runtime</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
Conclusion
AWS API Gateway integration makes it easy to implement REST API using Lambda Function, which conforms to OpenAPI standard and makes it easy to integrate the same with AWS API Gateway.