This repository implements the Three Letter Abbreviations (TLA) Sample Application of the Context Mapper project with serverless technology. The sample application is built with the CQRS (Command Query Responsibility Segregation) pattern and is used to manage TLAs (Three Letter Abbreviations) in a distributed system. It can easily be deployed on AWS by using Serverless SDK. The app illustrates basic CRUD operations using the following AWS services:
- Amazon API Gateway now serves the RESTful HTTP API to access the TLA Manager and Resolver.
- AWS Lambda is used (one function per endpoint) to process the request events of the API gateway, load the data from a DynamoDB table and return a response event back to the gateway. An event based Lambda function can also be used to process the data in the background.
- Amazon DynamoDB is used to persist the proposed and accepted TLAs. The data is stored in a single NoSQL table.
- AWS Pipe is used to check for modifications in the DynamoDB table and trigger and event on the event bus.
- Amazon EventBridge is used to transport the events from the DynamoDB table to the Lambda function. The event bus is used to decouple the components of the application and to allow for asynchronous processing of events. The choreography of the application is based on the Event-Driven Architecture as described by AWS.
The application is split into two sub-projects and is organized as a monorepo:
- TLA Manager: The TLA manager is responsible for managing the TLAs and their groups. It provides the RESTful API to create, read, update and delete TLAs and their groups. The TLA manager is implemented in Java with Spring Boot and Spring Cloud Function. TLAs are in a PROPOSED state until they are accepted. Once accepted, they are in the ACCEPTED state. After the acceptance the DynamoDB Stream is triggered and the event is sent to the EventBridge event bus.
- TLA Resolver: The TLA resolver is responsible for providing a public API to the TLAs and their meanings. The resolver only returns TLAs that have been accepted. It uses a separate DynamoDB table to store the accepted TLAs.
Both of these subprojects are deployed together using the Serverless Framework and can be found in the subfolders /tla-manager
and /tla-resolver
.
The app uses the following tools and frameworks:
- Maven to build the app deployable (JAR)
- Spring Boot and Spring Cloud Function to implement the functions.
- The AWS SDK for Java 2.x to connect to the DynamoDB.
- The Serverless Framework to deploy the whole application on AWS.
- Including the definition of the API endpoints and the DynamoDB table; see the serverless.yml file.
- GitHub Actions as CI/CD tool to automatically deploy the app to AWS.
- Postman to test the API endpoints.
- Gitlab CI/CD to build the monorepo and deploy the app to AWS.
- GNU Make to build the tla-resolver project and create the executables for the golang lambda functions.
- Go to implement the golang lambda functions for the tla-resolver project.
The app is built depending on the subproject that should be deployed.
As mentioned above, the app is split into two subprojects: tla-manager
and tla-resolver
.
Using the command below in the project root, the entire monorepo can be built together.
make build
The app is built with the Serverless Framework and can be deployed to AWS.
Serverless contains a feature called Serverless Compose which allows you to deploy multiple services together.
With the serverless-compose.yml
file in the root folder, both subprojects can be deployed together.
Note: If the deployment with the composition file fails, you have to deploy the subprojects separately.
Note: It is important to note that the tla-resolver
project is dependent on the tla-manager
project, as it uses the EventBridge event bus to receive events from the DynamoDB table of the tla-manager
project.
The dependency is reflected in the composition file serverless-compose.yml
in the root folder.
The command below deploys the entire monorepo to AWS. It will build both subprojects and deploy them together.
To display the output of the deployment, the --debug
flag can be used additionally.
serverless deploy
To remove the entire CloudFormation stacks from AWS, the command below can be used. This will remove all resources that were created during the deployment by Serverless.
serverless remove
Note: The serverless
command is a shorthand for sls
, so you can also use sls deploy
or sls remove
.
Note: serverless deploy
only works if you have already set up the serverless framework locally, including logging in and connecting to your AWS account.
See the Serverless Framework documentation for more information on how to do this.
Building the app and its JAR file is done with Maven:
./mvnw clean package
The command above creates a JAR file target\tla-sample-serverless-1.2-SNAPSHOT-aws.jar
which contains all the functions (lambdas) of the app.
For the Lambda functions written in Java it is recommended to make use of the snapStart
option to speed up the cold start time of the functions.
Additionally the GraalVM native image could be used to create a native image of the Java application which is much smaller and faster than the JAR file.
This is currently not implemented in the project. A good starting point for this are the following links:
Once serverless deploy
was successful, you can fill the DynamoDB table with some sample data by executing the seed_database
function.
You can do this via the following command:
sls invoke --function seed_database --data 'unused'
The TLA resolver is implemented in Golang and uses the AWS SDK for Go to connect to the DynamoDB table.
The app is built with the Makefile
in the tla-resolver
folder. The command below builds the app and creates the executables for the golang lambda functions.
make build
The command above creates the executables in single binary format for the lambda functions in the tla-resolver/cmd
folder.
AWS requires the executeables to be in a single binary format with the name bootstrap
, so the Makefile
uses the GOOS=linux
and GOARCH=amd64
flags to create the executables for the lambda functions.
A more detailed description of creating the executables can be found in the AWS documentation here.
The executables are then zipped and uploaded to AWS Lambda (Deploy Go Lambda functions with .zip file archives).
make deploy
Running the command above builds and deploys the project according to the serverless.yml
file in the tla-resolver
folder to AWS.
After the deployment is finished, the API Gateway URL is displayed in the console. Now you can access the TLA's via the apps API: 🎉
The application currently supports the following use cases, for which we provide some sample CURLs.
There is also a Postman collection in the docs
folder.
Disclaimer: Please note that we haven't implemented any identity and access control measures for this sample application. All endpoints are publicly available; including the writing ones (commands).
Note that you will need to replace {baseUrl}
with the URLs you get from sls deploy
in all the following examples.
The base URL is different for each subproject, as the API Gateway is created for each subproject separately, so make sure to use the correct one.
Endpoint | Method | Description |
---|---|---|
/tlas?status=PROPOSED | GET | Get TLAs in PROPOSED state. |
/tlas | POST | Create a new TLA group (see sample payload below). Containing TLAs will be in PROPOSED state. |
/tlas/{groupName} | POST | Create a new TLA within an existing group (see sample payload below). The created TLA will be in PROPOSED state. |
/tlas/{groupName}/{name}/accept | PUT | Accept a proposed TLA (state transition operation: PROPOSED -> ACCEPTED). |
Endpoint | Method | Description |
---|---|---|
/tlas | GET | Get all TLA groups including their TLAs (accepted TLAs only). |
/tlas/{groupName} | GET | Get all TLAs of a specific group. |
/tlas/all/{name} | GET | Search for a TLA over all groups. This query can return multiple TLAs as a single TLA is only unique within one group. |
The /tlas
(GET) endpoint returns all TLAs of all TLA groups that are in the ACCEPTED
state (read on to see how to propose and accept new TLAs).
Note that all TLAs are part of a group.
CURL: curl -X GET {baseUrl}/tlas
Sample output:
[
{
"name":"common",
"description":"Common TLA group",
"tlas":[
{
"name":"TLA",
"meaning":"Three Letter Abbreviation",
"alternativeMeanings":[
"Three Letter Acronym"
]
}
]
},
{
"name":"AppArch",
"description":"Application Architecture",
"tlas":[
{
"name":"ADR",
"meaning":"Architectural Decision Record",
"alternativeMeanings":[
],
"link":"https://adr.github.io/"
}
]
},
{
"name":"DDD",
"description":"Domain-Driven Design",
"tlas":[
{
"name":"ACL",
"meaning":"Anticorruption Layer",
"alternativeMeanings":[
]
},
{
"name":"CF",
"meaning":"Conformist",
"alternativeMeanings":[
]
},
{
"name":"OHS",
"meaning":"Open Host Service",
"alternativeMeanings":[
]
},
{
"name":"PL",
"meaning":"Published Language",
"alternativeMeanings":[
]
},
{
"name":"SK",
"meaning":"Shared Kernel",
"alternativeMeanings":[
]
}
]
}
]
Note that the endpoint returns all TLAs in state ACCEPTED
by default. Use the query parameter status
with the value PROPOSED
to list TLAs in the PROPOSED
state (see example below under "Query Proposed TLAs").
The endpoint /tlas/{groupName}
(GET) returns all TLAs of a specific group.
Sample CURL: curl -X GET {baseUrl}/tlas/DDD
Sample output:
{
"name": "DDD",
"description": "Domain-Driven Design",
"tlas": [
{
"name": "ACL",
"meaning": "Anticorruption Layer",
"alternativeMeanings": []
},
{
"name": "CF",
"meaning": "Conformist",
"alternativeMeanings": []
},
{
"name": "OHS",
"meaning": "Open Host Service",
"alternativeMeanings": []
},
{
"name": "PL",
"meaning": "Published Language",
"alternativeMeanings": []
},
{
"name": "SK",
"meaning": "Shared Kernel",
"alternativeMeanings": []
}
]
}
With the endpoint /tlas/all/{name}
(GET) you can search for a TLA through all groups. Note that this might return multiple results, as TLAs are only unique within one group.
Sample CURL: curl -X GET {baseUrl}/tlas/all/ACL
Sample output:
[
{
"name": "DDD",
"description": "Domain-Driven Design",
"tlas": [
{
"name": "ACL",
"meaning": "Anticorruption Layer",
"alternativeMeanings": []
}
]
}
]
Via /tlas
(POST) you can create a new TLA group.
Sample CURL 1 (without containing TLA):
curl --header "Content-Type: application/json" \
-X POST \
-d '{ "name": "FIN", "description": "Finance TLAs", "tlas": [] }' \
{baseUrl}/tlas
Sample CURL 2 (with containing TLA):
curl --header "Content-Type: application/json" \
-X POST \
-d '{ "name": "FIN", "description": "Finance TLAs", "tlas": [ { "name": "ROI", "meaning": "Return on Investment", "alternativeMeanings": [] } ] }' \
{baseUrl}/tlas
Sample output: (created group is returned)
{
"name": "FIN",
"description": "Finance TLAs",
"tlas": [
{
"name": "ROI",
"meaning": "Return on Investment",
"alternativeMeanings": []
}
]
}
Note that the new TLA is now in state PROPOSED
and not delivered by the endpoints mentioned above. They only return TLAs in state ACCEPTED
by default. Use the following endpoint ("Accept a Proposed TLA") to accept a proposed TLA.
With the endpoint /tlas/{groupName}
(POST) you can add a new TLA to an existing group.
Sample CURL:
curl --header "Content-Type: application/json" \
-X POST \
-d '{ "name": "ETF", "meaning": "Exchange-Traded Fund", "alternativeMeanings": [] }' \
{baseUrl}/tlas/FIN
Sample output: (updated group is returned)
{
"name": "FIN",
"description": "Finance TLAs",
"tlas": [
{
"name": "ETF",
"meaning": "Exchange-Traded Fund",
"alternativeMeanings": []
},
{
"name": "ROI",
"meaning": "Return on Investment",
"alternativeMeanings": []
}
]
}
Note that the new TLA is now in state PROPOSED
and not delivered by the endpoints mentioned above. They only return TLAs in state ACCEPTED
by default. Use the following endpoint ("Accept a Proposed TLA") to accept a proposed TLA.
The endpoint /tlas
(GET) offers a query parameter to list all TLAs in the PROPOSED
state: /tlas?status=PROPOSED
Sample CURL: curl -X GET {baseUrl}/tlas?status=PROPOSED
Sample output:
[
{
"name": "FIN",
"description": "Finance TLAs",
"tlas": [
{
"name": "ETF",
"meaning": "Exchange-Traded Fund",
"alternativeMeanings": []
},
{
"name": "ROI",
"meaning": "Return on Investment",
"alternativeMeanings": []
}
]
}
]
With the endpoint /tlas/{groupName}/{name}/accept
(PUT) you can accept a TLA ("name") within a group ("groupName"). This is a so-called state transition operation.
Sample CURL: curl -X PUT {baseUrl}/tlas/FIN/ROI/accept
(puts the TLA 'ROI' in group 'FIN' into state ACCEPTED
)
This endpoint does not expect a body (JSON) and does also not return one. The command is successful if HTTP state 200 is returned.
Once the TLA is accepted, the query endpoints listed above (such as /tlas
or /tlas/{groupName}
) will now list them.
- Create and deploy AWS Step Function with Serverless framework
- AWS Golang Rest API with DynamoDB
- CQRS - Command Query Responsibility Segregation
- Migrating AWS Lambda functions from the Go1.x runtime to the custom runtime on Amazon Linux 2
- Event-based apps with Go and EventBridge
- Deploy Go Lambda functions with .zip file archives
- EventBridge to Lambda
Contributions are always welcome! Here are some ways how you can contribute:
- Create GitHub issues if you find bugs or just want to give suggestions for improvements.
- This is an open source project: if you want to code, create pull requests from forks of this repository. Please refer to a GitHub issue if you contribute this way.
This refactored version of the original serverless TLA sample application was implemented by Nico Fehr and Kyra Maag as part of a group assignment for the Cloud Solutions course at OST in the spring semester of 2025. Many thanks to Nico and Kyra for their contribution!
This project is released under the Apache License, Version 2.0.