Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
## Lambda demo with common Java application frameworks

<p align="center">
<img src="imgs/diagram.jpg" alt="Architecture diagram"/>
</p>
![Architecture Diagram](imgs/diagram.jpg)

This is a simple serverless application built in Java using popular frameworks - [Micronaut](https://micronaut.io/), [Quarkus](https://quarkus.io/), and [Spring Boot](https://spring.io/projects/spring-boot)

Expand All @@ -13,7 +11,9 @@ functions and an [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) table for s

- [AWS CLI](https://aws.amazon.com/cli/)
- [AWS SAM](https://aws.amazon.com/serverless/sam/)
- Java 11
- Java:
- v17 - Micronaut and Quarkus
- v11 - Spring Boot
- Maven
- [Artillery](https://www.artillery.io/) for load-testing the application
- Docker (at least 8GB memory and 4 CPUs)
Expand Down Expand Up @@ -80,15 +80,15 @@ All latencies listed below are in milliseconds.
<td>314.99</td>
</tr>
<tr>
<th>Quarkus</th>
<td><b style="color: green">2858.41</b></td>
<td><b style="color: green">2980.96</b></td>
<td><b style="color: green">3310.81</b></td>
<td><b style="color: green">4639.79</b></td>
<td><b style="color: green">7.38</b></td>
<td><b style="color: green">12.11</b></td>
<td><b style="color: green">25.00</b></td>
<td><b style="color: green">231.03</b></td>
<th>Quarkus (Java 17)</th>
<td><b style="color: green">2525.22</b></td>
<td><b style="color: green">3146.27</b></td>
<td><b style="color: green">4055.57</b></td>
<td><b style="color: green">6343.83</b></td>
<td><b style="color: green">7.50</b></td>
<td><b style="color: green">12.28</b></td>
<td><b style="color: green">29.87</b></td>
<td><b style="color: green">231.52</b></td>
</tr>
<tr>
<th>Spring Boot</th>
Expand Down Expand Up @@ -148,15 +148,15 @@ It fits particularly well with Lambda to reduce the initialization time, but doe
<td>244.82</td>
</tr>
<tr>
<th>Quarkus</th>
<td><b style="color: green">367.98</b></td>
<td><b style="color: green">413.63</b></td>
<td><b style="color: green">517.43</b></td>
<td><b style="color: green">573.76</b></td>
<td><b style="color: green">6.66</b></td>
<td><b style="color: green">11.27</b></td>
<td><b style="color: green">21.66</b></td>
<td>228.24</td>
<th>Quarkus (Java 17)</th>
<td><b style="color: green">487.31</b></td>
<td><b style="color: green">586.87</b></td>
<td><b style="color: green">732.67</b></td>
<td><b style="color: green">932.17</b></td>
<td><b style="color: green">7.38</b></td>
<td><b style="color: green">11.91</b></td>
<td><b style="color: green">25.20</b></td>
<td><b style="color: green">147.26</b></td>
</tr>
<tr>
<th>Spring Boot</th>
Expand All @@ -167,7 +167,7 @@ It fits particularly well with Lambda to reduce the initialization time, but doe
<td>8.07</td>
<td>13.65</td>
<td>28.41</td>
<td><b style="color: green">226.37</b></td>
<td>226.37</td>
</tr>
</table>

Expand Down
Binary file modified imgs/quarkus/quarkus-sample-log-insights.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified imgs/quarkus/quarkus-snapstart-cold-log-insights.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified imgs/quarkus/quarkus-snapstart-warm-log-insights.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 11 additions & 30 deletions quarkus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,17 @@ Using this CloudWatch Logs Insights query you can analyze the latency of the req

The query separates cold starts from other requests and then gives you p50, p90 and p99 percentiles.

**Latency for JVM version:**
>:warning: Please note that this query is not applicable to SnapStart version.

```
filter @type="REPORT"
| fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldStart
| stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldStart
```
![JVM Version Log Insights](../imgs/quarkus/quarkus-sample-log-insights.jpg)

Latency for JVM version:
<p align="center">
<img src="../imgs/quarkus/quarkus-sample-log-insights.JPG" alt="JVM Version Log Insights"/>
</p>

Latency for SnapStart version:
**Latency for SnapStart version:**
AWS Lambda service logs Restoration time differently compared to cold start times in CloudWatch Logs. For this
reason, we need different CloudWatch Logs Insights queries to capture performance metrics for SnapStart functions.
Also, it's easier to get cold and warm start performance metrics with two different queries rather than one.
Expand All @@ -105,10 +102,7 @@ filter @message like "REPORT"
| fields @duration + restoreTime as duration
| stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max
```

<p align="center">
<img src="../imgs/quarkus/quarkus-snapstart-cold-log-insights.JPG" alt="Cold Start Metrics with SnapStart"/>
</p>
![Cold Start Metrics with SnapStart](../imgs/quarkus/quarkus-snapstart-cold-log-insights.jpg)

Use the below query to get warm start metrics for with SnapStart Lambda functions:
```
Expand All @@ -119,15 +113,10 @@ filter @message like "REPORT"
| stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max
```

<p align="center">
<img src="../imgs/quarkus/quarkus-snapstart-warm-log-insights.JPG" alt="Cold Start Metrics with SnapStart"/>
</p>

Latency for GraalVM version:
![Warm Start Metrics with SnapStart](../imgs/quarkus/quarkus-snapstart-warm-log-insights.jpg)

<p align="center">
<img src="../imgs/quarkus/quarkus-native-log-insights.JPG" alt="GraalVM Version Log Insights"/>
</p>
**Latency for GraalVM version:**
![GraalVM Version Log Insights](../imgs/quarkus/quarkus-native-log-insights.JPG)

## AWS X-Ray Tracing
You can add additional detail to your X-Ray tracing by adding a TracingInterceptor to your AWS SDK clients.
Expand All @@ -138,24 +127,16 @@ Refer to the [AWS Documentation](https://docs.aws.amazon.com/lambda/latest/dg/sn

Example cold start trace for JVM version:

<p align="center">
<img src="../imgs/quarkus/quarkus-sample-cold-trace.JPG" alt="JVM Version Cold Trace Example"/>
</p>
![JVM Version Cold Trace Example](../imgs/quarkus/quarkus-sample-cold-trace.JPG)

Example cold start trace for GraalVM version:

<p align="center">
<img src="../imgs/quarkus/quarkus-native-cold-trace.JPG" alt="GraalVM Version Cold Trace Example"/>
</p>
![GraalVM Version Cold Trace Example](../imgs/quarkus/quarkus-native-cold-trace.JPG)

Example warm start trace for JVM version:

<p align="center">
<img src="../imgs/quarkus/quarkus-sample-warm-trace.JPG" alt="JVM Version Warm Trace Example"/>
</p>
![JVM Version Warm Trace Example](../imgs/quarkus/quarkus-sample-warm-trace.JPG)

Example warm start trace for GraalVM version:

<p align="center">
<img src="../imgs/quarkus/quarkus-native-warm-trace.JPG" alt="GraalVM Version Warm Trace Example"/>
</p>
![GraalVM Version Warm Trace Example](../imgs/quarkus/quarkus-native-warm-trace.JPG)
12 changes: 6 additions & 6 deletions quarkus/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus-plugin.version>2.16.1.Final</quarkus-plugin.version>
<quarkus-plugin.version>3.0.1.Final</quarkus-plugin.version>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
<quarkus.platform.version>2.16.1.Final</quarkus.platform.version>
<quarkus.platform.version>3.0.1.Final</quarkus.platform.version>
<surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
<quarkus.package.type>uber-jar</quarkus.package.type>
<aws.package.name>${artifactId}-${version}-aws.jar</aws.package.name>
<aws.sdk2.version>2.17.131</aws.sdk2.version>
<aws.package.name>${project.artifactId}-${project.version}-aws.jar</aws.package.name>
<aws.sdk2.version>2.20.42</aws.sdk2.version>
</properties>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package software.amazonaws.example.product.product.dao;

import com.amazonaws.xray.interceptors.TracingInterceptor;
import jakarta.enterprise.context.ApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
Expand All @@ -12,18 +13,10 @@
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;
import software.amazon.awssdk.services.dynamodb.model.*;
import software.amazonaws.example.product.product.entity.Product;
import software.amazonaws.example.product.product.entity.Products;

import javax.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import software.amazonaws.example.product.product.entity.Product;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

public class ProductMapper {
Expand All @@ -16,20 +15,18 @@ public class ProductMapper {
private static final String PRICE = "price";

public static Product productFromDynamoDB(Map<String, AttributeValue> items) {
Product product = new Product();
product.setId(items.get(PK).s());
product.setName(items.get(NAME).s());
product.setPrice(new BigDecimal(items.get(PRICE).n()));

return product;
return new Product(
items.get(PK).s(),
items.get(NAME).s(),
new BigDecimal(items.get(PRICE).n())
);
}

public static Map<String, AttributeValue> productToDynamoDb(Product product) {
Map<String, AttributeValue> item = new HashMap<>();
item.put(PK, AttributeValue.builder().s(product.getId()).build());
item.put(NAME, AttributeValue.builder().s(product.getName()).build());
item.put(PRICE, AttributeValue.builder().n(product.getPrice().toString()).build());

return item;
return Map.of(
PK, AttributeValue.builder().s(product.id()).build(),
NAME, AttributeValue.builder().s(product.name()).build(),
PRICE, AttributeValue.builder().n(product.price().toString()).build()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,8 @@
import io.quarkus.runtime.annotations.RegisterForReflection;

import java.math.BigDecimal;
import java.math.RoundingMode;

@RegisterForReflection
public class Product {
private String id;
private String name;
private BigDecimal price;
public record Product(String id, String name, BigDecimal price) {

public Product() {
}

public Product(String id, String name, BigDecimal price) {
this.id = id;
this.name = name;
setPrice(this.price = price);
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public BigDecimal getPrice() {
return price;
}

public void setPrice(BigDecimal price) {
this.price = price.setScale(2, RoundingMode.HALF_UP);
}

@Override
public String toString() {
return "Product{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", price=" + price +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,7 @@
import java.util.List;

@RegisterForReflection
public class Products {
private List<Product> products;
public record Products(List<Product> products) {

public Products() {
}

public Products(List<Product> products) {
this.products = products;
}

public List<Product> getProducts() {
return products;
}

public void setProducts(List<Product> products) {
this.products = products;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import software.amazon.awssdk.http.HttpStatusCode;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazonaws.example.product.product.dao.ProductDao;
import software.amazonaws.example.product.product.entity.Product;

import javax.inject.Inject;
import javax.inject.Named;

@Named("createProduct")
public class CreateProductHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
@Inject
Expand All @@ -34,7 +33,7 @@ public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent re
String id = requestEvent.getPathParameters().get("id");
String jsonPayload = requestEvent.getBody();
Product product = objectMapper.readValue(jsonPayload, Product.class);
if (!product.getId().equals(id)) {
if (!product.id().equals(id)) {
return new APIGatewayProxyResponseEvent()
.withStatusCode(HttpStatusCode.BAD_REQUEST)
.withBody("Product ID in the body does not match path parameter");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import software.amazon.awssdk.http.HttpStatusCode;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazonaws.example.product.product.dao.ProductDao;
import software.amazonaws.example.product.product.entity.Product;

import javax.inject.Inject;
import javax.inject.Named;
import java.util.Optional;

@Named("deleteProduct")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import software.amazon.awssdk.http.HttpStatusCode;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazonaws.example.product.product.dao.ProductDao;

import javax.inject.Inject;
import javax.inject.Named;

@Named("getAllProducts")
public class GetAllProductsHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

Expand Down
Loading