Skip to content

Add support to define TableSchema as part of Spring context and inject it into DefaultDynamoDbTableSchemaResolver #674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
gvart opened this issue Feb 15, 2023 · 1 comment · Fixed by #957
Labels
component: dynamodb DynamoDB integration related issue type: enhancement Smaller enhancement in existing integration
Milestone

Comments

@gvart
Copy link
Contributor

gvart commented Feb 15, 2023

Type: Feature

Is your feature request related to a problem? Please describe.
When I'm trying to use io.awspring.cloud:spring-cloud-aws-starter-dynamodb with GraalVM native-image, the application fails at runtime during TableSchema creation:

return tableSchemaCache.computeIfAbsent(tableName, entityClassName -> TableSchema.fromBean(clazz));
,

It fails due to the fact that AWS SDK trying to generate a class at runtime, that is not supported by native-image (Related issue: oracle/graal#3386)

Stacktrace example:

2023-02-15T12:18:19.678Z ERROR 9 --- [pool-3-thread-1] c.e.perister.entrypoint.LambdaFunction   : Failed to process records
--
java.lang.IllegalArgumentException: Failed to generate method handle.
at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.LambdaToMethodBridgeBuilder.build(LambdaToMethodBridgeBuilder.java:92) ~[na:na]
at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ObjectConstructor.create(ObjectConstructor.java:37) ~[na:na]
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.newObjectSupplierForClass(BeanTableSchema.java:375) ~[na:na]
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.createStaticTableSchema(BeanTableSchema.java:182) ~[na:na]
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:138) ~[na:na]
at software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema.create(BeanTableSchema.java:129) ~[na:na]
at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromBean(TableSchema.java:83) ~[application:na]
at io.awspring.cloud.dynamodb.DefaultDynamoDbTableSchemaResolver.lambda$resolve$0(DefaultDynamoDbTableSchemaResolver.java:32) ~[na:na]
at java.base@17.0.5/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[application:na]
at io.awspring.cloud.dynamodb.DefaultDynamoDbTableSchemaResolver.resolve(DefaultDynamoDbTableSchemaResolver.java:32) ~[na:na]
at io.awspring.cloud.dynamodb.DynamoDbTemplate.prepareTable(DynamoDbTemplate.java:100) ~[application:3.0.0-RC1]
at io.awspring.cloud.dynamodb.DynamoDbTemplate.save(DynamoDbTemplate.java:52) ~[application:3.0.0-RC1]
at com.everynote.perister.gateway.dynamodb.DefaultSaveNoteRequest.save(DefaultSaveNoteRequest.java:30) ~[application:na]
at com.everynote.perister.domain.ProcessNoteRequest.process(ProcessNoteRequest.java:18) ~[application:na]
at java.base@17.0.5/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) ~[na:na]
at java.base@17.0.5/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
at java.base@17.0.5/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625) ~[na:na]
at java.base@17.0.5/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[application:na]
at java.base@17.0.5/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[application:na]
at java.base@17.0.5/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150) ~[application:na]
at java.base@17.0.5/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) ~[na:na]
at java.base@17.0.5/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[application:na]
at java.base@17.0.5/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[application:na]
at com.everynote.perister.entrypoint.LambdaFunction.accept(LambdaFunction.java:30) ~[application:na]
at com.everynote.perister.entrypoint.LambdaFunction.accept(LambdaFunction.java:13) ~[application:na]
at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeConsumer(SimpleFunctionRegistry.java:973) ~[na:na]
at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.doApply(SimpleFunctionRegistry.java:684) ~[na:na]
at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.apply(SimpleFunctionRegistry.java:533) ~[na:na]
at org.springframework.cloud.function.adapter.aws.CustomRuntimeEventLoop.eventLoop(CustomRuntimeEventLoop.java:167) ~[na:na]
at org.springframework.cloud.function.adapter.aws.CustomRuntimeEventLoop.lambda$run$0(CustomRuntimeEventLoop.java:89) ~[na:na]
at java.base@17.0.5/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[application:na]
at java.base@17.0.5/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base@17.0.5/java.lang.Thread.run(Thread.java:833) ~[application:na]
at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775) ~[application:na]
at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203) ~[na:na]
Caused by: java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported.
at java.base@17.0.5/java.lang.invoke.InnerClassLambdaMetafactory.generateInnerClass(InnerClassLambdaMetafactory.java:413) ~[na:na]
at java.base@17.0.5/java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:315) ~[na:na]
at java.base@17.0.5/java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:228) ~[na:na]
at java.base@17.0.5/java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:341) ~[na:na]
at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.LambdaToMethodBridgeBuilder.build(LambdaToMethodBridgeBuilder.java:83) ~[na:na]
... 34 common frames omitted
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported.
at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
at java.base@17.0.5/java.lang.ClassLoader.defineClass0(ClassLoader.java:338) ~[application:na]
at java.base@17.0.5/java.lang.System$2.defineClass(System.java:2307) ~[na:na]
at java.base@17.0.5/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2439) ~[na:na]
at java.base@17.0.5/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClassAsLookup(MethodHandles.java:2420) ~[na:na]
at java.base@17.0.5/java.lang.invoke.MethodHandles$Lookup.defineHiddenClass(MethodHandles.java:2127) ~[application:na]
at java.base@17.0.5/java.lang.invoke.InnerClassLambdaMetafactory.generateInnerClass(InnerClassLambdaMetafactory.java:407) ~[na:na]
... 38 common frames omitted

