April 27, 2012

Scala + Spring MVC = True? (Part 2)

This is the second part in a series of articles about mixing Scala and Spring MVC. In the first post we looked at how to set up a Scala-Java Maven project and we saw how well Spring MVC translates over to Scala in a very simple example. In this post we will continue with our exploration of Spring MVC and Scala by extending our web application that we started on in the previous post so you might want to go get the example code on GitHub so you can follow along.


Jackson JSON Processor

Lets continue our example by turning our web application into a web service returning data in JSON format. Maybe it will even grow up and be a RESTful web service someday. The typical framework of choice for processing JSON in a Spring MVC application is the Jackson JSON Processor.

For those of you not familiar with using Jackson, it works by registering JSON specific message converters on the Spring MVC application and then the Spring MVC framework will take care of converting the POJOs used by your controllers to/from JSON. The Spring framework will register the message converters automatically if Jackson is on the class path of the application so you do not need to add any extra configuration for this.

We will now expand our our web service by giving it an echo feature. If we do a GET to http://localhost:9090/scala-spring-mvc/java/echo?name=Daniel the service will answer back with the name Daniel and the current day of the week, all formatted in JSON. Again, we start with the Java implementation of this and the controller method for the echo feature would look like this:
@RequestMapping("/echo")
@ResponseBody
public JavaEcho echo(@RequestParam String name) {
    return new JavaEcho(name, getWeekDay());
}
And the accompanying POJO would look like this:
public class JavaEcho {

    private final String name;
    private final String weekDay;

    public JavaEcho(@JsonProperty("name") String name,
                    @JsonProperty("weekDay") String weekDay) {
        this.name = name;
        this.weekDay = weekDay;
    }

    public String getName() {
        return name;
    }

    public String getWeekDay() {
        return weekDay;
    }

}

JavaEcho is a simple POJO containing two fields: name and weekDay. The extra JSON annotations are there because we made the POJO immutable. If it had been mutable we would not need them. But who wants mutable objects anyway?

If we just copy this over to Scala code we would end up with a controller method like this:
@RequestMapping(Array("/echo"))
@ResponseBody
def echo(name: String): ScalaEcho = {
    ScalaEcho(name, weekDay())
}
and the supporting class:
case class ScalaEcho(@JsonProperty("name") val name: String,
                     @JsonProperty("weekDay") val weekDay: String)
Now here is where we run into our first problem. If we would try to run this code it would not work because Jackson would not know what to do with the Scala objects returned by the controller methods. This is because Jackson, like many other frameworks, are expecting classes to follow the Java Bean naming convention for accessor methods and Scala classes does not do that by default.

In this simple example, we could solve this problem by annotating the class parameters with the Scala annotation @BeanProperty. This will tell the Scala compiler to generate accessor methods that follows the Java Bean convention and then the Jackson processor will work. After annotating our case class it will look like this:
case class ScalaEcho(@JsonProperty("name") @BeanProperty val name: String,
                     @JsonProperty("weekDay") @BeanProperty val weekDay: String)
But that approach has its limitations. Especially when it comes to deserializing JSON to Scala classes. If we for example adds a Tuple to our ScalaEcho class then the serialization process to JSON would work just fine but we would no longer be able to deserialize to ScalaEcho. To exemplify this, the test code below would fail during the deserialization process if we added a Tuple to the ScalaEcho class:
@Test
public void scalaEcho() {
    String weekDay = new SimpleDateFormat("E").format(new Date());

    ScalaEcho echo = createScalaTemplate().getForObject(baseUrl + "scala/echo?name={name}", ScalaEcho.class, "Daniel");

    assertEquals("Daniel", echo.name());
    assertEquals(weekDay, echo.weekDay());
}
An alternative solution to using @Beanproperty is to enable Jackson to serialize and deserialize Scala classes. To do this we need to use the Jackson module for Scala. The module will provide serialization and deserialization of case classes, sequences, maps, tuples, options, and enumerations. But it does have some limitations and I would suggest checking out the documentation and trying out more advanced examples yourself if you decide to go all in for this solution. You can refer to the example code for this post if you want to know how to register the module so that your message converter can use it.

Another effect of using the Scala module for Jackson is that now we can also remove the JSON specific annotations from our case class. The case class after introducing the Scala module will now look just like an ordinary case class. Nice and clean:
case class ScalaEcho(val name: String, val weekDay: String)
Once you have got Jackson working for processing Scala classes you can mix and match Java and Scala in your Spring MVC controllers. For example, you could have a Java controller return Scala objects or vice versa. It all works the same.

Next

So, to wrap up. We started out with setting up a Maven project for Scala and Java and learned how to create controllers that could respond to simple GET requests. We then added an echo feature that returned JSON formatted data and that's when we ran into our first trouble. We looked at different ways to solve the problem, caused by Scala classes not following the Java Bean conventions, and found out that the most flexible and clean solution was to use the Scala module for Jackson.
In the next part of this series we will continue to expand our web service and look at how we can use JSR-303 bean validation to validate input data.

I highly recommend playing around with these features yourself to really get a feeling of how it works and you can use the example code for this post to quickly get up and running the examples we have talked about.

1 comment: