From 9121d564d85e00fddc99f97cf124b2e922651e40 Mon Sep 17 00:00:00 2001 From: scastine Date: Tue, 3 Oct 2023 16:08:28 -0300 Subject: [PATCH 1/5] changed Lambda implementation to use API Gateway Integration Proxy, which means, full request and response objects with status code, cors headers, content type and path parameters values within the Lamda implementation, using the Lambda events SDK --- MonoToMicroLambda/build.gradle | 4 +- .../monoToMicro/Lambda/UnicornBasketImpl.java | 89 ++++++++++++++----- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/MonoToMicroLambda/build.gradle b/MonoToMicroLambda/build.gradle index bc82486..bd9e5d4 100755 --- a/MonoToMicroLambda/build.gradle +++ b/MonoToMicroLambda/build.gradle @@ -14,10 +14,12 @@ repositories { } dependencies { - implementation 'com.amazonaws:aws-lambda-java-core:1.2.1' + implementation 'com.amazonaws:aws-lambda-java-core:1.2.3' + implementation 'com.amazonaws:aws-lambda-java-events:3.11.3' implementation platform('software.amazon.awssdk:bom:2.17.136') implementation platform('com.amazonaws:aws-xray-recorder-sdk-bom:2.14.0') implementation 'software.amazon.awssdk:aws-crt-client:2.17.143-PREVIEW' + implementation 'software.amazon.lambda:powertools-serialization:1.17.0' implementation 'software.amazon.awssdk:dynamodb-enhanced', { exclude group: 'software.amazon.awssdk', module: 'netty-nio-client' exclude group: 'software.amazon.awssdk', module: 'apache-client' diff --git a/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java b/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java index 34dfea9..366a2ae 100644 --- a/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java +++ b/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java @@ -19,10 +19,16 @@ package com.monoToMicro.Lambda; import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse; import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.entities.Subsegment; import com.amazonaws.xray.interceptors.TracingInterceptor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; @@ -37,20 +43,27 @@ import software.amazon.awssdk.utils.StringUtils; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; -public class UnicornBasketImpl implements RequestHandler { + +public class UnicornBasketImpl implements RequestHandler { private static final String UNICORN_TABLE_NAME = "unishop"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final DynamoDbAsyncClient ddb = DynamoDbAsyncClient.builder() .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) .httpClientBuilder(AwsCrtAsyncHttpClient.builder().maxConcurrency(50)) .region(Region.of(System.getenv("AWS_REGION"))) .overrideConfiguration(ClientOverrideConfiguration.builder() - .addExecutionInterceptor(new TracingInterceptor()).build()) + .addExecutionInterceptor(new TracingInterceptor()).build()) .build(); private static final DynamoDbEnhancedAsyncClient client = DynamoDbEnhancedAsyncClient.builder() .dynamoDbClient(ddb) .build(); + private static Map staticHeaders = Map.of("Content-Type", "application/json", + "Access-Control-Allow-Headers", "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", + "Access-Control-Allow-Origin", "*", + "Access-Control-Allow-Methods", "DELETE,OPTIONS,POST,GET"); static { try { @@ -64,15 +77,20 @@ public class UnicornBasketImpl implements RequestHandler } @Override - public String handleRequest(UnicornBasket unicornBasket, Context context) { - return "Unicorn Lives Matter"; + public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) { + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Unicorn Lives Matter").build(); } - public String addUnicornToBasket(UnicornBasket unicornBasket, Context context) - throws ExecutionException, InterruptedException { + public APIGatewayV2HTTPResponse addUnicornToBasket(APIGatewayV2HTTPEvent event, Context context) + throws ExecutionException, InterruptedException { final DynamoDbAsyncTable unicornBasketTable = client.table( UNICORN_TABLE_NAME, TableSchema.fromBean(UnicornBasket.class)); + UnicornBasket unicornBasket = extractDataFrom(event).as(UnicornBasket.class); + LambdaLogger logger = context.getLogger(); + logger.log("Incoming addUnicornToBasket request " + parseDTOToString(unicornBasket)); + //Get current basket UnicornBasket currentBasket = unicornBasketTable.getItem(r -> r.key(Key.builder().partitionValue(unicornBasket.getUuid()).build())).get(); @@ -84,9 +102,11 @@ public String addUnicornToBasket(UnicornBasket unicornBasket, Context context) Subsegment subsegment = AWSXRay.beginSubsegment("Creating new basket"); subsegment.putMetadata("unicorns", "newBasket", unicornBasket.getUuid()); AWSXRay.endSubsegment(); - return "Added Unicorn to basket"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Added Unicorn to basket").build(); } - return "No basket exist and none was created"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("No basket exist and none was created").build(); } //basket already exist, will check if item exist and add if not found @@ -101,7 +121,8 @@ public String addUnicornToBasket(UnicornBasket unicornBasket, Context context) for (Unicorn currentUnicorn : currentUnicorns) { if (currentUnicorn.getUuid().equals(unicornToAddUuid)) { //The unicorn already exists, no need to add him. - return "Unicorn already exists!"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Unicorn already exists!").build(); } } @@ -112,23 +133,30 @@ public String addUnicornToBasket(UnicornBasket unicornBasket, Context context) Subsegment subsegment = AWSXRay.beginSubsegment("Adding new unicorn"); subsegment.putMetadata("unicorns", "newUnicorn ", unicornToAdd.getUuid()); AWSXRay.endSubsegment(); - return "Added Unicorn to basket"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Added Unicorn to basket").build(); } - return "Are you sure you added a Unicorn?"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Are you sure you added a Unicorn?").build(); } - public String removeUnicornFromBasket(UnicornBasket unicornBasket, Context context) + public APIGatewayV2HTTPResponse removeUnicornFromBasket(APIGatewayV2HTTPEvent event, Context context) throws ExecutionException, InterruptedException { final DynamoDbAsyncTable unicornBasketTable = client.table( UNICORN_TABLE_NAME, TableSchema.fromBean(UnicornBasket.class)); + UnicornBasket unicornBasket = extractDataFrom(event).as(UnicornBasket.class); + LambdaLogger logger = context.getLogger(); + logger.log("Incoming removeUnicornFromBasket request " + parseDTOToString(unicornBasket)); + //Get current basket UnicornBasket currentBasket = unicornBasketTable.getItem(r -> r.key(Key.builder().partitionValue(unicornBasket.getUuid()).build())).get(); //if no basket exist then return an error if (currentBasket == null) { - return "No basket exist, nothing to delete"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("No basket exist, nothing to delete").build(); } //basket exist, will check if item exist and will remove @@ -146,12 +174,14 @@ public String removeUnicornFromBasket(UnicornBasket unicornBasket, Context conte if (currentUnicorns.isEmpty()) { //no more unicorns in basket, will delete the basket unicornBasketTable.deleteItem(currentBasket); - return "Unicorn was removed and basket was deleted!"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Unicorn was removed and basket was deleted!").build(); } else { //keeping basket alive as more unicorns are in it currentBasket.setUnicorns(currentUnicorns); unicornBasketTable.putItem(currentBasket); - return "Unicorn was removed! Other unicorns are still in basket"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Unicorn was removed! Other unicorns are still in basket").build(); } } } @@ -160,21 +190,36 @@ public String removeUnicornFromBasket(UnicornBasket unicornBasket, Context conte //no unicorn to remove, will try to remove the basket nonetheless unicornBasketTable.deleteItem(currentBasket); } - return "Didn't find a unicorn to remove"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Didn't find a unicorn to remove").build(); } - return "Are you sure you asked to remove a Unicorn?"; + return APIGatewayV2HTTPResponse.builder().withStatusCode(200) + .withHeaders(staticHeaders).withBody("Are you sure you asked to remove a Unicorn?").build(); } - public UnicornBasket getUnicornsBasket(UnicornBasket unicornBasket, Context context) + public APIGatewayV2HTTPResponse getUnicornsBasket(APIGatewayV2HTTPEvent event, Context context) throws ExecutionException, InterruptedException { final DynamoDbAsyncTable unicornBasketTable = client.table( UNICORN_TABLE_NAME, TableSchema.fromBean(UnicornBasket.class)); + LambdaLogger logger = context.getLogger(); - if (!StringUtils.isEmpty(unicornBasket.getUuid())) { - return unicornBasketTable.getItem(r -> - r.key(Key.builder().partitionValue(unicornBasket.getUuid()).build())).get(); + UnicornBasket unicornBasket = null; + logger.log("Incoming getUnicornsBasket request Path params" + event.getPathParameters().toString()); + String uuidPathParamValue = event.getPathParameters().get("uuid"); + + if (StringUtils.isNotBlank(uuidPathParamValue)) { + unicornBasket = unicornBasketTable + .getItem(r -> r.key(Key.builder().partitionValue(uuidPathParamValue).build())).get(); } + return APIGatewayV2HTTPResponse.builder().withStatusCode(200).withHeaders(staticHeaders) + .withBody(parseDTOToString(unicornBasket)).build(); + } - return null; + private static String parseDTOToString(UnicornBasket unicornBasket) { + try { + return OBJECT_MAPPER.writeValueAsString(unicornBasket); + } catch (JsonProcessingException e) { + return ""; + } } } \ No newline at end of file From cf63cf6ae97eadcd1aab0e5617079088505a5ec5 Mon Sep 17 00:00:00 2001 From: scastine Date: Wed, 4 Oct 2023 00:07:19 -0300 Subject: [PATCH 2/5] updated version number for gradle build to make changes easier to track from previous lambda version, as it is not backwards compatible --- MonoToMicroLambda/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MonoToMicroLambda/build.gradle b/MonoToMicroLambda/build.gradle index bd9e5d4..970f165 100755 --- a/MonoToMicroLambda/build.gradle +++ b/MonoToMicroLambda/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'com.monoToMicro' -version '0.0.1' +version '0.0.2' java.sourceCompatibility = JavaVersion.VERSION_11 java.targetCompatibility = JavaVersion.VERSION_11 From d6f9040eba30971331750ae23b904699b349436c Mon Sep 17 00:00:00 2001 From: scastine Date: Wed, 4 Oct 2023 11:28:19 -0300 Subject: [PATCH 3/5] following jvdl@ advice, I use the ObjectMapper inside the powertools library, instead of having 2 redundant ones, making the code cleaner --- .../main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java b/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java index 366a2ae..35101c5 100644 --- a/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java +++ b/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java @@ -26,7 +26,6 @@ import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.entities.Subsegment; import com.amazonaws.xray.interceptors.TracingInterceptor; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; @@ -41,6 +40,7 @@ import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; import software.amazon.awssdk.utils.StringUtils; +import software.amazon.lambda.powertools.utilities.JsonConfig; import java.util.List; import java.util.Map; @@ -49,7 +49,6 @@ public class UnicornBasketImpl implements RequestHandler { private static final String UNICORN_TABLE_NAME = "unishop"; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final DynamoDbAsyncClient ddb = DynamoDbAsyncClient.builder() .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) .httpClientBuilder(AwsCrtAsyncHttpClient.builder().maxConcurrency(50)) @@ -217,7 +216,7 @@ public APIGatewayV2HTTPResponse getUnicornsBasket(APIGatewayV2HTTPEvent event, C private static String parseDTOToString(UnicornBasket unicornBasket) { try { - return OBJECT_MAPPER.writeValueAsString(unicornBasket); + return JsonConfig.get().getObjectMapper().writeValueAsString(unicornBasket); } catch (JsonProcessingException e) { return ""; } From af5cce07d2eb1dab24b3ea3ece6157d2011d959e Mon Sep 17 00:00:00 2001 From: scastine Date: Thu, 5 Oct 2023 09:49:40 -0300 Subject: [PATCH 4/5] rewrote the method implementation to protect against NPE's in case Path param from request is null --- .../com/monoToMicro/Lambda/UnicornBasketImpl.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java b/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java index 35101c5..73377d5 100644 --- a/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java +++ b/MonoToMicroLambda/src/main/java/com/monoToMicro/Lambda/UnicornBasketImpl.java @@ -201,14 +201,17 @@ public APIGatewayV2HTTPResponse getUnicornsBasket(APIGatewayV2HTTPEvent event, C final DynamoDbAsyncTable unicornBasketTable = client.table( UNICORN_TABLE_NAME, TableSchema.fromBean(UnicornBasket.class)); LambdaLogger logger = context.getLogger(); - + UnicornBasket unicornBasket = null; - logger.log("Incoming getUnicornsBasket request Path params" + event.getPathParameters().toString()); - String uuidPathParamValue = event.getPathParameters().get("uuid"); - if (StringUtils.isNotBlank(uuidPathParamValue)) { - unicornBasket = unicornBasketTable - .getItem(r -> r.key(Key.builder().partitionValue(uuidPathParamValue).build())).get(); + if (null != event && null != event.getPathParameters()) { + logger.log("Incoming getUnicornsBasket request Path params" + event.getPathParameters().toString()); + String uuidPathParamValue = event.getPathParameters().get("uuid"); + + if (StringUtils.isNotBlank(uuidPathParamValue)) { + unicornBasket = unicornBasketTable + .getItem(r -> r.key(Key.builder().partitionValue(uuidPathParamValue).build())).get(); + } } return APIGatewayV2HTTPResponse.builder().withStatusCode(200).withHeaders(staticHeaders) .withBody(parseDTOToString(unicornBasket)).build(); From 5a66ec3d4da4c9d507b35d1c21bb7e8725af8a35 Mon Sep 17 00:00:00 2001 From: scastine Date: Thu, 5 Oct 2023 14:56:21 -0300 Subject: [PATCH 5/5] updated jar version number for new lambda implementation using integration proxy --- MonoToMicroAssets/MonoToMicroLambda.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MonoToMicroAssets/MonoToMicroLambda.yaml b/MonoToMicroAssets/MonoToMicroLambda.yaml index f8c5450..49987ae 100644 --- a/MonoToMicroAssets/MonoToMicroLambda.yaml +++ b/MonoToMicroAssets/MonoToMicroLambda.yaml @@ -13,7 +13,7 @@ Resources: - !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:38" Code: S3Bucket: '{{resolve:ssm:LambdaAssetBucketName:1}}' - S3Key: MonoToMicroLambda-0.0.1.jar + S3Key: MonoToMicroLambda-0.0.2.jar Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/service-role/MonoToMicroLambdaRole' Handler: com.monoToMicro.Lambda.UnicornBasketImpl::addUnicornToBasket TracingConfig: @@ -33,7 +33,7 @@ Resources: - !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:38" Code: S3Bucket: '{{resolve:ssm:LambdaAssetBucketName:1}}' - S3Key: MonoToMicroLambda-0.0.1.jar + S3Key: MonoToMicroLambda-0.0.2.jar Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/service-role/MonoToMicroLambdaRole' Handler: com.monoToMicro.Lambda.UnicornBasketImpl::removeUnicornFromBasket TracingConfig: @@ -53,7 +53,7 @@ Resources: - !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:38" Code: S3Bucket: '{{resolve:ssm:LambdaAssetBucketName:1}}' - S3Key: MonoToMicroLambda-0.0.1.jar + S3Key: MonoToMicroLambda-0.0.2.jar Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/service-role/MonoToMicroLambdaRole' Handler: com.monoToMicro.Lambda.UnicornBasketImpl::getUnicornsBasket TracingConfig: