I suspect that the essence of your question may be redone: "Can we put our Spring controllers on top of the non-blocking HTTP level of Ratpack?" and the simplest answer to this question is no, because the MVC programming model doesnβt fit very well in the reactive / NIO model.
However, if your application has implemented some common model-view-controller- (and service) templates, then your controllers should really just do data binding and parsing and delegation to the service level. If so, then most likely the code in your controller is no longer blocked, and you can easily translate it into Ratpack code.
As an example, consider the following @RestController in a Spring Boot application:
@RestController @RequestMapping("/user") class UserController { @Autowired UserService userService @RequestMapping(method = RequestMethod.POST) Long create(@RequestBody @Valid User user) { User savedUser = userService.save(user) return savedUser.id } }
The spring aspect of data binding is the calculation process (i.e. not related to I / O), so we can easily translate it into the Ratpack handler:
import app.SpringConfig import app.User import app.UserService import org.springframework.boot.SpringApplication import org.springframework.context.ApplicationContext import ratpack.jackson.JacksonModule import static ratpack.groovy.Groovy.ratpack import static ratpack.jackson.Jackson.fromJson import static ratpack.jackson.Jackson.json import static ratpack.spring.Spring.spring ratpack { bindings { add(new JacksonModule()) bindInstance(ApplicationContext, SpringApplication.run(SpringConfig)) } handlers { ApplicationContext ctx -> register(spring(ctx)) prefix("user") { handler { UserService userService -> byMethod { post { def user = parse(fromJson(User)) blocking { userService.save(user) } then { User savedUser -> render(json(savedUser)) } } } } } } }
Where SpringConfig looks like this:
package app import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class SpringConfig { @Bean UserService userService() { new UserService() } }
And here is a functional test to prove it:
package app import com.fasterxml.jackson.databind.ObjectMapper import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest import ratpack.test.ApplicationUnderTest import ratpack.test.http.TestHttpClient import spock.lang.Shared import spock.lang.Specification import static groovy.json.JsonOutput.toJson class FuncSpec extends Specification { @Shared ApplicationUnderTest aut = new GroovyRatpackMainApplicationUnderTest() @Shared ObjectMapper mapper = new ObjectMapper() @Delegate TestHttpClient client = aut.httpClient def "should parse and save user"() { given: def user = new User(username: "dan", email: " danielpwoods@gmail.com ") when: requestSpec { spec -> spec.body { b -> b.type("application/json") b.text(toJson(user)) } } post('user') then: def savedUser = mapper.readValue(response.body.text, User) and: savedUser.id } }
Hope this helps!
Daniel Woods
source share