SparkJava: Separating routing and resources

You know how a lot of the tutorials out there always look so clean and neat, but when using it to other things than hello world, it just gets messed up?

When playing around with Spark, we didn't want to lose that cleanness, because a clean Spark application sure is beautiful. So while we got familiar with the framework, we decided to try something a bit different by separating the routing and the resources.

We saw that this also could have another advantage. Done right, this separation could prevent Spark code from getting tangled into our own code. Sure we enjoy Spark now, but you never know what the future brings. So by creating this decoupling, it would be easier to replace Spark if necessary.

The solution we ended up with was to have our resources return functions that could be executed in the routing class. The creation of these functions would be black boxed, so our resources didn't work directly on the Spark response object.

Let's start by looking at the class that black box the functions returned by our resources.

class MyResponse {

    public static ResponseCreator ok(String body) {
        return (req, res) -> {
            res.status(200);
            res.type("application/json");

            return body;
        };
    }

    public static ResponseCreator badRequest(String body) {
        return (req, res) -> {
            res.status(400);
            res.type("application/json");

            return body;
        };
    }

    // more methods creating responses
}

Let's also take a look at the interface that these return functions implement.

public interface ResponseCreator extends Route {}  

We just extend the Route interface to ensure our resources don't have any knowledge about Spark.

Let's now see how these higher-order functions could be used by our resources.

public class Resource {

    public ResponseCreator get(String id) {

        // parse JSON, do validation, fetch data, etc

        return MyResponse.ok("{\"some\": \"json\"}");
    }
}

As you can see, the resource just gets a path param as an argument. Then it does some work before sending a JSON to MyResponse.ok() that creates a function to return.

Finally, let's take a look at the routing class.

public class Application implements SparkApplication {

    private final Resource resource;

    public Application(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void init() {
        post("/", map((req, res) -> resource.create(req.body())));
        get("/", map((req, res) -> resource.getList()));
        get("/:id", map((req, res) -> resource.get(req.params(":id"))));
    }

    Route map(Converter c) {
        return (req, res) -> c.convert(req, res).handle(req,res);
    }

    private interface Converter {
        public ResponseCreator convert(Request req, Response res);
    }
}

Looking at the init method, there are two things worth noticing.

First, the routing functions have the responsibility of getting all the information the resources need from the request. This is the key to avoid tangling the request object into our resources.

Second, we have a simple map function that converts the response from our resources to a Route function that could be passed back to Spark.

It's worth mentioning that the map function should be extracted to a common place if you have several web applications. But for simplicity, we just added it to the routing class in this example.

You can find the source code of the example on GitHub.

Enjoyed the post?

If you don't want to miss future posts, make sure to subscribe