Extension library for typed handlers
See original GitHub issueA little bit complex proposition here, though it could simplify life for some people writing services on Javalin. I will name it TypedHandler
here.
After a little bit of experimenting with request and response transformers I’ve noticed that they can be used to allow handlers like (T) -> R
(Kotlin lambda syntax) or R method (T param)
. This will allow almost painless parse of request and response and much cleaner controllers.
The proposition syntax
Let’s imagine we have a controller which takes an JSON body from POST request, does something with it and returns result in JSON. (JSON can be replaced with any kind of serialization)
This controller will be a function from PostBody
instance to PostResponse
instance. (PostResponse doSomething(PostBody body)
)
Now to translate Context
to PostBody
and back to PostResponse
we have to define a handler or use custom wrapper. However, if we can define a functional interface like this:
interface TypedHandler<Req, Res> {
Res handle(Req request);
}
and the Javalin
instance method like this:
public <Req, Res> Javalin get(String path, Transformer<Req, Res> transformer, TypedHandler<Req, Res> handler) {
get(path, ctx -> transformer.response.tranform(handler.handle(tranformer.request.transform(ctx)), ctx);
}
then the controller method doSomething
can be utilized straightaway (if transformer is predefined):
javalin.get("some-path", transformer, controller::doSomething)
Transformers
The transformer itself has the following interface:
interface RequestTransformer<Request> {
fun transform(ctx: Context): Request
}
interface ResponseTransformer<Response> {
fun transform(value: Response, ctx: Context)
}
interface Transformer<Request, Response> {
val request: RequestTransformer<Request>
val response: ResponseTransformer<Response>
}
Examples: (JsonTransformer)
class JsonRequestTransformer<T>(private val mapper: ObjectMapper, val cls: Class<T>) : RequestTransformer<T> {
override fun transform(ctx: Context): T = mapper.readValue(ctx.request().inputStream, cls)
}
class JsonResponseTransformer<T>(private val mapper: ObjectMapper): ResponseTransformer<T> {
override fun transform(value: T, ctx: Context) {
mapper.writeValue(ctx.response().outputStream, value)
}
}
class JsonTransformer<In, Out>(mapper: ObjectMapper, cls: Class<In>): Transformer<In, Out> {
companion object {
inline fun <reified T, reified R> get(mapper: ObjectMapper) = JsonTransformer<T, R>(mapper, T::class.java)
}
override val request = JsonRequestTransformer(mapper, cls)
override val response = JsonResponseTransformer<Out>(mapper)
}
Example with custom Jackson features (view class):
data class View<T>(val view: Class<in T>, val value: T)
class JsonViewResponseTransformer<T>(private val mapper: ObjectMapper): ResponseTransformer<View<T>> {
override fun transform(value: View<T>, ctx: Context) {
mapper.writerWithView(value.view)
.writeValue(ctx.response().outputStream, value.value)
}
}
There could be some inaccurate code definitions here, as I was writing that from memory (tested some days ago).
EDIT:
After some thoughts, I noticed that transformers can be used as an entity to produce the Handler
:
interface TypedRequestHandler<Req> {
void handle(Req request)
}
interface RequestTransformer<Req> {
Req transform(Context context);
default Handler apply(TypedRequestHandler<Req> handler) {
return ctx -> handler.handle(transform(ctx))
}
}
// The same for response
interface TypedResponseHandler<Res> {
Res handle()
}
interface ResponseTransformer<Res> {
void transform(value: Res, Context context);
default Handler apply(TypedResponseHandler<Res> handler) {
return ctx -> transform(handler.handle(), ctx);
}
}
// For both TypedHandler is used (described above)
interface Transformer<Req, Res> {
RequestTranformer<Req> request();
ResponseTransformer<Res> response();
default Handler apply(TypedHandler<Req, Res> handler) {
return ctx -> response.transform(handler.handle(request.transform(ctx)), ctx);
}
}
Usage:
get("path", transformer.apply(controller::doSomething));
Issue Analytics
- State:
- Created 6 years ago
- Comments:12 (12 by maintainers)
Top GitHub Comments
Let’s not close it, I think the feature sounds interesting. What worries me the most is intimidating and/or confusing users. It would be helpful if you could show how this helps you structure your apps by creating a bigger example, preferably with some fake business logic.
I’ve been trying to understand what this is supposed to do for almost an hour, I need you to ELI5 (showing the controller code should be enough).