14/06/2024
Software development
Technology consulting
ReBooting your application with Micronaut!
14/06/2024
Spring vs micronaut
As developers we face constant change and evolution. Whenever we reach a certain level of awareness and confidence in particular technology, another solution appears on the horizon... It happened when we were learning java, life changed with spring and its vast array of libraries. In the meantime, the spring itself has undergone quite an evolution in the form of spring-boot.
We notice a certain pattern - something new, different and promising is emerging, and with that novelties, there comes some uncertainty in ourselves. Can the transition to another framework be painless?
I will try to answer this question and, through several simple examples, prove that change can be not only easy but also pleasant.
Overcoming Cloud Autoscaling Challenges with Micronaut
To begin with, I have to say that I have not encountered a problem for which spring did not have a proposal for a solution, so I rate spring's maturity highly - after all, it started over 20 years ago!
So let's go back a few years. I was lucky enough to work on a project where a significant problem arose when using cloud architecture.
But first things first... We had several nodes of an application based on the Spring Framework.
The application was small and launched in only several seconds. One of the client's requirements was mechanisms for promotions initiated on a specific day, which resulted in great interest and consequently generated a lot of traffic on the website. We were prepared for this. Autoscaling was supposed to come into play when such a situation occurred. Unfortunately, even a relatively quick start could not cope with it, and autoscaling simply could not keep up with the supply of new instances in relation to those that stopped.
Today, with a range of technologies such as spring-boot, micronaut, quarkus and the ability to generate native images, the above problem should be able to be effectively mitigated.
For the purposes of this article, we will focus on micronaut from the perspective of a developer who knows spring-boot.
Compile-Time Annotations
We have always associated spring with being a framework that is widely known and much preferred.
How does it work? Spring creates proxy classes which it then uses through reflection mechanisms - I suspect that a large number of readers are aware that the very term “reflection” in java is not associated with performance. Additionally, component setup happens at application launches which significantly affects the starting speed of a new instance.
We have refreshed the basics of spring - so how does micronaut do it?
Micronaut uses annotations at compilation time so that all Beans (and any problems with them) are resolved at compilation time rather than on the launched application. In addition, the fact that micronaut does not use reflection allows it to significantly reduce memory consumption.
A Seamless Transition for Java Developers
99% of java developers currently know the spring framework like the back of their hand. Will micronaut require learning from scratch?
To answer this question, I decided to write the same application twice.
A simple REST service - controller, service, repository, plus object mapping in the service to separate the application layers. The whole thing connects to a non-relational MongoDB database.
When I have already had an idea for the application, I started with a spring-boot.
I used the well-known https://start.spring.io/. I have chosen the appropriate dependencies needed to achieve the goal.
With the skeleton ready, I created logic and after a short while I could test my API with swagger-ui.
I could then start writing the same application, but with Micronaut.
The first pleasant surprise was that, similarly to spring, Micronaut also provides an online tool to facilitate the setup of the application https://micronaut.io/launch/.
Using the same buzzwords as for spring, after a while I had the same ready skeleton as a moment before with spring.
At the start, I wanted the application to connect correctly to the local mongo. Using the micronaut documentation and bearing in mind how it works in spring-boot, I quickly finished with the intended result.
mongodb:
uri: mongodb://XXX:XXX@localhost:27017/
It’s time to rewrite the logic. Using the documentation, I managed to rewrite the logic within a short period of time, which works exactly as its prototype that was written before.
What are the differences?
The controllers:
Controllers
Spring
@RestController
@RequestMapping("/api/users")
public class UserController {
...
}
Micronaut
@Controller("/api/users")
public class UserController {
...
}
Post
Spring
@PostMapping
public ResponseEntity<UserDto> upsertUser(UserDto userDto, UriComponentsBuilder builder) {
UserDto createdUser = userService.upsertUser(userDto);
UriComponents uriComponents = builder.path("/api/users/{id}"). buildAndExpand(createdUser.getId());
return ResponseEntity. created(uriComponents.toUri()).body(createdUser);
}
Micronaut
@Post
public HttpResponse<UserDto> upsertUser(UserDto userDto) {
return HttpResponse.created(userService.upsertUser(userDto));
}
Get
Spring
@GetMapping
public ResponseEntity<Collection<UserDto>> findAll() {
return ResponseEntity.ok(userService.findAll());
}
Micronaut
@Get
public HttpResponse<Collection<UserDto>> findAll() {
return HttpResponse.ok(userService.findAll());
}
Delete
Spring
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteById(@PathVariable String id) {
userService.deleteById(id);
return ResponseEntity.noContent().build();
}
Micronaut
@Delete("/{id}")
public HttpResponse<Void> deleteById(@PathVariable String id) {
userService.deleteById(id);
return HttpResponse.noContent();
}
migration__missing-content
Defining services and repositories is similar as well:
Defining the services
Spring
@Service
public class UserService {
...
}
Micronaut
@Singleton
public class UserService {
...
}
Defining the repositories
Spring
public interface UserRepository extends MongoRepository<User, String> {
...
}
Micronaut
@MongoRepository(databaseName = "user")
public interface UserRepository extends CrudRepository<User, String> {
...
}
Maximize your IT solutions? Schedule a free consultation today!