We're a Spring Boot shop and rely heavily on Spring MVC for our REST endpoints. We use Boot and embedded Tomcat to create a self-hosting JAR. Is it possible to replace Tomcat with Ratback while still keeping all my Spring MVC code in place? I am afraid that Spring MVC is tied into the servlet specification somehow and will not run without a servlet container. I am aware of dsyer/spring-boot-ratpack work but after skimming the code couldn't decide if Spring MVC would play well using the bridge. Is anyone aware of any work that will allow us to retain our investment in Spring MVC and have Spring Boot use Ratpack to manage HTTP traffic?
Yes, you can use Spring MVC with Spring Boot. To create and run a Spring MVC web application in spring boot, you need to add the spring-boot-starter dependency in your pom. xml file.
Spring Boot is not a replacement of Spring MVC. Spring Boot eases the development of Spring-based apps, including ones based on Spring MVC. A Spring MVC-only app needs to be packaged into a WAR file which is then deployed on a Servlet container such as Apache Tomcat.
Spring MVC is the framework to build web sites/applications using Spring. There's not a project called just "Spring Web" - you may be thinking of "Spring Web Flow" which is a project that works within Spring MVC.
I suspect the crux of your question can be distilled to: "can we put our Spring controllers on top of Ratpack's non-blocking HTTP layer?" and the simplest answer to that question is no, for reason that the MVC programming model doesn't fit well into the reactive/NIO model very well.
However, if your application has followed some common model-view-controller-(and service) patterns, then your controllers should really just be performing data binding and parsing and delegating out to a service layer. If that's the case, then likely the code in your controller is already non-blocking, and you could easily translate it to Ratpack code.
As an example, consider the following @RestController
in a Spring Boot app:
@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
}
}
Spring's data binding aspect is a computation process (ie isn't I/O bound), so we can easily translate this into a 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's 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: "[email protected]")
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!
The Spring MVC programming model is not very heavily dependent on Servlet APIs, but it's not supported in any other containers (i.e. not in Ratpack). There is some async stuff there now and Servlet 3.1 enhances it some more, so if that's the part of Ratpack that attracts you, maybe just using that would be a better approach. You won't get all the way to reactive and non-blocking IO that way though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With