EXPEDIA GROUP TECHNOLOGY — SOFTWARE

Fully Reactive Request Processing with Project Reactor, gRPC, and MongoDB

How to make request-response–oriented software paths fully reactive

Sage Pierce
Expedia Group Technology

--

Photo of complex industrial plumbing
Photo by Magda Ehlers on Pexels

In my last blog post, I discussed the characteristics of a reactive system and reactive streaming processes in such systems. Specifically, I made the case for what qualifies as “fully reactive” in stream processing applications.

With the advent of new client-server communication protocols (specifically, HTTP/2) and related technologies (like gRPC), request-response–based processes are moving in a direction that makes their code look more like traditional stream processing code. That is, request-response handling code is looking less like the imperative (usually blocking) code we see in RESTful services and more like asynchronous data processing code. You don’t have to jump very far into gRPC before seeing such concepts as client-stream, server-stream, and/or bidirectional stream procedures. While gRPC also supports “unary” procedures (single request with single response) similar to RESTful processes, all gRPC operations, regardless of cardinality, are natively stream based. This is because the underlying protocol, HTTP/2, is inherently stream-centric.

HTTP/2, and tech like gRPC built on top of it, allow us to expand fully reactive coding practices beyond traditional asynchronous streaming processes and into request-response–based processes. I find this appealing; as an industry, we have already spent years developing tools (like Reactive Streams, Project Reactor, and many others) and methodologies (like Functional Reactive Programming) for efficiently and reliably developing event and/or message-driven processes. Now that protocols like HTTP/2 support native streaming at the low-level protocol layer, implementing HTTP/2 request-response handling in fully reactive manners is desirable. We can reuse the same methodologies, and in many cases the same tools, to accomplish fully reactive request-response handling as we do for message driven processes.

Spoiler: Requests are just events to which we react!

I’ll show you reactive coding practices with examples of what fully-reactive request-response handling looks like.

Preface: Reactive programming in Java

In case you aren’t familiar with the history of the JDK, reactive programming in Java is still a relatively new phenomenon. Reactive programming fundamentally builds on functional programming, which didn’t have first class support in the Java language spec until JDK 8 was released in 2014. Even then, the canonical reactive streams API wasn’t included in the JDK until version 9 was released in 2017 with the Flow API.

While Java was lagging behind, other JVM languages were addressing developers’ desires for less boiler plate code and more functional syntax. Groovy was one of the first JVM languages to introduce closures. Scala was meant to be the oasis for pure functional programming experts. More recently, Kotlin has been on the fast track to remedy as many Java developer pain points as possible while also introducing reactive concepts like coroutines.

Even though Java’s slow speed of picking up desirable language features has frustrated me, I still prefer developing in Java instead of other JVM languages. The reason for this is simple: I like fewer layers of abstraction between my keyboard and the bytecode that gets executed. In other words, I like the finer-grained control that writing code which nearly compiles one-to-one with JVM bytecode provides. For this reason, I’ll be showing and linking to Java code, and using popular libraries developed in the absence of Java natively supporting reactive concepts. However, the same principles should still apply to other languages (and should be usable by other JVM languages).

Example gRPC Service

I’ll use the following protofile to define and interact with a gRPC service:

This allows us to implement a Java-based gRPC service like this:

This example simply takes a name, and responds Hello, <name>!

Add in a database

The next thing we’ll do is add a requirement to say how many times a particular name has been “hello’d”. We want this count to be durable, so we’ll use a database to keep track of how many times a particular name has been hello’d. For the sake of this example, I’ll be using a Mongo database.

If implemented with imperative blocking code, our code might look like this:

While this code may be correct, it is not reactive. The reason for this is due to the blocking I/O call that happens at helloCollection.findOneAndUpdate.

In order to look at different ways of making this code reactive, we’ll go from top-to-bottom (code-wise) and then bottom-to-top.

Asynchronous database interaction

The first way we can make this code (pseudo) reactive is to introduce reactive types and make the database interaction asynchronous.

Already, our code has become more declarative via usage of reactive types from Project Reactor. Nothing has changed about the database interaction code, but the invocation of that code has been made asynchronous via Reactor’s Mono subscribeOn method. Functionally, this means that the thread that executes sayHello will effectively return immediately and be able to service other requests while another thread interacts with the database and responds with the “hello” message.

This example shows how you can make non-reactive code that you don’t/can’t control reactively invokable. Reactor’s Scheduler abstraction essentially makes thread pooling easier such that blocking code can be isolated on dedicated thread pools.

Reactive database interaction

More and more database drivers and persistence APIs are offering natively reactive functionality. The benefit of many of these implementations is that they take advantage of low-level non-blocking selectors, channels, and buffers instead of blocking I/O streams and queues. Ultimately, this means more efficient resource usage, allowing more work capacity per application instance.

MongoDB provides a Reactive Streams Driver that recently superseded the now-deprecated Async Driver. Let’s update our code to use the reactive driver:

The updated code is now “natively” reactive and uses exactly the same amount of code (in lines) as the imperative blocking implementation. Neat!

Reduce boilerplate with reactive gRPC

We can do one more thing to reduce the amount of boilerplate necessary to write each gRPC endpoint. Because gRPC callback methods match up nearly one-to-one with Reactive Streams Subscriber methods, Salesforce implemented and open sourced reactive-grpc, which allows adding a plugin to generate natively reactive base stub implementations. Using Reactor bindings, our code can be modestly simplified to the following:

Our code is now made up of fully monadic functions. Using Reactor bindings allows our code to more clearly express the cardinality between requests and responses, which is one-to-one (unary) in this case. This code now also makes it clearer that requests are now just events/messages that we react to.

What about resiliency?

You may recall that one of the key characteristics of a reactive system is resiliency. Any location in a system/application that interacts with a potentially brittle resource, typically through I/O ports, is an opportunity to build in fallback logic to provide degraded service instead of failing completely.

In our case, interaction with the database is one such potential point of failure. For instance, there might be intermittent network issues or the database may become degraded. Since the Reactive Streams API has built-in error channels (i.e., Subscriber::onError) and we use Reactor, we can easily add some resiliency to our service and provide a degraded state of operation when these failures occur.

Let’s update our code to the following, with new error handling logic in sayHelloTo:

With the updated code, we’re now switching to a degraded message that still says “Hello”, but also indicates the call may not have been counted. In addition, we are logging the error so we can alert on it for later debugging.

Hopefully, the code here helps make reactive programming a little less scary and more accessible to you when implementing request-response oriented code paths.

Here are a few other resources you might be interested in:

  • Project Reactor Learning Resources — If you’re interested in learning more about Project Reactor, check out their excellent set of learning materials
  • Blocking I/O and Non-Blocking I/O — Good blog discussing the difference between two methodologies of I/O
  • Reactive OkHttp Client — If you’re needing to communicate via HTTP/1 (i.e. REST or SOAP APIs), this code shows how to take advantage of OkHttp’s async calling functionality to make (pseudo) reactive HTTP/1 calls

--

--

Sage Pierce
Expedia Group Technology

Software Engineer with a penchant for refactoring all of the things