Skip to content

kdgregory/example-lambda-java

Repository files navigation

LambdaPhoto

This project is a simple photo-sharing site with the following features:

  • Self-signup for users.
  • Each user can upload as many photos as they want (as long as they're JPEGs).
  • Each photo will be resized to some standard "web" sizes.
  • Users can share or embed photos via link.

I originally wrote it early in 2017 to support a talk for the Philadelphia Java Users Group. In 2019 I revisted it to generally clean up the code and use what I believe are current best-practices: CloudFront and an Application Load Balancer rather than API Gateway, and direct upload to S3. In 2020 I revised it once again, using the Cognito Hosted UI and ALB-Cognito integration rather than a homebuilt UI and direct calls to Cognito.

At this point I consider the architecture and back-end code to be production quality (if limited in scope). The front-end code ... well, nobody hires me to be a front-end developer.

I should be clear, however, that I don't think Lambda is a good implementation plaform for a web-app. Performance is generally OK when using Python or NodeJS, but Java and its long cold-star times (in my experience, 4-5 seconds for first response) is simply unacceptable. On top of this, you still need to jump through hoops (or pay for a NAT) if you need a mixture of inside-VPC and outside-VPC resources.

Architecture

Architecture Diagram

The core of the application are the two Lambda functions:

  • The WebApp handles client API requests.
  • The Resizer is invoked when a new file is uploaded, to transform the image into a set number of sizes.

The front-end is written as a single-page application, using (an obsolete version of) AngularJS. It relies on the Cognito hosted UI for authentication.

An application load balancer stands in front of the webapp Lambda, using Cogito to authorize requests. I chose an ALB rather than API Gateway because the integration is much simpler. This decision, however, has a few architectural consequences:

  • The application's main page is served as a static response by the load balancer. This is necessary in order to support redirect to the Cognito hosted UI.
  • CloudFront is relegated to serving static content other than the main page (although the Cognito docs claim that it can sit in front of the load balancer, at the current time that's simply not true). I also use CloudFront to cache the uploaded photos (in the expecation that their URLs will be shared).

Photos are stored on S3. Uploads go directly to their own bucket, using a signed URL from the webapp Lambda. This avoids the 1MB ALB/Lambda payload limit, but I think it's useful even with a traditional app-server, to minimize load. Once an image has been uploaded, the bucket invokes the Resizer Lambda, which transforms the image and updates stored metadata.

User and photo metadata is stored in a DynamoDB table. For more information about the table design, look here.

What's in the repository

  • README.md - This file.
  • docs - Additional implementation documentation.
  • scripts - Deployment scripts (see below).
  • pom.xml - Maven build file.
  • lib-Shared - Code that is shared between the WebApp and Resizer (the DynamoDB and S3 code lives here).
  • resizer-Lambda - A Lambda function that resizes photos in response to a message on SNS.
  • webapp-Lambda - A Lambda function that implements a simple WebApp.
  • webapp-Static - Static content for the WebApp.

Building and Deploying

To build and deploy, run this script (you must have AWS configured for command-line use):

scripts/build_and_deploy.sh BASENAME BASE_BUCKETNAME VPC_ID PUBLIC_SUBNETS HOSTNAME DNS_DOMAIN ACM_CERT_ARN

where:

  • BASE_NAME is a unique name that's used for the stack and all deployed components. I use LambdaPhoto.
  • BASE_BUCKET_NAME is used as the prefix for all buckets used by the application (there are five: one each for uploads, images, static content, Lambda deployment, and ALB access logs). This name must be unique in all of AWS; I recommend an inverse-hostname such as com-mycompany-lambdaphoto.
  • VPC_ID and SUBNET_IDS are used to deploy the load balancer; the latter is a comma-separated list of public subnets within the VPC (eg: subnet-123456,subnet-789012).
  • HOSTNAME, DNS_DOMAIN, and ACM_CERT_ARN are used to configure the load balancer and CloudFront; see below for more information.

When you run the script, it first builds the project, then creates the buckets and copies the deployment bundles into its bucket; you'll see various messages as this happens. Next, it starts building the CloudFormation stack; you should see a message like this:

creating CloudFormation stack
waiting on stack: arn:aws:cloudformation:us-east-1:012345678901:stack/JavaLambda/a5c99920-ba81-11aa-8229-0e8302531d14

Then the script will wait for 15-20 minutes while the stack builds (I believe a large part of this is the CloudFront distribution, which has been getting better in the past year). After building the stack, the script copies static content into its bucket. Interrupting the script does not interrupt stack creation, and leaves the application non-functional due to missing static content.

Shutting Down

Warning: a running ALB will cost slighly under $1 per day, and you'll be charged that for every day that you leave this stack running. To avoid this cost, you can run the shutdown script:

Scripts/undeploy.sh BASENAME

where:

  • BASENAME is the same name that used when creating the stack; you can also use the stack ID.

This script deletes the stack and also all buckets that were created by the deployment script. If you don't want to delete the buckets, delete the stack manually using the AWS Console.

Hostnames and Certificates

This application requires custom hostnames for the load balancer and CloudFront, along with an ACM certificate that allows both of them to serve HTTPS traffic. You provide a base hostname as a stack parameter, uses that hostname to create and assign two Route53 recordsets:

  • The base hostname is used for the load balancer.
  • The CloudFront distribution uses the base hostname plus the suffix -static.

Note that the ACM certificate must be valid for both hostnames. I recommend using a wildcard cert.

About

An web-app built from AWS components, with the core functionality written in Java

Resources

Stars

Watchers

Forks

Packages

No packages published