Skip to content

Custom Serialization feature for handler class that implements RequestHandler and uses method name handler syntax does not use exact types during serialization #552

@sigpwned

Description

@sigpwned

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions