-
Notifications
You must be signed in to change notification settings - Fork 239
Description
The custom serialization feature does not use the exact types of type parameters in the presence of inheritance when (a) the implementation class implements RequestHandler
; and (b) the configured handler uses method name syntax (as opposed to class name syntax, so, com.example.ExampleLambdaFunction#requestHandler
).
For example, consider the below Lambda implementation with the given custom serializer. Note that the ExampleLambdaFunction
is the actual handler class, and it extends LambdaFunctionBase
, which implements RequestHandler
and uses generic type parameters for the handleRequest
method parameters and result.
package com.example;
public abstract class LambdaFunctionBase<InputT, OutputT>
implements RequestHandler<InputT, OutputT> {
@Override
public OutputT handleRequest(InputT input, Context context) {
System.out.println("Enter handleRequest");
OutputT result = doHandleRequest(input, context);
System.out.println("Exit handleRequest");
return result;
}
protected abstract OutputT doHandleRequest(InputT input, Context context);
}
public class ExampleLambdaFunction
extends LambdaFunctionBase<ExampleRequest, ExampleResponse> {
@Override
public ExampleResponse doHandleRequest(ExampleRequest request) {
ExampleResponse result;
result = businessLogic(request);
return result;
}
}
public class ExampleCustomPojoSerializer
implements CustomPojoSerializer {
@Override
public <T> T fromJson(InputStream input, Type type) {
System.err.println("fromJson(" + type + ")");
return deserializeImplementation(input, type);
}
@Override
public <T> T fromJson(String input, Type type) {
System.err.println("fromJson(" + type + ")");
return deserializeImplementation(input, type);
}
@Override
public <T> void toJson(T value, OutputStream output, Type type) {
System.err.println("toJson(" + type + ")");
return serializeImplementation(value, output, type);
}
}
If the configured handler looks like com.example.ExampleLambdaFunction
, then the result of creating, deploying, and invoking the lambda would produce the following output on stderr:
fromJson(ExampleRequest)
toJson(ExampleResponse)
So everything works as expected.
However, if the configured handler looks like com.example.ExampleLambdaFunction::handleRequest
, then the result is different:
fromJson(InputT)
toJson(OutputT) // <-- Assuming serialization succeeded, and the lambda completed successfully
Having poked around in the code a bit, it looks like the difference is due to code in aws-lambda-java-runtime-interface-client. If we look at EventHandler#getHandlerFromOverload(Class,Method)
, we see that it creates the LambdaRequestHandler
from the literal return and parameter types of the given method. However, we know that in this example, those are going to be generic type parameters. It needs to use the exact return type of the method and the exact parameter types of the method (examples linked from the excellent https://github.com/leangen/geantyref library, purely for illustration and reference).
The call stack looks like this when the error occurs:
EventHandlerLoader::loadEventHandler // <-- Branch in this method is why handler syntax matters
EventHandlerLoader::loadEventPojoHandler
EventHandlerLoader::getHandlerFromOverload // <-- Error is here
The feature works as expected when the handler looks like com.example.ExampleLambdaFunction
because in EventHandlerLoader#loadEventHandler
, control flows through EventHandler.loadStreamingRequestHandler
, which calls through methods to handle generic types, i.e., EventHandlerLoader#wrapPojoHandler
.
The feature does not work as expected when the handler looks like com.example.ExampleLambdaFunction::handleRequest
because in EventHandlerLoader#loadEventHandler
, control flows through EventHandler.loadEventPojoHandler
, which uses the literal types of the indicated method, as noted above.
It's not clear whether or not this is a "bug", per se. Based on the code, it seems like the intent is that developers use com.example.ExampleLambdaFunction
syntax when their implementation inherits from RequestHandler
, and com.example.ExampleLambdaFunction::methodName
when it doesn't. On the one hand, method name syntax should always work as expected. On the other hand, if the user uses method name syntax for a class that inherits from RequestHandler, it's "belt and suspenders" at best, and potentially confusing at worst.
In any case, I lost some time debugging my custom serializer due to this issue, so I thought I'd write it up. Fortunately, there should be a pretty simple fix, if the team agrees that this is a bug. (I'd be happy to contribute a patch!) Otherwise, a line in the documentation like "If your class implements RequestHandler, then use com.example.ExampleLambdaFunction
syntax; otherwise, use com.example.ExampleLambdaFunction::methodName
syntax. If your RequestHandler implementation uses method name syntax, you might get unexpected behavior in some cases." might be useful to others. (Please ignore if that documentation already exists! I looked, but didn't find it.)
Please let me know if you have any questions, or if I can help in any way!