Skip to content

Commit 65f00fe

Browse files
kjswaruphphipag
andauthored
feat: Support CRaC priming of powertools validation (#2081)
* feat: support CRaC priming of powertools validation - Add org.crac dependency and generate-classesloaded-file profile in pom.xml - Add classesloaded.txt in src/main/resources for automatic class preloading - Update ValidationConfig to register for Crac resource - Update spotbugs-exclude.xml to suppress RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT spotbug error in beforeCheckpoint method * test: add ValidationConfigTest to assert beforeCheckpoint and afterRestore hooks do not throw exception * docs: Add SnapStart priming subsection for validation * test: Remove public modifier from ValidationConfigTest class and methods * docs: Improve Lambda SnapStart priming section Co-authored-by: Philipp Page <[email protected]> --------- Co-authored-by: Philipp Page <[email protected]>
1 parent 7594e5a commit 65f00fe

File tree

6 files changed

+7434
-1
lines changed

6 files changed

+7434
-1
lines changed

docs/utilities/validation.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,53 @@ If you need to configure the Jackson ObjectMapper, you can use the `ValidationCo
292292
}
293293
}
294294
```
295+
296+
## Advanced
297+
298+
### Lambda SnapStart priming
299+
300+
The Validation utility integrates with AWS Lambda SnapStart to improve restore durations. To make sure the SnapStart priming logic of this utility runs correctly, you need an explicit reference to `ValidationConfig` in your code to allow the library to register before SnapStart takes a memory snapshot. Learn more about what priming is in this [blog post](https://aws.amazon.com/blogs/compute/optimizing-cold-start-performance-of-aws-lambda-using-advanced-priming-strategies-with-snapstart/){target="_blank"}.
301+
302+
If you don't set a custom `ValidationConfig` in your code yet, make sure to reference `ValidationConfig` in your Lambda handler initialization code. This can be done by adding one of the following lines to your handler class:
303+
304+
=== "Constructor"
305+
306+
```java hl_lines="7"
307+
import software.amazon.lambda.powertools.validation.Validation;
308+
import software.amazon.lambda.powertools.validation.ValidationConfig;
309+
310+
public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
311+
312+
public MyFunctionHandler() {
313+
ValidationConfig.get(); // Ensure ValidationConfig is loaded for SnapStart
314+
}
315+
316+
@Override
317+
@Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json")
318+
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
319+
// ...
320+
return something;
321+
}
322+
}
323+
```
324+
325+
=== "Static Initializer"
326+
327+
```java hl_lines="7"
328+
import software.amazon.lambda.powertools.validation.Validation;
329+
import software.amazon.lambda.powertools.validation.ValidationConfig;
330+
331+
public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
332+
333+
static {
334+
ValidationConfig.get(); // Ensure ValidationConfig is loaded for SnapStart
335+
}
336+
337+
@Override
338+
@Validation(inboundSchema = "classpath:/schema_in.json", outboundSchema = "classpath:/schema_out.json")
339+
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
340+
// ...
341+
return something;
342+
}
343+
}
344+
```

powertools-validation/pom.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
<groupId>com.amazonaws</groupId>
7272
<artifactId>aws-lambda-java-serialization</artifactId>
7373
</dependency>
74+
<dependency>
75+
<groupId>org.crac</groupId>
76+
<artifactId>crac</artifactId>
77+
</dependency>
7478

7579
<!-- Test dependencies -->
7680
<dependency>
@@ -119,4 +123,25 @@
119123
<scope>test</scope>
120124
</dependency>
121125
</dependencies>
126+
127+
<profiles>
128+
<profile>
129+
<id>generate-classesloaded-file</id>
130+
<build>
131+
<plugins>
132+
<plugin>
133+
<groupId>org.apache.maven.plugins</groupId>
134+
<artifactId>maven-surefire-plugin</artifactId>
135+
<configuration>
136+
<argLine>
137+
-Xlog:class+load=info:classesloaded.txt
138+
--add-opens java.base/java.util=ALL-UNNAMED
139+
--add-opens java.base/java.lang=ALL-UNNAMED
140+
</argLine>
141+
</configuration>
142+
</plugin>
143+
</plugins>
144+
</build>
145+
</profile>
146+
</profiles>
122147
</project>

powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
import com.fasterxml.jackson.databind.JsonNode;
1818
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import com.networknt.schema.JsonSchema;
1920
import com.networknt.schema.JsonSchemaFactory;
2021
import com.networknt.schema.SpecVersion;
2122
import io.burt.jmespath.JmesPath;
2223
import io.burt.jmespath.function.BaseFunction;
24+
import org.crac.Context;
25+
import org.crac.Core;
26+
import org.crac.Resource;
27+
import software.amazon.lambda.powertools.common.internal.ClassPreLoader;
2328
import software.amazon.lambda.powertools.utilities.JsonConfig;
2429
import software.amazon.lambda.powertools.utilities.jmespath.Base64Function;
2530
import software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction;
@@ -31,10 +36,15 @@
3136
* For everything but the validation features (factory, schemaVersion), {@link ValidationConfig}
3237
* is just a wrapper of {@link JsonConfig}.
3338
*/
34-
public class ValidationConfig {
39+
public class ValidationConfig implements Resource {
3540
private SpecVersion.VersionFlag jsonSchemaVersion = SpecVersion.VersionFlag.V7;
3641
private JsonSchemaFactory factory = JsonSchemaFactory.getInstance(jsonSchemaVersion);
3742

43+
// Static block to ensure CRaC registration happens at class loading time
44+
static {
45+
Core.getGlobalContext().register(get());
46+
}
47+
3848
private ValidationConfig() {
3949
}
4050

@@ -102,6 +112,26 @@ public ObjectMapper getObjectMapper() {
102112
return JsonConfig.get().getObjectMapper();
103113
}
104114

115+
@Override
116+
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
117+
// Initialize key components
118+
getObjectMapper();
119+
getJmesPath();
120+
getFactory();
121+
122+
// Dummy validation
123+
String sampleSchema = "{\"type\":\"object\"}";
124+
JsonSchema schema = ValidationUtils.getJsonSchema(sampleSchema);
125+
ValidationUtils.validate("{\"test\":\"dummy\"}", schema);
126+
127+
ClassPreLoader.preloadClasses();
128+
}
129+
130+
@Override
131+
public void afterRestore(Context<? extends Resource> context) throws Exception {
132+
// No action needed after restore
133+
}
134+
105135
private static class ConfigHolder {
106136
private static final ValidationConfig instance = new ValidationConfig();
107137
}

0 commit comments

Comments
 (0)