Skip to content

mhspradlin/clojure-gradle-cdk-example

Repository files navigation

Clojure + Gradle + CDK Example

This project is an example of how to connect the code and infrastructure for a small web project written in Clojure and ClojureScript. The main idea is that full CI/CD infrastructure is overkill for many small projects but it is still valuable to model any infrastructure in code (if only to make it easier to delete.) I couldn't find any examples of connecting the output of backend and UI code to the CDK using Gradle, so I created this example to see how it could be done.

Explanation

This project is a monorepo of all the components needed for the application. Each component is a Gradle subproject where the necessary build inputs and outputs are connected to each other via Gradle's output sharing mechanisms. There's four subprojects:

  • app-ui: ClojureScript SPA using Figwheel Main
  • app-server: Clojure web server using deps.edn and depstar
  • packaging: Gradle-only project to stage the uberjar from app-server to be run in a Docker image
  • cdk: Typescript AWS CDK package to deploy the SPA and Docker image onto AWS

app-ui

The only changes from the standard Figwheel main template is the addition of a button that calls the "hello world" API on the backend. The build is managed by the usual Clojure CLI tools. The copyAssets build task reaches into the Figwheel compilation output and copies the minified assets to Gradle's build output folder, filtering out the test HTML page.

app-server

Created from a template, no significant alterations to the code. The build is managed by the usual Clojure CLI tools. depstar is used to create an uberjar (ahead-of-time compiled output with all dependencies) of the server. The buildUberjar task simply copies the uberjar to Gradle's build output.

packaging

Conceptually, the code in app-server could be run in a variety of environments. The first such environment you'll encounter is on your local development machine, which is certainly different than what's running in AWS Fargate (or EC2, or Azure, etc.) The idea behind the packaging project is that it isolates app-server from having to understand all the different platforms it might be deployed and run on. It is also generally easier to manage the packaging process if there's a clear handoff between the application compiler and the packaging step, and the explicit modeling of inputs/outputs using Gradle is good for that.

This copies the uberjar from the app-server project along with the Dockerfile and run_server.sh script into the Gradle build output for this project. It locates and names the files such that the CDK is able to build the Dcokerfile.

cdk

Models the infrastructure for the application:

  • A VPC to run in
  • An ECS Fargate cluster for the web API
  • An ECS Fargate service and load balancer for the web API
  • An S3 bucket for the SPA
  • A CloudFront distribution that routes to the SPA and web API

The special sauce here is how the build output from the other projects is handed to the CDK for upload at deployment time. This uses the CDK context passed via command-line arguments that are read by the appropriate constructs. Namely:

  • For the SPA, BucketDeployment
  • For the web server, DockerImageAsset

When you run cdk deploy, the CDK will copy the SPA assets to the bucket, build the Docker image, and upload the Docker image to ECR to be used by the rest of the infrastructure.

Running

You need a few things installed to deploy this:

Once you're all set up, you can simply run ./gradlew deploy and Gradle will manage building all the subprojects and invoking the CDK. If you make changes you can run ./gradlew deploy and Gradle will manage rebuilding the changed projects and invoking the CDK again.

Please note: This infrastructure costs you money; it cost about $12/month when I had it running, almost entirely due to the NAT gateway on the VPC private subnet. YMMV depending on if you're still in the free tier and what region you deploy to.

If you want to delete the infrastructure, navigate to the CloudFormation page in the AWS console and delete the stacks that the CDK created. There are inter-stack dependencies, so you need to delete them in reverse dependency order. Luckily the stack deletion fails fast if it cannot be deleted because of inter-stack dependencies, so it's not so bad even if you have to use trial-and-error. The stack dependencies are clear if you look at bin/cdk.ts in the cdk subproject.

Next Places to Look

If you want to use this for more than a prototype, then you should set up logging and monitoring for the web server. AWS Firelens is the latest hotness for log routing in AWS Fargate, the X-Ray daemon is good for collecting X-Ray traces. An alternative to look at is the AWS distribution for OpenTelemetry for collecting logs/metrics/traces.

This uses Gradle's configuration blocks for sharing artifacts between subprojects, but the artifacts block might actually be more appropriate.

Many applications need some sort of authentication and authorization. I don't recommend trying to call Cognito APIs directly. I've had good success with using the AWS Amplify library to manage user tokens and API integrations. The built-in Cognito Authorizer for API gateway is simple to use and should handle basic usecases without your web server needing to call Cognito.

About

Example project deploying a Clojure server and ClojureScript SPA to AWS using the CDK and Gradle.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published