Reactive Endpoints
Although traditional server calls work fine in most cases, sometimes you need different tools. Reactor is one of these, and can help you stream data to clients — and it fits well into a non-blocking application. Whenever the simple request-response pattern doesn’t suit your needs, you might consider Reactor. Multi-user applications, infinite data streaming, and retries are all good examples of what you can do with it.
If you want to know more about Reactor, see their curated learning page.
Although getting comfortable with creating streaming, non-blocking applications requires some effort, Hilla allows you to take advantage of Reactor in minutes. To learn the basics of reactive endpoints, read the Hilla blog article on the subject.
Familiarizing
Before looking at the API details, you might familiarize yourself with the basics of reactive endpoints by writing a simple application that can stream the current time of the server to the client. The task of this example is to send the server time to all subscribed clients, with an interval of one second between each message.
Start by creating a Hilla application like so:
npx @hilla/cli init flux-clock
Server-Side Endpoints
Next you’ll need to create a server-side endpoint. To do this, open your application in your favorite IDE or editor.
Create a new endpoint in a package of your choice and name it ReactiveEndpoint
. Add these two methods to it:
@AnonymousAllowed
public Flux<@Nonnull String> getClock() {
return Flux.interval(Duration.ofSeconds(1))
.onBackpressureDrop()
.map(_interval -> new Date().toString());
}
@AnonymousAllowed
public EndpointSubscription<@Nonnull String> getClockCancellable() {
return EndpointSubscription.of(getClock(), () -> {
LOGGER.info("Subscription has been cancelled");
});
}
Note
|
You can click on the expand code icon at the top-right of the code snippet to get the whole file.
|
The first method, getClock()
, creates a Flux that streams the server time each second. It uses onBackpressureDrop() to avoid saturation in case some messages can’t be sent to the client in real time. In this use case, it’s acceptable to lose some messages.
The second method, getClockCancellable()
, returns the same Flux
, but wrapped in an EndpointSubscription
. This is a Hilla-specific Flux
wrapper which allows you to execute something on the server side if the subscription is cancelled by the client.
In this example, a log message is produced.
Showing Messages
Next, you’ll need to create a view to show the messages. You’ll use these methods in your client views.
Create the frontend/views/reactive/ReactiveView.tsx
file to add a new view. This looks like the default "Hello world" example, customized with the code that follows.
import { Subscription } from "@vaadin/hilla-frontend";
import { Button } from "@vaadin/react-components/Button.js";
import { TextField } from "@vaadin/react-components/TextField.js";
import { getClockCancellable } from "Frontend/generated/ReactiveEndpoint";
export default function ReactiveView() {
const serverTime = useSignal("");
const subscription = useSignal<Subscription<string> | undefined>(undefined);
const toggleServerClock = async () => {
if (subscription) {
subscription.cancel();
subscription.value = undefined;
} else {
subscription.value = getClockCancellable().onNext((time) => {
serverTime.value = time;
});
}
}
return (
<section className="flex p-m gap-m items-end">
<TextField label="Server time" value={serverTime.value} readonly />
<Button onClick={toggleServerClock}>Toggle server clock</Button>
</section>
);
}
Then, add the new view to the router by importing it in frontend/routes.tsx
with this:
import ReactiveView from './views/reactive/ReactiveView';
Declare it with:
{ path: '/reactive', element: <ReactiveView />, handle: { icon: 'la la-file', title: 'Reactive' } },
right after the /about
path.
API Overview
The generated TypeScript endpoint methods return a Subscription<T>
object, which offers these methods:
cancel()
-
Cancel the subscription.
onNext()
-
Get the next message in the Flux.
onError()
-
Get errors that can happen in the Flux.
onComplete()
-
Be informed when the Flux has no more messages to send.
context()
-
Bind to the context, which allows the application automatically to close it when the view is detached.