Christmas is coming! Like every year, Santa needs to schedule its presents delivery. Can you imagine? Millions of (good) children will receive a present on the night of the 24th of December. To achieve this prowess, Santa needs a bit of technical help to schedule the deliveries. What about a microservice architecture with MicroProfile and Quarkus?
Resilient Microservice Architecture
Santa has been using several technical architecture in the past, but now, he wants to move to microservices! He has already found external partners to work with, so he wants us to develop a resilient architecture to interact with these third-party microservices.
So we need to develop Santa its own microservice (called Santa), with its own database, to schedule his coming deliveries. With this microservice, Santa can find his past deliveries per country, but more important, he can create the new schedule for Christmas 2020. For that, Santa accesses (JSON over HTTP) two external microservices developed and maintained by external partners:
- Kid: Third-party microservice that gives Santa the list of good kids per country who deserve a present (the naughty ones don’t get anything this year).
- Pokemon: Third-party microservice returning Pokemons (this year Santa will only deliver Pokemons).
On the 24th of December, Santa will only have a few hours to deliver Pokemons to all the good children all over the globe. That means that the Santa microservice will invoke the Kid microservice to get the location of all the good kids, per country, and then, for each kid, it invokes the Pokemon microservice to get a present. So the system cannot fail! We need to build a resilient architecture.
If the Santa microservice cannot access the two external microservices (eg. due to network problem because of weather condition), we need to find a backup plan. As a backup, if Santa cannot access the Kid microservice, he can create his new schedule based on the schedule of the previous year (which is stored in his local database). And if the Pokemon microservice does not respond, well, Santa will deliver some lollies instead (he has tons of lollies).
For that, we can use MicroProfile with Quarkus.
MicroProfile and Quarkus
Eclipse MicroProfile addresses the need for enterprise Java microservices. It is a set of specifications for handling microservices design patterns. MicroProfile APIs establish an optimal foundation for developing microservices-based applications by adopting a subset of the Jakarta EE standards and extending them to address common microservices patterns. Eclipse MicroProfile is specified under the Eclipse Foundation and is implemented by SmallRye.
If you haven’t heard of Quarkus, you should definitely look at it. Quarkus is A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards. In practice, Quarkus is an Open Source stack for writing Java applications, specifically back end applications. So Quarkus is not limited to microservices, even though it is highly suited for it. All this without reinventing the wheel by proposing a new programming model, Quarkus leverages your experience in standard libraries that you already know (e.g. CDI, JPA, Bean Validation, JAX-RS, etc.) as well as many popular frameworks (e.g. Vert.x, Apache Camel, etc.).
To build Santa a resilient system, we will use Quarkus with two MicroProfile specifications:Eclipse MicroProfile REST Client and Eclipse MicroProfile Fault Tolerance
Let’s develop the Santa REST Endpoint with Quarkus and JAX-RS.
Santa REST Endpoint
To interact with the Santa microservice, we will develop a JAX-RS endpoint called SantaResource
.
As shown in the diagram above, the SantaResource
has two methods:
createASchedule()
: Invoked with an HTTP POST, this method creates the schedule for one country.getASchedule()
: Invoked with an HTTP GET, this method returns a schedule for a given country and a given year.
The createASchedule()
method delegates the business logic and database access to a SantaService
. This service is the one invoking the remote third-party microservices Kid and Pokemon. It also accesses the database through the Schedule
entity. A schedule is made for a specific year (2020 in our case) and country. It has a set of Delivery
entities: a delivery is when Santa delivers one present to one child.
@Path("/api/santa") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.TEXT_PLAIN) public class SantaResource { @Inject SantaService service; @POST @Transactional public Schedule createASchedule(String country) { Schedule schedule = service.getAllGoodChildren(country); schedule = service.getEachChildAPresent(schedule); schedule.persist(); return schedule; } @GET public Optional<Schedule> getASchedule(@QueryParam("country") String country, @DefaultValue("2020") @QueryParam("year") int year) { return Schedule.findByYearAndCountry(year, country); } }
The way Santa invokes this endpoint is as follow:
# Creates a new schedule for Angola
curl -X POST -H "Content-Type: text/plain" -d "Angola" http://localhost:8701/api/santa
# Gets the 2019 schedule for Venezuela
curl "http://localhost:8701/api/santa?country=Venezuela&year=2019"
Now let’s use Eclipse MicroProfile REST Client so the SantaService
can access the remote microservices.
MicroProfile Rest Client
Eclipse MicroProfile REST Client provides a type safe approach using proxies and annotations for invoking RESTful services over HTTP. The Eclipse MicroProfile REST Client builds upon the JAX-RS APIs for consistency and ease-of-use. As shown in the diagram below, instead of invoking a remote microservice using low-level HTTP APIs, with Eclipse MicroProfile REST Client it’s just a matter of using a Java interface.
As seen in the SantaResource
above, the method createASchedule()
invokes the getAllGoodChildren()
of the SantaService
.getAllGoodChildren()
uses a ChildProxy
to communicate with the remote Kid microservice. For that, we use the standard CDI @Inject
annotation in conjunction with the MicroProfile @RestClient
annotation to inject the ChildProxy
interface.
@ApplicationScoped public class SantaService { @Inject @RestClient ChildProxy childProxy; public Schedule getAllGoodChildren(String country) { Schedule schedule = new Schedule(2020, country); List<Child> allChildrenPerCountry = childProxy.getAllGoodChildren(country); for (Child child : allChildrenPerCountry) { schedule.addDelivery(child); } return schedule; } }
Using the Eclipse MicroProfile REST Client is as simple as creating a ChildProxy
interface using the proper JAX-RS and MicroProfile annotations:
@RegisterRestClient
allows Quarkus to know that this interface is meant to be available for CDI injection as a REST Client,@Path
and@GET
are the standard JAX-RS annotations used to define how to access the remote service,@Produces
defines the expected content-type.
@Path("/api/kids") @RegisterRestClient(configKey = "child-proxy") public interface ChildProxy { @GET @Produces(MediaType.APPLICATION_JSON) List<Child> getAllGoodChildren(@QueryParam("country") String country); }
The ChildProxy
returns a list of Child
objects. A Child
has all the needed information for Santa to delivery a present (name, address and a boolean indicating if the house has a chimney or not).
public class Child { public String name; public String address; public boolean chimney; }
There is only one piece of information missing:
the location of the remote Kid microservice.
This is just a matter of configuring it in the application.properties
file within Quarkus.
For that, we take the child-proxy
property key (see the @RegisterRestClient
annotation) and set a URL value (here it’s http://localhost:8702).
child-proxy/mp-rest/url=http://localhost:8702
present-proxy/mp-rest/url=http://localhost:8703
In this configuration file, you also notice the URL of the remote Pokemon microservice.
Let’s do the same and use Eclipse MicroProfile REST Client to invoke it through a proxy.
Once Santa gets the list of all good children, he invokes the other microservice to get a toy for each child. As seen below, the SantaService
uses another proxy, the PresentProxy
to invoke the remote Pokemon microservice.
@Inject @RestClient PresentProxy presentProxy; public Schedule getEachChildAPresent(Schedule schedule) { for (Delivery delivery : schedule.deliveries) { delivery.presentName = presentProxy.getAPresent().name; } return schedule; }
The PresentProxy
is an interface using a few JAX-RS and Eclipse MicroProfile REST Client annotations.
@Path("/api/pokemons/random") @RegisterRestClient(configKey = "present-proxy") public interface PresentProxy { @GET @Produces(MediaType.APPLICATION_JSON) Present getAPresent(); }
Everything is looking good: the Santa microservice can now invoke the Kid and Pokemon microservices! But Santa is very careful. He has seen other architectures before and he has seen them fail when he needed them. What if the communication breaks?
MicroProfile Fault-Tolerance
If one of the remote microservices does not respond as expected, e.g. because of fragile network communication, we have to compensate for this exceptional situation. Eclipse MicroProfile Fault Tolerance allows us to build up our microservice architecture to be resilient and fault tolerant. This means we must not only be able to detect any issue but also to handle it automatically.
Let’s provide a fallback for getting the list of all good children in case of failure. As shown in the diagram below, if the communication fails with the Kid microservice, we could get the schedule for the previous year. Of course, it’s not 100% accurate (some children might have been naughty this year) but at least Santa can get the name and location of the children from last Christmas. As for the Pokemon microservice, if we can’t reach it, we fall back to delivering lollies instead of a Pokemon.
For that, we add one fallback method to the SantaService
called getLastYearSchedule()
. Then, we add the Eclipse MicroProfile Fault Tolerance @Fallback
annotation to the getAllGoodChildren()
method pointing to the newly created getLastYearSchedule()
method. We also add the @Retry
annotation to the method. We configure @Retry
so it tries 5 times to invoke the Kid microservice. It waits for 2 seconds before each retry. If the communication cannot be made, then, it will fall back to getLastYearSchedule()
. Both, the getLastYearSchedule()
method must have the same method signature as getAllGoodChildren()
(in our case, it takes a country and returns a Schedule
object).
@Inject @RestClient ChildProxy childProxy; @Retry(maxRetries = 5, delay = 2, delayUnit = ChronoUnit.SECONDS) @Fallback(fallbackMethod = "getLastYearSchedule") public Schedule getAllGoodChildren(String country) { Schedule schedule = new Schedule(2020, country); List<Child> allChildrenPerCountry = childProxy.getAllGoodChildren(country); for (Child child : allChildrenPerCountry) { schedule.addDelivery(child); } return schedule; } public Schedule getLastYearSchedule(String country) { Schedule schedule = Schedule.findByYearAndCountry(2019, country).get(); return deepCopy(schedule); }
And we also use the @Fallback
annotation on the getEachChildAPresent()
method: if we can’t reach the Pokemon microservice, then, we fall back to delivering lollies as a present (better then nothing).
@Inject @RestClient PresentProxy presentProxy; @Fallback(fallbackMethod = "getEachChildSomeLollies") public Schedule getEachChildAPresent(Schedule schedule) { for (Delivery delivery : schedule.deliveries) { delivery.presentName = presentProxy.getAPresent().name; } return schedule; } public Schedule getEachChildSomeLollies(Schedule schedule) { for (Delivery delivery : schedule.deliveries) { delivery.presentName = "Santa Lollies"; } return schedule; }
Conclusion
Now Santa is reassured: on the night of the 24th of December, he will fly over each country and will be able to deliver Pokemons or lollies to all the children, no matter what happens.
The communication between microservices is very challenging. In a distributed architecture, you rely on an unreliable network: the network can slow down, can be cut, a microservice can hang and have a domino effect on other microservices, and so on. That’s why Eclipse MicroProfile brings us a few specifications to ease microservices communication and handle communication failure.
Eclipse MicroProfile REST Client is a very elegant API where you use a type-safe approach by using a Java interface, that’s all. All the underneath HTTP plumbing is taken care of. Eclipse MicroProfile REST Client will handle all the networking and marshalling, leaving our code clean of such technical details. Thanks to Eclipse MicroProfile Fault Tolerance, you can develop a fallback mechanism with only a few annotations.
References
If you want to give this code a try, download it from GitHub, build it, run it, and make sure to break the communication between the microservices to see fallback in action.
If you were naughty this year and Santa doesn’t bring you a Pokemon, never mind, you can download (for free) my two books on Quarkus and read for Christmas.
And don’t forget to check the MicroProfile and Quarkus documentation.
Author: Antonio Goncalves
Antonio Goncalves is a senior developer living in Paris. He evolved in the Java EE landscape for a while and then moved on to Spring, Micronaut and Quarkus. From distributed systems to microservices, today he helps his customers to develop the architecture that suits them the best.
Aside from freelancing, Antonio wrote a few books (Java EE and Quarkus), talks at international conferences (Devoxx, JavaOne, GeeCon…), writes technical papers and articles and co-presents on the Technical French pod cast Les Cast Codeurs. He has co-created the Paris JUG, Voxxed Microservices and Devoxx France. For all his work for the community he has been made Java Champion ten years ago.