Describe the solution you'd like
Since the current implementation of the TableSchema generation is not working with native-image (and a workaround for that would be to use StaticTableSchema and to simplify usage of it as a part of the Spring ecosystem I'd recommend to let developers define beans of type software.amazon.awssdk.enhanced.dynamodb.TableSchema, which can be used to prefill the cache in DefaultDynamoDbTableSchemaResolver

Describe alternatives you've considered
As an alternative solution without any changes in the framework, I'd have to implement DefaultDynamoDbTableSchemaResolver to do my own TableSchema management and add that class to the Spring Context.

Additional context
The solution seems to be pretty simple. A pseudo-code example:

@Configuration
public class MyConfig {
   
   @Bean
    public TableSchema myEntitySchema() {
        return StaticTableSchema.builder(MyEntity.class)
                //..some configs
                .build();
    }

   @Bean
    public TableSchema mySecondEntitySchema() {
        return StaticTableSchema.builder(MySecondEntity.class)
                //..some configs
                .build();
    }
}

Add a method to prefill the cache in DefaultDynamoDbTableSchemaResolver (this method can be invoked from the autoconfiguration)

public class DefaultDynamoDbTableSchemaResolver implements DynamoDbTableSchemaResolver {
	private final Map<String, TableSchema> tableSchemaCache = new ConcurrentHashMap<>();

	@Override
	public <T> TableSchema<T> resolve(Class<T> clazz, String tableName) {
		return tableSchemaCache.computeIfAbsent(tableName, entityClassName -> TableSchema.fromBean(clazz));
	}

   public void prefillCache(String tableName, TableSchema schema) {
      tableSchemaCache.put(tableName, schema);
   }
}

And finally, in DynamoDbAutoConfiguration we can inject a list of TableSchema beans and prefill the cache for the DynamoDbTableSchemaResolver. (note the initial entity's class can be retrieved from TableSchema.itemType().rawClass()

In this way, the current behavior of the DynamoDbTableSchemaResolver is not changed (i.e. for classes that derive their schema using TableSchema.fromBean), but also it allows developers to define their schema by themselves.

If that solution is feasible I can implement it

@gvart gvart changed the title Since GraalVM doesn't support Add support to define TableSchema as part of Spring context and inject it into DefaultDynamoDbTableSchemaResolver Feb 15, 2023
@maciejwalkowiak
Copy link
Contributor

Sounds good! Go for it @gvart!

@maciejwalkowiak maciejwalkowiak added component: dynamodb DynamoDB integration related issue type: enhancement Smaller enhancement in existing integration labels Feb 16, 2023
@maciejwalkowiak maciejwalkowiak added this to the 3.1 milestone Feb 16, 2023
gvart added a commit to gvart/spring-cloud-aws that referenced this issue Feb 17, 2023
@maciejwalkowiak maciejwalkowiak modified the milestones: 3.1, 3.1.0 Nov 6, 2023
maciejwalkowiak added a commit that referenced this issue Nov 15, 2023
maciejwalkowiak added a commit that referenced this issue Nov 29, 2023
maciejwalkowiak added a commit that referenced this issue Dec 10, 2023
maciejwalkowiak added a commit that referenced this issue Dec 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: dynamodb DynamoDB integration related issue type: enhancement Smaller enhancement in existing integration
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants