the/experts. Blog

Cover image for Building a Micronaut AWS Lambda API with Jackson Subtypes.
Patrick Dronk
Patrick Dronk

Posted on • Updated on

Building a Micronaut AWS Lambda API with Jackson Subtypes.

What we wanted

We had to build an API that was going to be used by multiple teams. Those teams could manage (CRUD) their subscriptions on certain events via our API.
 

How we started

We had an initial discussion on our approach.

  1. We could create different Lambdas with their own payloads. So one Lambda for the create, one for the read, etc.
  2. We could also create a Lambda that accepts multiple payloads and handles them according to the type. Jackson subtypes would be our solution here. The example below shows an example of subtypes with Jackson. To put it simply via the property type we can define which class it needs to serialize to
@Introspected(accessKind = [Introspected.AccessKind.FIELD])
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = PutSubscriptionCommand::class, name = "putSubscriptionCommand"),
)
abstract class Command

data class PutSubscriptionCommand(val subscriptionId: String): Command()
Enter fullscreen mode Exit fullscreen mode

The Lambda would be rather straightforward (so we thought)
We would simply use the MicronautRequestHandler<Command, Response> and it would give us the proper subtype.
 

The problem

The MicronautRequestHandler extends the AWS RequestHandler, this RequestHandler has its own serializer.
That provided serializer is not able to handle the Jackson Subtypes. The docs of AWS describe that if you need to do custom serialization you need to implement the AWS RequestStreamHandler
 

How we solved it

Luckily Micronaut follows the same approach as AWS and provides the MicronautRequestStreamHandler. However, there isn't a lot of docs that go into depth about how you should implement that MicronautRequestStreamHandler. Luckily the code behind the implementation is thoroughly tested and gave a good solution direction.
The main difference between the MicronautRequestHandler and MicronautRequestStreamHandler is that the MicronautRequestHandler implements the Micronaut FunctionBean, so the simple solution is to create your own:

@Introspected
@FunctionBean("EventGatewayManager")
class EventGatewayManager(): Function<Command, Response> {
    override fun apply(t: Command): Response {
        return Response(t is PutSubscriptionCommand)
    }
}

@Introspected(accessKind = [Introspected.AccessKind.FIELD])
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = PutSubscriptionCommand::class, name = "putSubscriptionCommand"),
)
abstract class Command

data class PutSubscriptionCommand(val subscriptionId: String): Command()

data class Response(val success: Boolean)
Enter fullscreen mode Exit fullscreen mode

We can then reference this FunctionBean in the implementation of our MicronautRequestStreamHandler:

@Introspected
class EventGatewayRequestStreamHandler : MicronautRequestStreamHandler() {
    override fun resolveFunctionName(env: Environment): String {
        return "EventGatewayManager"
    }
}
Enter fullscreen mode Exit fullscreen mode

Since we are now using the MicronautRequestStreamHandler we are using the serialization configured by Micronaut (which does support the subtypes)

We can now deploy this lambda and test it with the following payload:

{
  "type": "putSubscriptionCommand",
  "subscriptionId": "someSubscriptionId"
}
Enter fullscreen mode Exit fullscreen mode

The response will be the result of the expression defined in EventGatewayManager.apply()

{
  "success": true
}
Enter fullscreen mode Exit fullscreen mode

Example can be found here:

Example repo using subtypes with MicronautRequestStreamHandler

Blog can be found here

Discussion (0)