Skip to content

Commit 2955632

Browse files
committed
Merge branch 'pagejep/generic-serdes-durable-config' into 'main'
feat(config): Customizable DurableConfig for setting default configuration See merge request mxschell/aws-durable-execution-sdk-java!13
2 parents 2628d54 + 9e4923b commit 2955632

30 files changed

Lines changed: 1438 additions & 146 deletions

examples/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
<artifactId>aws-lambda-java-core</artifactId>
3131
</dependency>
3232

33+
<!-- AWS SDK Apache HTTP Client -->
34+
<dependency>
35+
<groupId>software.amazon.awssdk</groupId>
36+
<artifactId>apache-client</artifactId>
37+
</dependency>
38+
3339
<!-- SLF4J Simple Logger -->
3440
<dependency>
3541
<groupId>org.slf4j</groupId>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.amazonaws.lambda.durable.examples;
4+
5+
import com.amazonaws.lambda.durable.DurableConfig;
6+
import com.amazonaws.lambda.durable.DurableContext;
7+
import com.amazonaws.lambda.durable.DurableHandler;
8+
import com.amazonaws.lambda.durable.TypeToken;
9+
import com.amazonaws.lambda.durable.client.LambdaDurableFunctionsClient;
10+
import com.amazonaws.lambda.durable.serde.SerDes;
11+
import com.fasterxml.jackson.core.JsonProcessingException;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
14+
import java.io.IOException;
15+
import java.time.Duration;
16+
import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
17+
import software.amazon.awssdk.core.SdkSystemSetting;
18+
import software.amazon.awssdk.http.apache.ApacheHttpClient;
19+
import software.amazon.awssdk.regions.Region;
20+
import software.amazon.awssdk.services.lambda.LambdaClient;
21+
22+
/**
23+
* Example demonstrating custom configuration with both custom HTTP client and custom SerDes. Shows how to configure a
24+
* custom Apache HTTP client for the Lambda client while maintaining automatic credentials detection and region
25+
* fallback, plus a custom SerDes with snake_case naming.
26+
*
27+
* <p>This example demonstrates:
28+
*
29+
* <ul>
30+
* <li>Custom Apache HTTP client configuration for improved performance
31+
* <li>Automatic region detection with fallback to us-east-1 for testing environments
32+
* <li>Environment variable credentials provider
33+
* <li>Custom SerDes with snake_case property naming
34+
* </ul>
35+
*/
36+
public class CustomConfigExample extends DurableHandler<String, String> {
37+
38+
@Override
39+
protected DurableConfig createConfiguration() {
40+
// Configure custom Apache HTTP client for better performance
41+
var httpClient = ApacheHttpClient.builder()
42+
.maxConnections(50)
43+
.connectionTimeout(Duration.ofSeconds(30))
44+
.socketTimeout(Duration.ofSeconds(60))
45+
.build();
46+
47+
// Get region with fallback to us-east-1 if AWS_REGION not set
48+
// This prevents initialization failures in testing environments
49+
var region = System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable());
50+
if (region == null || region.isEmpty()) {
51+
region = "us-east-1";
52+
}
53+
54+
// Create Lambda client with custom HTTP client
55+
// Uses automatic credentials detection and region fallback
56+
var lambdaClient = LambdaClient.builder()
57+
.httpClient(httpClient)
58+
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
59+
.region(Region.of(region))
60+
.build();
61+
62+
// Wrap the Lambda client with LambdaDurableFunctionsClient
63+
var durableClient = new LambdaDurableFunctionsClient(lambdaClient);
64+
65+
// Create custom SerDes with snake_case naming
66+
var customSerDes = new SnakeCaseSerDes();
67+
68+
return DurableConfig.builder()
69+
.withDurableExecutionClient(durableClient)
70+
.withSerDes(customSerDes)
71+
.build();
72+
}
73+
74+
@Override
75+
public String handleRequest(String input, DurableContext context) {
76+
// Step 1: Create a custom object with camelCase fields to demonstrate snake_case serialization
77+
var customObject = context.step(
78+
"create-custom-object",
79+
CustomData.class,
80+
() -> new CustomData("user123", "John Doe", 25, "john.doe@example.com"));
81+
82+
return "Created custom object: " + customObject.userId + ", " + customObject.fullName + ", "
83+
+ customObject.userAge + ", " + customObject.emailAddress;
84+
}
85+
86+
/**
87+
* Custom data class with camelCase field names to demonstrate snake_case serialization. The SerDes will convert
88+
* these field names to snake_case in the JSON output.
89+
*/
90+
public static class CustomData {
91+
public String userId;
92+
public String fullName;
93+
public int userAge;
94+
public String emailAddress;
95+
96+
public CustomData() {}
97+
98+
public CustomData(String userId, String fullName, int userAge, String emailAddress) {
99+
this.userId = userId;
100+
this.fullName = fullName;
101+
this.userAge = userAge;
102+
this.emailAddress = emailAddress;
103+
}
104+
}
105+
106+
/**
107+
* Custom SerDes implementation using snake_case property naming. Demonstrates how to provide custom serialization
108+
* behavior.
109+
*/
110+
private static class SnakeCaseSerDes implements SerDes {
111+
private final ObjectMapper objectMapper;
112+
113+
public SnakeCaseSerDes() {
114+
this.objectMapper = new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
115+
}
116+
117+
@Override
118+
public String serialize(Object obj) {
119+
try {
120+
return objectMapper.writeValueAsString(obj);
121+
} catch (JsonProcessingException e) {
122+
throw new RuntimeException("Serialization failed", e);
123+
}
124+
}
125+
126+
@Override
127+
public <T> T deserialize(String json, Class<T> clazz) {
128+
try {
129+
return objectMapper.readValue(json, clazz);
130+
} catch (IOException e) {
131+
throw new RuntimeException("Deserialization failed", e);
132+
}
133+
}
134+
135+
@Override
136+
public <T> T deserialize(String json, TypeToken<T> typeToken) {
137+
try {
138+
return objectMapper.readValue(json, objectMapper.constructType(typeToken.getType()));
139+
} catch (IOException e) {
140+
throw new RuntimeException("Deserialization failed", e);
141+
}
142+
}
143+
}
144+
}

