Moving from Grails to Spring Boot part 2: Spring Boot controllers
After having migrated our Grails services to vanilla Spring Boot, let’s see some tips for migrating a Grails controller to a vanilla Spring Boot one.
Define interfaces
The first step is to define interfaces on top of controllers. It’s a simple tip, but it will ensure our new controller is consistent with the former one. For example, if we have this controller
We define that interface
and make the Grails controller implement it.
Two things that stand out here. The @CompileStatic
annotation that will fail at compile time if the interface is not fully implemented. And the return
statement after the render
one, which is necessary to comply with the return type defined in the interface because render
returns void
. However, it is still the render
statement that will be returned by Grails to the client.
With this preliminary work, consistency with previous endpoint exposure is ensured when we implement our Spring Boot controller.
Unfortunately, it won’t cover path variables and query parameters. Let’s take this example with a POST, a GET with a query parameter, and a PUT with a path variable.
Here the list()
signature has no parameter, and the update()
signature has the body but not the path variable. In Grails, those are usually accessed through the params
map. In Spring Boot, those would be in the signature, annotated by @RequestParam
or @PathVariable
. In that configuration, endpoints with path variables or query parameters cannot implement the same interface between Grails and Spring Boot controller.
Thankfully, declaring path variables and query parameters in controller methods’ signatures is possible since Grails 2, just name the argument the same as the query parameter or the same as the path variable defined in the UrlMappings
file, and Grails will do the trick.
Going back to our example, to prepare your migration, you would define the following interface
and modify your Grails controller that way:
Change payload validation to javax.validation
Classes representing payloads are constrained with static declarations in Grails, exactly like GORM entities.
This must be changed to usejavax.validation
annotations, and you’ll need to annotate your Spring Boot controller with @Validated
and your request body parameter with @Valid
.
I strongly recommend having automated tests that check validation errors before migrating, ensuring you have kept the same constraints.
Serialize with Jackson
Finally, assuming you’re using JSON data format, you’ll need to use Jackson instead of Grails to serialize and deserialize payloads.
If you have custom bindings (i.e classes implementing theValueConverter
interface of Grails), you’ll need to change them to classes extending JsonDeserializer
(or use an already available configuration, for instance, we had custom Grails bindings for Instant
that became unnecessary with the JavaTimeModule
of Jackson).
If you use the Grails rendering feature with gson
files into the views
directory, you’ll throw this away and just return objects that will be serialized by Spring thanks to Jackson.
If you would want to take smaller steps (Kaizen !), you could start using Jackson in your Grails controller. In the PersonController
, you would keep the render
but you would get rid of gson
views and would just pass it a string serialized with Jackson.
Notice that you’ll need to set the contentType
in the render method since Grails will only get a string and won’t be able to determine that the data type is JSON.
When you’re done with all steps, you’ll get this fancy vanilla Spring Boot controller.
Now that the controller is based on vanilla Spring Boot, the signature List<PersonOutput> list(String personIds)
can be modified to List<PersonOutput> list(List<String> personIds)
and the split
statement can be dropped.
That’s it for our second part on migrating from Grails to vanilla Spring Boot, nothing revolutionary here but those reminders could be useful if you’re migrating too. The third and last part will be about migrating from GORM to Spring Data.