Continuum
For years, the client-server architecture has been the de-facto standard to build applications. But a major shift happened. The one model rules them all age is over. A new range of applications and architecture styles has emerged and impacts how code is written and how applications are deployed and executed. HTTP microservices, reactive applications, message-driven microservices and serverless are now central players in modern systems.
Quarkus has been designed with this new world in mind, and provides first-class support for these different paradigms. Quarkus development model morphs to adapt itself to the type of application you are developing.
HTTP microservices
Let’s start with the basic: HTTP microservices. In this context, you need to develop an HTTP endpoint, often called REST or CRUD. You process incoming HTTP requests, and to do so you often need to rely on others services, such as databases, or another HTTP service.
For this type of application, Quarkus relies on well-known standards such as JAX-RS, JPA and MicroProfile Rest Client. Let’s take a very simple application handling fruits. The code would be something like:
@Path("fruits")
@Produces("application/json")
@Consumes("application/json")
public class FruitResource {
@Inject
EntityManager entityManager;
@GET
public List<Fruit> get() {
return entityManager.createNamedQuery("Fruits.findAll", Fruit.class)
.getResultList();
}
@GET
@Path("{id}")
public Fruit getSingle(@PathParam Integer id) {
Fruit entity = entityManager.find(Fruit.class, id);
if (entity == null) {
throw new WebApplicationException("Fruit with id of " + id + " does not exist.", 404);
}
return entity;
}
@POST
@Transactional
public Response create(Fruit fruit) {
if (fruit.getId() != null) {
throw new WebApplicationException("Id was invalidly set on request.", 422);
}
entityManager.persist(fruit);
return Response.ok(fruit).status(201).build();
}
// ...
}
If you are a Java EE or Spring user, this development model should look familiar.
You expose a resource containing methods annotated with @GET
, @POST
… to handle the different requests.
The path is specified using the @Path
annotation.
JPA users will recognize the EntityManager
used to access the database.
Finally, methods requiring a transaction are simply annotated with @Transactional
.
Let’s now imagine you need to access another HTTP endpoint. You can use a HTTP client directly, this is nothing more than repeating boilerplate code. Quarkus provides a way to call HTTP endpoint easily using the MicroProfile Rest Client API.
First declare your service as follows:
@Path("/v2")
@RegisterRestClient
public interface CountriesService {
@GET
@Path("/name/{name}")
@Produces("application/json")
Set<Country> getByName(@PathParam("name") String name);
}
For each call you are intending to do, add a method and use annotations to describe the behavior.
Then, in your resource, just use the CountriesService
:
@Path("/country")
public class CountriesResource {
@Inject
@RestClient
CountriesService countriesService;
@GET
@Path("/name/{name}")
@Produces(MediaType.APPLICATION_JSON)
public Set<Country> name(@PathParam("name") String name) {
return countriesService.getByName(name);
}
}
But you may be wondering where the URL is configured as it’s not in the code. Remember, it must not be hard-coded because the url likely depends on the environment. The URL is configured in the application configuration:
# the format is interface-name/mp-rest/url=the-service-url
org.acme.restclient.CountriesService/mp-rest/url=https://restcountries.eu/rest
The url can now be updated during the deployment or at launch time using system properties or environment variables.
Being reactive
Application requirements have changed drastically over the last few years. For any application to succeed in the era of cloud computing, big data or IoT, going reactive is increasingly becoming the architecture style to follow.
Today’s users embrace applications that have milliseconds of response time, 100% uptime, lower latency, push data instead of pull, higher throughput and elasticity. However, these features are nearly impossible to achieve by using yesterday’s software architecture without a huge investment in resources, infrastructure and tooling. The world changed and having dozen of servers, long response time (> 500 ms), downtime due to maintenance or waterfalls of failures does not meet the expected user experience.
Quarkus stands with you on your path to reactive. First, you can implement asynchronous HTTP endpoint as follows:
@GET
@Path("/greeting/{name}")
public CompletionStage<String> greetings(@PathParam("name") String name) {
return reactiveService.getAsyncGreetings(name);
}
Method actions can compose asynchronous operations and complete the result when everything is done without blocking threads. This greatly improves resource consumption and elasticity.
But, what about streams? Generating a server-sent event response with Quarkus is just as simple:
@Produces(MediaType.SERVER_SENT_EVENTS)
@GET
@Path("/neo")
public Publisher<String> stream() {
return service.getStream();
}
This method returns a Reactive Streams Publisher
.
To provide this stream, you can either use Rx Java 2 or MicroProfile Reactive Streams Operators:
@Produces(MediaType.SERVER_SENT_EVENTS)
@GET
@Path("/neo")
public Publisher<String> stream() {
return ReactiveStreams.of("a", "b", "c")
.map(String::toUpperCase)
.buildRs();
}
Message-driven microservices
However, HTTP characteristics prohibit implementing reactive systems, where all the components interact using asynchronous messages passing. But no worries, Quarkus is perfectly suited to implement message-driven microservices and reactive systems.
First, you can consume messages from various brokers such as AMQP or Kafka, and process these messages smoothly:
@ApplicationScoped
public class HealthDataProcessor {
@Incoming("health")
@Outgoing("heartbeat")
public double filtered(Health health) {
return health.getHeartbeat();
}
}
The @Incoming
and @Outgoing
annotations are part of Reactive Messaging.
They are used to express from which stream you are consuming and to which stream you are sending.
Thanks to Reactive Messaging you can consume and send messages from and to different brokers and transports such as HTTP, Kafka, or Apache Camel.
Sometimes you need more than just handling messages one by one. You can also express your message processing logic using reactive programming as illustrated in the following snippet:
@Incoming("health")
@Outgoing("output")
public Publisher<KafkaMessage<String, JsonObject>> filterState(Flowable<MqttMessage> input) {
return input
.map(message -> Buffer.buffer(message.getPayload()).toJsonObject())
.map(json -> json.getJsonObject("state"))
.distinctUntilChanged(json -> json.getString("state")) // Filter on the "State" key of the json object.
.doOnNext(json -> LOGGER.info("Forwarding new state '{}' to Kafka", json.encode()))
.map(json -> KafkaMessage.of("state", "neo", json));
}
Stream-based manipulation can either use Rx Java 2 (as in the previous snippet) or MicroProfile Reactive Streams Operators.
Functions as a Service and Serverless
Thanks to their stellar startup time and low memory usage, you can implement functions using Quarkus to be used in serverless environments. If you are using AWS Lambda, your Quarkus function looks like:
public class HelloLambda implements RequestHandler<HelloRequest, String> {
@Inject
HelloGreeter greeter;
@Override
public String handleRequest(HelloRequest request, Context context) {
return greeter.greet(request.firstName, request.lastName);
}
}
You can use any of the Quarkus features in your function and benefit from the fast startup and low memory utilization. With Quarkus, you can embrace this new world without having to change your programming language.