examples/src/main/java/com/amazonaws/lambda/durable/examples/GenericTypesExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public Output(List<String> items, Map<String, Integer> counts, Map<String, List<
4848
}
4949

5050
@Override
51-
protected Output handleRequest(Input input, DurableContext context) {
51+
public Output handleRequest(Input input, DurableContext context) {
5252
logger.info("Starting generic types example for user: {}", input.userId);
5353

5454
// Step 1: Fetch a list of items (List<String>)

examples/src/main/java/com/amazonaws/lambda/durable/examples/RetryExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class RetryExample extends DurableHandler<Object, String> {
2929
private Instant startTime;
3030

3131
@Override
32-
protected String handleRequest(Object input, DurableContext context) {
32+
public String handleRequest(Object input, DurableContext context) {
3333
// Step 1: Record start time
3434
startTime = context.step("record-start-time", Instant.class, Instant::now);
3535
logger.info("Recorded start time: {}", startTime);

examples/src/main/java/com/amazonaws/lambda/durable/examples/RetryInProcessExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class RetryInProcessExample extends DurableHandler<Object, String> {
3131
private final AtomicInteger attemptCount = new AtomicInteger(0);
3232

3333
@Override
34-
protected String handleRequest(Object input, DurableContext context) {
34+
public String handleRequest(Object input, DurableContext context) {
3535
logger.info("Starting retry in-process example");
3636

3737
// Start async step that will fail and retry

examples/src/main/java/com/amazonaws/lambda/durable/examples/SimpleStepExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
public class SimpleStepExample extends DurableHandler<GreetingRequest, String> {
2020

2121
@Override
22-
protected String handleRequest(GreetingRequest input, DurableContext context) {
22+
public String handleRequest(GreetingRequest input, DurableContext context) {
2323
// Step 1: Create greeting
2424
var greeting = context.step("create-greeting", String.class, () -> "Hello, " + input.getName());
2525

examples/src/main/java/com/amazonaws/lambda/durable/examples/WaitAtLeastExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class WaitAtLeastExample extends DurableHandler<GreetingRequest, String>
2828
private static final Logger logger = LoggerFactory.getLogger(WaitAtLeastExample.class);
2929

3030
@Override
31-
protected String handleRequest(GreetingRequest input, DurableContext context) {
31+
public String handleRequest(GreetingRequest input, DurableContext context) {
3232
logger.info("Starting concurrent step + wait example for: {}", input.getName());
3333

3434
// Start an async step that takes 2 seconds

examples/src/main/java/com/amazonaws/lambda/durable/examples/WaitAtLeastInProcessExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class WaitAtLeastInProcessExample extends DurableHandler<GreetingRequest,
2828
private static final Logger logger = LoggerFactory.getLogger(WaitAtLeastInProcessExample.class);
2929

3030
@Override
31-
protected String handleRequest(GreetingRequest input, DurableContext context) {
31+
public String handleRequest(GreetingRequest input, DurableContext context) {
3232
logger.info("Starting concurrent step + wait example for: {}", input.getName());
3333

3434
// Start an async step that takes 10 seconds

examples/src/main/java/com/amazonaws/lambda/durable/examples/WaitExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
public class WaitExample extends DurableHandler<GreetingRequest, String> {
2323

2424
@Override
25-
protected String handleRequest(GreetingRequest input, DurableContext context) {
25+
public String handleRequest(GreetingRequest input, DurableContext context) {
2626
// Step 1: Start processing
2727
var started = context.step("start-processing", String.class, () -> "Started processing for " + input.getName());
2828

examples/src/test/java/com/amazonaws/lambda/durable/examples/CloudBasedIntegrationTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,33 @@ void testGenericTypesExample() {
194194
assertNotNull(runner.getOperation("count-by-category"));
195195
assertNotNull(runner.getOperation("fetch-categories"));
196196
}
197+
198+
@Test
199+
void testCustomConfigExample() {
200+
var runner = CloudDurableTestRunner.create(arn("custom-config-example"), String.class, String.class);
201+
var result = runner.run("test-input");
202+
203+
assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus());
204+
205+
var finalResult = result.getResult(String.class);
206+
assertNotNull(finalResult);
207+
assertTrue(finalResult.contains("Created custom object"));
208+
assertTrue(finalResult.contains("user123"));
209+
assertTrue(finalResult.contains("John Doe"));
210+
assertTrue(finalResult.contains("25"));
211+
assertTrue(finalResult.contains("john.doe@example.com"));
212+
213+
// Verify the step operation was executed
214+
var createObjectOp = runner.getOperation("create-custom-object");
215+
assertNotNull(createObjectOp);
216+
assertEquals("create-custom-object", createObjectOp.getName());
217+
218+
// The step result should contain the serialized JSON with snake_case
219+
var stepResult = createObjectOp.getStepDetails().result();
220+
assertNotNull(stepResult);
221+
assertTrue(stepResult.contains("user_id"));
222+
assertTrue(stepResult.contains("full_name"));
223+
assertTrue(stepResult.contains("user_age"));
224+
assertTrue(stepResult.contains("email_address"));
225+
}
197226
}

0 commit comments

Comments
 (0)