Skip to content

A POC project to compare the development experience of building a reactive API using latest Spring Boot as opposed to Vert.x.

License

Notifications You must be signed in to change notification settings

u-ways/spring-boot-reactive-microservice-kt

Repository files navigation

Spring Boot Reactive Microservice (Kt)

CICD Code Climate GitHub License Awesome

Background

Our older monolithic applications were built using an older generation of Spring Boot with vertical scaling architecture. With the rise of cloud computing and the need for more scalable and resilient applications, we've moved towards a more modern, reactive architecture. Our framework of choice was Vert.x, which allowed us to build reactive microservices that could scale horizontally with ease.

Vert.x reactive model was a great experience and a great fit for our use case, it became our de-facto standard for building new services. However, this made us neglect the Spring Boot ecosystem, which has been evolving rapidly and is becoming a strong contender in the reactive space.

This project is a software's engineer take at building a modern reactive API using Spring Boot and Kotlin. The goal is to leverage as much as possible from the Spring Boot ecosystem and compare it to our current experience with Vert.x.

Katanox API

I have chosen Katanox API as the base for this project mainly because their API is well-documented and provides a good use case for building a reactive API.

Katanox is a direct booking API that allows Buyers and Suppliers to establish a partnership effortlessly in a B2B marketplace. We aim to open hotel sales to the next generation of travel sellers ranging from airlines to travel publishers, voice assistants to messaging apps.

  • Katanox is not a Channel Manager - it connects with different PMSs without the need to map rooms for new partners.
  • Katanox is not a GDS - direct deals are easily managed through a new technology that keeps your content updated.
  • Katanox is not an OTA - it is not even a channel. It is a platform where you have control over your partnerships.

We want to drive innovation for Buyers and Suppliers and ultimately help the growth of the travel industry.

Check them out, they have a great API and a great team behind it.

Vert.x vs Spring WebFlux

You might be interested in a technical comparison between Vert.x and Project Reactor. Here are some relevant (albeit some are old) resources that can help you with your research:

The Streams Specification Vs The Reactive Manifesto

Both Vert.x and Project Reactor (What Spring WebFlux is based on) are based on the Reactive Streams Specification, which in turn came about as a result of the Reactive Manifesto. However, it's important to distinguish between the after mentioned specification and manifesto, for that, have a look at the AWS Whitepaper on Reactive programming and reactive streams.

Personal Verdict

Although the SpringBoot ecosystem is extremely rich and well-supported, I found reactive Spring implementation locks you into a specific set of types (e.g. returning an instance of Mono<T> or Flux<T> so it can be subscribed to at the end). As a result, you are unable to leverage the full power of the Spring ecosystem. However, I found Project Reactor a joy to work with as it's functional programming model and the reactive streams API are well-thought-out.

In contrast, Vert.x is much more minimalistic than Spring, and it's architecture model, with careful engineering, can be abstracted away from the developer. This allows the developer to focus on the business logic and not on the framework, resulting in a more straightforward and cleaner codebase. However, the ecosystem is not as rich as Spring, and you might find yourself building custom libraries to handle the complexity of your services.

For example, one of my favourite features of Vert.x is the Verticle model, which allows you to build your services in a more modular way. Allowing your team members to work in multiple parts of the application without causing conflicts.

Although, it's worth mentioning we have gone through a lot of Vert.x iterations to get the complexity right to reduce our TTM (Time to Market) via our own in-house Vert.x framework that is used for building new services. This involved trial and error, and we had to build a lot of custom solutions to handle the complexity of our services. So I am sure we can achieve the same with reactive Spring Boot with a bit more effort.

Implementation Details

Solution Architecture

Solution Architecture

For this project, I decided to go for a READ-ONLY microservice that will provide the availability of properties and the ability to book a room. The "pre-booking" service will simulate an asynchronous flow via a RabbitMQ message broker, and the "reservation service" will confirm and save the booking details to the database.

Of course, this is a simplified version of the architecture, in a real-world scenario, we would have more services and complex provisioning logic involved in the booking process. However, for the sake of this project, I decided to keep it simple.

It's also worth mentioning the "Availability & Pre-Booking Service" shouldn't be communicating with the Database directly, as it would complicate data synchronisation with the "Reservation Service". Instead, it would be better to have a repository service that would handle the database operations. This would allow us to have a more decoupled architecture and a more scalable solution via an intermediary API layer which would mediate database operations. However, I wanted to use the R2DBC in this POC, and so I've overlooked this architectural decision.

Other Practices and Technical Decisions

I have decided to follow DDD (Domain-Driven Design) principles for this project, and drive the implementation via ATDD (Acceptance Test-Driven Development). So the acceptance tests are the driving force behind the implementation features.

On top of that, jOOQ will be used to generate the database schema and the queries, and Flyway will be used to manage the database migrations. This will allow us to have a database-first approach to the implementation and a typesafe database object referencing.

Metrics scraping will be done via Micrometer and Prometheus, and the logs (in-production) will be sent as JSON to a centralised logging system (e.g. ELK stack). Readiness and Liveness probes will be implemented via Spring Boot Actuator to allow Kubernetes to manage the lifecycle of the application.

I will also admit I did not have the time to do a 100% test coverage so many of the sad-path scenarios are not covered, likewise the happy-path scenarios are not fully covered. However, I have tried to cover the most important scenarios.

Algorithmic Solution

Bulk of the work was done in the availability service, where we calculate the availability of a given hotel ID rooms based on the check-in and check-out dates. The algorithm is quite simple, we have to check if the room is available for the given dates, and if it is, we have to calculate the price based on set of associated prices (Details can be found in the AvailabilityService kdoc).

Availability Service Sequence Diagram

The implementation is driven via the AvailabilityEndpointIT acceptance tests.

Likewise, the booking service implementation is driven via the BookingEndpointIT acceptance tests. In which it relies on the availability service to check if the requested room is "really" available for the given dates. Therefore, we enabled a caching layer to reduce the number of calls to the availability repository. You can see the implementation details in the BookingService class.

This is by no means a perfect solution, and there are many ways to improve it, but the reactive programming patterns via Project-Reactor are well-illustrated in the implementation.

Usage

Prerequisites

  • You should have Gradle installed to build the application.
  • You also need Docker installed to deploy the application and accompanying infrastructure.

Running the Application

We use Makefile to simplify the build and run process.

Simply run:

make deploy-app

and everything will be set and ran for you. You can also use make undeploy-app to gracefully stop the application and tear down the containers. For checking the health status of the application you can run:

curl -sv http://localhost:8080/actuator/health | jq

Actuator discovery is turned on, so accessing http://localhost:8080/actuator will give you a list of available actuator endpoints to probe. (e.g. flyway, metrics, health, etc.)

TIP: check out the ./local directory for handy SQL inserts and open HTTP requests files.

Contributing

Although this is more of an educational/research project, I am open to contributions. If you have any suggestions or improvements, feel free to open a PR or an issue.

I am a big fan of providing a great Developer Experience (DX), so I've allocated time in making the build process as seamless as possible. When you try generating jOOQ classes, you will trigger the appropriate Gradle task to generate the classes without manual intervention. Task input/output caching is also configured.

Integration tests are ran via testcontainers, and they're gracefully stopped after the tests are done via Ryuk.

Also, a JVM toolchain is provided to ensure the correct version (21) of Java is being used.

I advise checking out the build.gradle.kts and the Makefile to understand the build process and what's available to you.