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.
- We could create different Lambdas with their own payloads. So one Lambda for the create, one for the read, etc.
- 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()
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)
We can then reference this FunctionBean in the implementation of our MicronautRequestStreamHandler
:
@Introspected
class EventGatewayRequestStreamHandler : MicronautRequestStreamHandler() {
override fun resolveFunctionName(env: Environment): String {
return "EventGatewayManager"
}
}
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"
}
The response will be the result of the expression defined in EventGatewayManager.apply()
{
"success": true
}
Example can be found here:
Example repo using subtypes with MicronautRequestStreamHandler
Blog can be found here
Discussion (0)