-
Notifications
You must be signed in to change notification settings - Fork 112
Back end
It is very common for applications to handle user requests, process data, and respond back with success or error. These users have to get authenticated, and require ways to communicate with APIs (usually via a web interface).
For example, the create-application operation of the service is exposed as a Amazon API Gateway API. Customers interact with the service using the AWS SDK and the AWS console, where auth(n) and auth(z) is managed via AWS IAM. Requests made to the service get routed to AWS Lambda where the business logic is executed, and state is managed via Amazon DynamoDB. The open source project captures the request/response architecture of the production service almost identically, except that we show how to use Amazon Cognito for authentication and authorization. The following diagram captures the architecture for the back-end component released with this open source project.
The back-end component implements the following operations:
- CreateApplication: creates an application
- UpdateApplication: updates the application metadata
- GetApplication: gets the details of an application
- ListApplications: lists applications that you have created
- DeleteApplication: deletes an application
We used the Open API’s (Swagger) specification to define our APIs, and used the Swagger code-gen tool to generate server side models (for input/output), and JAX-RS annotations for the APIs listed above.
A quick word about JAX-RS (and Jersey)
JAX-RS (Java API for RESTful Web Services) is a Java programming language API spec that provides support in creating web services according to the Representational State Transfer architectural pattern. Jersey, the reference implementation of JAX-RS, implements support for the annotations defined in JSR 311, making it easy for developers to build RESTful web services by using the Java programming language.
Let’s walk through a few sections of code that help show how requests get routed and then processed by the appropriate Java methods of the application. It will be helpful to see how we used existing Java frameworks to help developers stay focused on writing just the business logic of the application.
The code snippet below shows JAX-RS annotations defined for the create-application API in the ApplicationsApi Java Interface generated from the API spec.
@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.JavaJAXRSSpecServerCodegen", date = "2019-10-10T15:53:41.375-07:00[America/Los_Angeles]")
public interface ApplicationsApi {
@POST
@Consumes({ "application/json" })
@Produces({ "application/json" })
@Operation(summary = "", description = "", security = {
@SecurityRequirement(name = "cognitoAuthorizer") }, tags={ })
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Successfully Created an application.", content = @Content(schema = @Schema(implementation = Application.class))),
@ApiResponse(responseCode = "400", description = "Bad Request Exception", content = @Content(schema = @Schema(implementation = BadRequestException.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized Exception", content = @Content(schema = @Schema(implementation = UnauthorizedException.class))),
@ApiResponse(responseCode = "409", description = "Conflict Exception", content = @Content(schema = @Schema(implementation = ConflictException.class))),
@ApiResponse(responseCode = "429", description = "Too Many Requests Exception", content = @Content(schema = @Schema(implementation = TooManyRequestsException.class))),
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(schema = @Schema(implementation = InternalServerErrorException.class))) })
Application createApplication(@Valid CreateApplicationInput body);
The ApplicationService
class contains all business logic for the APIs. This class implements the methods defined by the ApplicationsApi
Java interface. The code snippet below shows the implementation of the create-application API: a simple Java method that accepts the CreateApplicationInput
POJOas input to process the request for creating an application.
@Override
public Application createApplication(final CreateApplicationInput createApplicationInput) {
log.info("Creating application with input {}", createApplicationInput);
ApplicationRecord applicationRecord = modelMapper.map(createApplicationInput,
ApplicationRecord.class);
applicationRecord.setCreatedAt(Instant.now(clock));
applicationRecord.setVersion(1L);
applicationRecord.setUserId(securityContext.getUserPrincipal().getName());
try {
dynamodb.putItem(PutItemRequest.builder()
.tableName(tableName)
.item(applicationRecord.toAttributeMap())
.conditionExpression(
String.format("attribute_not_exists(%s) AND attribute_not_exists(%s)",
ApplicationRecord.USER_ID_ATTRIBUTE_NAME,
ApplicationRecord.APPLICATION_ID_ATTRIBUTE_NAME))
.build());
} catch (ConditionalCheckFailedException e) {
throw new ConflictApiException(new ConflictException()
.errorCode("ApplicationAlreadyExist")
.message(String.format("Application %s already exists.",
createApplicationInput.getApplicationId())));
}
return modelMapper.map(applicationRecord, Application.class);
}
Finally, the code snippet below shows how Amazon API Gateway requests get routed (from the Lambda handler) to the appropriate Java methods in the ApplicationService
class.
As mentioned earlier, the project uses the Jersey framework (an implementation of JAX-RS). The ApplicationService
class is registered with ResourceConfig
(a Jersey Application) so that Jersey can forward REST calls to the appropriate methods in ApplicationService
class. The API requests get sent to Jersey via the JerseyLambdaContainerHandler
middle ware component that natively supports API Gateway's proxy integration models for requests and responses. This middle ware component is part of the open source AWS Serverless Java Container framework which provides Java wrappers to run Jersey, Spring, Spark, and other Java-based framework apps inside AWS Lambda.