In this series of articles Terra10 rockers Dirk Janssen and Roger Goossens will explore the brave new world beyond the Spring Boot ecosystem and will dive into two promising microservice frameworks that have been around for some time now: Micronaut and Quarkus!
Both frameworks have a lot in common. If we compare them to Spring Boot – the de facto microservice framework of the moment – both frameworks offer the following extras:
- They’re polyglot frameworks, supporting more languages than just Java;
- They promise Faster Startup Time and Lower Memory Consumption;
- They support compilation into a GraalVM native image.
As for the last bullet point, truth be told that Spring Boot recently added support for that as well. Still, more than enough reasons to check out these two frameworks!
In this series Dirk will dissect Quarkus, while Roger deep dives into Micronaut, enjoying a proper beer in the process. Our approach will be to start off with a baseline service built in the mother of all microservice frameworks – Spring Boot – and try to replicate the functionality in the other two frameworks. Eventually we will compare the frameworks, summarize how easy it is to migrate, look into added value and report any lacking functionality.
So to build up a little suspense, we’ll not be showing you any Micronaut of Quarkus code in this introductory part of the series, but instead focus on building a reference Spring Boot REST service. Final code can be found on GitHub.
The baseline BeerService
Our baseline service gets its data from MongoDB. Its setup is archetypal Spring Boot and consists of a couple of layers: you got your Controller layer – exposing the REST api – delegating to a Service layer – orchestrating CRUD functionality – which in turn delegates to a Repository layer – taking care of persistence (a good tutorial for building a Spring Boot – MongoDB service can be found here).
A unit test is present to test the controller logic and we’ve added an OpenApi service description and UI for good measure.
Let’s break down the code a bit.
MongoDB
To keep things simple, we use the official MongoDB Docker image instead of installing MongoDB on our laptops. For more information on running MongoDB with the help of Docker, check out this tutorial.
To fire up a container, issue the following command:
docker run -d -p 27017-27019:27017-27019 --name mongodb mongo
To connect the Spring Boot service to MongoDB, all that’s needed are a few properties:
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=terraxbeer
Note that there is no need to create the terraxbeer database upfront. When you use the service to post some data, the database and its beers table get created automatically.
Model
The model consists of two entities, i.e. Beer and Brewery. The Beer entity is bound to a MongoDB table – beers – via the Document annotation:
@Document("beers")
public class Beer {
@Id
private String id;
private String name;
private Brewery brewery;
// getters and setters
}
For simplicity we’ll reuse these model objects in the Controller layer. This is not a recommended practice as it leads to tight coupling of the REST interface and the underlying persistence model. A better way would be to use a DTO layer and restrict exposing the model objects to the service and repository layers. The mapping logic to convert from DTO to model and back should be placed in the Controller layer. For more information, check out this tutorial.
Controller
The Repository and Service layer are pretty straightforward, so we won’t discuss them here. Let’s take a look at the Controller code instead:
@RestController
@RequestMapping("/beers/1.0")
public class BeerController {
private final BeerService beerService;
public BeerController(BeerService beerService) {
this.beerService = beerService;
}
@GetMapping(value = "/")
public List<Beer> getAllBeers() {
return beerService.findAll();
}
@GetMapping(value = "/{beerName}")
public Beer getBeerByName(@PathVariable("beerName") String name) {
return beerService.findByName(name);
}
@PostMapping(value = "/")
public ResponseEntity<String> saveOrUpdateBeer(@RequestBody Beer beer) {
beerService.saveOrUpdateBeer(beer);
return new ResponseEntity<>("Beer added successfully", HttpStatus.OK);
}
@DeleteMapping(value = "/{beerName}")
public void deleteBeer(@PathVariable("beerName") String name) {
beerService.deleteBeer(beerService.findByName(name).getId());
}
}
We’ve got a POST method for creating beers, a DELETE method for deleting them and two GET methods, one for retrieving a particular beer and the other for the retrieval of the complete list of beers. The Controller delegates its operations to the constructor injected BeerService.
OpenAPI
Adding an OpenAPI doc to a Spring Boot service is really simple. Check out this tutorial for more information.
Basically all you need to do is add a maven dependency:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc-openapi-ui.version}</version>
</dependency>
Configuration can be controlled through a few properties:
springdoc.api-docs.path=/api-docs
That’s all there is to it. You now have your OpenApi docs available at http://localhost:8081/api-docs and http://localhost:8081/api-docs.yaml.
A full blown OpenAPI UI is available at http://localhost:8081/swagger-ui.html.
Unit Testing
Unit testing a controller is a little tricky. We’re using Spring Boot’s WebMvc here. With WebMvc you can directly act on the REST methods. For more information on the use of WebMvc, check out the tutorials here and here.
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = BeerController.class)
class BeerControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private BeerService beerServiceMock;
@Test
void saveOrUpdateBeer() throws Exception {
final String beerName = "Terra10 Gold";
final String breweryName = "Terrax Micro-Brewery Inc.";
final String breweryCountry = "The Netherlands";
final Beer beer = newTestBeer(beerName, breweryName, breweryCountry);
mockMvc.perform(post("/beers/1.0/")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(beer)))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(TEXT_PLAIN_VALUE))
.andExpect(content().string("Beer added successfully"));
final ArgumentCaptor<Beer> beerArgumentCaptor = ArgumentCaptor.forClass(Beer.class);
verify(beerServiceMock, times(1)).saveOrUpdateBeer(beerArgumentCaptor.capture());
verifyNoMoreInteractions(beerServiceMock);
final Beer beerArgument = beerArgumentCaptor.getValue();
assertNull(beerArgument.getId());
assertEquals(beerName, beerArgument.getName());
assertEquals(breweryName, beerArgument.getBrewery().getName());
assertEquals(breweryCountry, beerArgument.getBrewery().getCountry());
}
}
- The @WebMvcTest annotation enables the use of WebMvc through injection. WebMvc allows the testing of methods on which lots of assertions can be added;
- The jackson ObjectMapper enables conversion between JSON and Java;
- The BeerService dependency get resolved with the help of a MockBean;
- The delegation to the BeerService is tested using plain Mockito.
Test Driving
After startup, the OpenAPI UI can be found here: http://localhost:8081/swagger-ui.html.
To check if it all works, try POSTing some beers and issue a GET to check if they all got through.
Check! Our baseline service appears to be working nicely. Time to end this first blog post.
Summary
In this blog, which is the first part of a series of Micronaut vs Quarkus blogs, we built a REST service using Spring Boot. This service we’ll try to replicate using the other two frameworks further down the series. In our next posts we’re gonna see how easy or how hard it is to rebuild this service in Micronaut and Quarkus and what additionional bonuses that will bring us.
Whether the next post will be on Quarkus or on Micronaut, you can only find out by staying tuned! And don’t forget to grab a beer while doing so.
References
Code
MongoDB
Spring Boot Service
- https://medium.com/javarevisited/building-a-rest-service-with-spring-boot-and-mongodb-3aa5cd2dce73
- https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application