question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Extension library for typed handlers

See original GitHub issue

A 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:closed
  • Created 6 years ago
  • Comments:12 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
tipsycommented, Mar 14, 2018

In that case this feature is not needed here, I just wanted to add an instrument to allow entity manipulation straightaway, without any complications with Context interactions.

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.

1reaction
tipsycommented, Mar 13, 2018

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).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Extension Handlers - Win32 apps - Microsoft Learn
The extension handler is a COM object that provides routines for encoding the more complex, but commonly used extensions and data types, ...
Read more >
Extensions - 1.64.0 - Boost C++ Libraries
The simplest extension just takes a single handler, which can be done in a functional style. So let's start with a simple hello-world...
Read more >
APL Extensions | Alexa Skills Kit - Amazon Developer
Extensions are optional enhancements to an APL runtime that provide additional sources of data, commands, and event handlers.
Read more >
What are Rust's HTTP extensions? - A Less Mad March
Today I'm going to explain what extensions really are (a set of values, keyed by type), and how you can use them to...
Read more >
App Extension Programming Guide: Handling Common ...
As you write custom code that performs your app extension's task, you may need to handle some scenarios that are common to many...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found