Transcript
This transcript was autogenerated. To make changes, submit a PR.
You. Hi folks, thanks to be
here for this talk about from imperative to reactive.
I am Nicola Frankel. I've been working in it for 20
years in technical roles and since a couple of years I'm a developer
advocate. I must mention that I'm not a reactive guru,
so this is not a deep dive. It's just like
a gentle introduction. How you could migrate to reactive?
I work for a company called Hazelcast. If you are a Java developer,
you might have heard about Hazelcast. We have two products. The first one
is an in memory data grid, and you can think about an in memory
data grid as distributed data structures. So you
can replicate or short your data over several
nodes in the cluster. And the other one is Haslko's
jet and it allows to do stream processing in memory.
So very, very fast. Today I will talk boot
reactive and reactive well, like officially started
in 2014 with the reactive manifesto. And the reactive
manifesto lets four properties of a system to be considered
reactive. The first one must be responsive,
so when you request the system, it responds
as fast as possible. And well, you shouldn't wait too much
until you get your response. The second, it must
be resilient, so even if some components
fail, it should still be working.
The third one is it must be elastic. So if you
increase the cloud, it will probably act
slower but not stop working at all. And in my
opinion, the most important characteristic is it's message driven.
Meaning that it's only based by
not directly calling an object,
but by passing a message. So it's asynchronous.
You send a message and you expect the
response at some time, you just don't know how. Meaning it's
not blocking. And the
fun part is that it had in mind the actor
model. The actor model has been popularized by the Lang or
OTP platform. And the idea behind the platform is
that you don't create object
and make calls like direct method
calls to the other object. You just get a reference on
another object and you send messages and it's up
to the actor that received the message to
handle it. Of course, bit has a queue, a mailbox of messages,
it can process them in order. But the most important fact
is you don't access the states
of another object. So every actor has its own
state, it's strongly encapsulated, you just pass
messages. And of course, if you need data, you expect data
to be in the message or in the response
to the message. And well, on the OTP platform it's
the de facto standard because it's backed into the platform.
But in the Java world it didn't work like this.
Although the people behind the reactive manifesto were mainly
working for on ACA or for
lightband, the company behind hacka that models
the actor system on the TVM,
well it didn't work out so well. I mean ACA is still popular
library, but it's right now not the most popular
library. The stuff that the
industry sets its eyes on is reactivestream and
this is the definition of reactive stream. So I let you read,
you can find the website just below the idea
behind Reactivestream. We still have like this message
queue, but we have a single event loop that's important,
a single event loop that will process the messages and dispatch them
to the correct event handler. And this event
loop is run by a single threat, meaning that when
it passes the message, what it does should never
block. Because if you block this event loop until
some long running process is done, then you are blocking all
the messages, all the events that arrive in the
queue and you are stopping your system. So that's
very, very important. Non blocking is at the
roots of reactive and reactive streams.
There are probably good reasons to go reactives.
There are not so good reasons. So I want
just to mention two. The first one is scalability
and the idea behind the proponents of reactive. For scalability
say, hey, like now we normally in
a normal Tomcat or jetty, when you
receive a request, then the application server
will spin on a thread and it will be the thread responsibility
to handle the whole request response chain.
And it works pretty well until you hit the limits.
Might be 10,000, might be 1000, I don't know exactly.
But at some point you will reach the limits and you cannot scale
anymore. And proponents of reactive say, hey, in order
to be web scale you need to be reactive. I think that's
the wrong reason. The reason why this is the wrong reason is
because most of our application don't need to be web scale. We are not
Google, we are not Facebook, we are not Amazon. Of course
I wish every one of your application was to
be that successful. And at one point in time you
might want to migrate to this model. But unless you've
got this load, it's just like an additional
craft on your application and on the next slide I will tell
you about. Yeah, because also reactive has downside,
I will let you know about them. So forget scalability. Like for
most of our loads it's probably enough to have non reactive
system. However, I think that there is quite good reason
to go reactive. It's to be cloud friendly like if
you have an on premise hardware, you are
not optimizing your software, you are just
probably over consuming resources. And that's fine. You don't care.
You might waste cpu cycles, you might waste
memory, you might even waste storage. That's not an issue.
I mean the hardware has been bought, it's fine.
Now if you want to migrate to the cloud, you will be paying for everything
and you will be billed on cpu usage, memory consumption and
storage usage. So the idea in then that
if you block, you're just wasting cpu cycle,
you shouldn't do that. So in order to optimize your
monthly bill, then going reactive is a good idea
in all cases. Whether you have good reasons or
bad reasons, you are curious about reactive. You must consider
also the downside, because as developers,
as architects, well, we must make a trade off.
So the first, like the biggest downside of reactive is
to understand that hey, when I make a
function call, I'm not actually calling the function,
I'm just subscribing to the response
that I will get later, or I hope I will get later.
And that makes it hard to reason about.
When you look at reactive code, you must replace
all those function or method calls
by subscribing. Of course real developers,
they don't debug, they don't need debugging. But I'm not a real developer
and I find myself very often in the need of
debugging. And in that case, debugging reactive
code is much harder because in normal
code, when you have a single thread that handle the whole request
response chain, you set a breakpoint and you can go back
in time to see, hey, this was called with this object
on the stack with this state. And I can understand why
now I have this. But with reactive codes,
well, the thread might have switched, you don't know, so you
might lose important bits of data. Of course, the tools that are
getting better and better now, it still might be an issue
depending on your tools. The third point that I need to mention is
now you need to learn specific APIs.
It's not the GDK anymore, it's not the
servlet API anymore. It's like dedicated API.
And of course there are several reactive frameworks,
several reactive APIs. They look similar,
they probably don't use the same method names.
And even if they might do the same, there might
be some slightly different semantics from one to the
other. So it might be a problem when I
have a question. Generally I ask my favorite reactive guru
and I tell him, hey, I want to do this and this, and this.
And say, hey, you should use this method, okay?
It's quite a dedicated world. And last but not least,
if you want your application to be reactive,
everything in the request response chain must be reactive.
If in the middle anywhere you have a single blocking call,
well, the chain is not reactive anymore. So we must be very,
very careful about not introducing blocking
API call in our application and in the demo,
I will show you how we can do that. This is the reactive streams API.
Right now, as you can see, it's quite simple. You have a publisher,
publisher subscribes to a subscriber and
the subscriber is the one that reacts. So basically,
hey, you can react when you first subscribe, you can react
when you get an item, you can react when you get an error. And finally,
if the stream finishes, which most of the time we want it to be
successfully, you can do something when we do that. And also,
you can also see here the subscription,
you can see the back pressure baked in. Like the subscription interface
has a method called request. So you can ask for hey,
I want x items. You process the subs and then you can
a I want x more items. And so that's very good
because it means that your subscriber is not overflown
with too much data. So if you have a very fast producing
publisher and a slow consuming subscriber, you can handle
that well, you will need to deal with the data anyway,
but you won't bring your subscriber to its knees. And finally,
we have the processor, and because we will be
subscribing, and we will be subscribing in
steps, so every item in the chain will be subscribing to
a parent. We need a way to chain them together.
So we have the processor, which is both a publisher and a subscriber.
Now, in the Java world, as I mentioned, there are several reactive
frameworks. I want just to mention today, Project Reactor,
we have erics, but I want to mention Project Reactor because
I will be using it in demo and it's pretty popular as
well, especially in the spring framework. And the demo is based on spring
boot. So project Reactor just builds upon those
four small building blocks and it adds more abstractions.
For example, it provides from the publisher two different abstractions,
the flux, which can produce zero till like
store items, and the mono, which can produce zero or one
item. Just to mention that bit has no dependency on spring.
On the opposite, spring depends on Project Reactor. So you could
use Project Reactor without spring with no issues.
You might have heard also about the GDK nine
flow class. And if you look at the GDK nine flow
class, you will notice that it all has the same building blocks
as I mentioned in reactive streams. So it has the publisher,
it has the subscriber, the subscription and the processor.
All are like nested interfaces of the
flow class. And why? Well,
project reactor probably was designed just around the
time that GDK nine was going to happen, and so
they provided their own. And now GDK nine
now also has the same building blocks. So on the reactive
stream site you will see this funny quote that
hey, we will migrating and there will be a migration period
and we will migrate to GdK nine flow at some point. Well,
Gdk nine is 2017 and four years later
we still have no usage of this flow class.
Now if you want to bridge between the Tideca classes
and reactive streams, you have this flow adapter class
that lets you go back and forth. So it's an
adapter class. So you can still work with that in the spring realm.
Like the legacy way, the usual way. The way
I learned if you wanted to do web application was to use spring
framework webmdc. Now with version five we
have a new component called Spring Webflox and this spring
webflox uses reactive types and they are
all located in this web reactive package.
Now how can we like from a very pragmatic point of
view, can we migrating our code? So with spring
MVC, the usual way to configure
your application was to create controllers and to annotate with a
controller or rest controller. Then you have mappings.
Then you probably use get mappings or post mappings or whatever.
So the usual way. The first way was and patience in
the previous year. I think with spring framework five
there is something called Spring MVC FN so bit
makes you able to write spring MVC configuration
with a functional API. Why? Well,
the reason is Spring Webflox. Actually spring Webflox started
like a new API and if we look at the functional
app of spring Webflox and spring MVC, it's exactly the
same. Also, spring Webflox allows you to
use annotations. So looking like having a
cursory glance at codes from spring
Web, you cannot know really very well until you
look at it into the detail. If it will be managed
by regular spring MVC blocking call like sevlette API
or spring webflox reactive codes, anything is
possible. I believe since most of us have been using spring
MVC with annotations, that it's much
easier to say we will use spring webflox with the functional
app because at least you will know that you must be careful about
it and then just start from there.
So our migration pass will be hey, we will be using first
spring MVC with annotation. That's our starting point.
Then we will migrating to the functional way of using spring
MVC and then it will be just a change of the package to
use spring webflux. I've talked a lot, now let's
do some demo. So now I am in intellij. I have
created project from thought spring
IO and looking at the palm and my head
is just on top but here is the palm. We can see
that. Well as I mentioned it
inherits from spring bootstart apparent. I'm using
not the latest JDk but the latest at the time I created the
project I'm using Springboot starter data GPA. I'm using Springboot
starter web. I will be using also some caching. So because
I am using Springboot starter data GPA I have
a database. So in that case the database is h two which is not a
really great id because this is for demo purpose.
I'm just storing my data in memory. So adding caching in
memory on top of that might not be so a great id. But anyway
it's just for the purpose of the demo I want to add some
caching and the idea would be like to show
you that yeah, we are going away from spring MVC
and at some point we will lose the automatically
configured caching. We will need to add it explicitly.
So I think it's interesting because it's not only unicorns and rainbows,
we need to tackle some problems. And how does it work?
Well, I have this data SQL,
so when I start the application, this file will be read
by spring boot and so it will execute those
statements. And so when I start I will already have
some degree of data into
my application and I have a person controller.
I can ask for all persons that are in the database.
I can ask for a single person and
a person is just a regular entity.
It has like four fields. So nothing mind blowing on this
site. And I'm using the spring data
GPA repository, meaning that I
don't need to write all the sql by myself at
runtime. Spring will do that for me. So I
can query easily, find all and find by id.
It's done for me. So let's start
it and we see spring starting. So here it's
my starting point. I'm using spring MVc and
yes, has Elkata started as well? And something interesting,
I have configured the hibernate statistics because
I want to show you how you can easily cache
your data from the database. So I
will be curling this application curl,
HTTP localhost
80 80 and person and
one. Now I receive my data, which is
Joe Delton. And here in this I
can see that hey, the cache was of course empty and
so there was one cache miss and I did one cache put.
So if I redo it,
if I pass the same query now the cache is hot
and I have a cache bit and so I have no interaction
with the database at all, which is really, really good. And if I
query everything,
every one of those entities will be put in the cache.
So that now here you can see
I have five puts. Now if I query
the second one, which I didn't query individually before,
I have a hit. That's the first step,
that's my starting point. It works and I'm happy about it.
Now the second step is actually to move to,
as I mentioned before, to web MVC FN.
So I will do that. I have everything in git
because it's much easier. I don't need to mistype.
And here you can see that I didn't remove that much
configuration. Now I don't have a controller,
I have something called boot, that's how I called it.
And it's not a controller anymore, it's configuration class.
And my get mappings, I've moved them to
a router function that are annotated with bin.
So every router function will contribute
to the whole roads of the application. And here
you can use this function that
is provided to you by rotor functions and you
say hey, I want to use the get method and the mapping is person.
And then here you pass a function and that's
the reason why it's called functional web Mvc
fn is because here you pass a function that
accepts a request and that returns a response.
And you can think about a web server like a function.
It accepts like data and
it returns other data. It accepts data in
the form of a HTTP request and bit returns data
in the form of HTTP response. And here this
function is not executed, we pass
it by reference. And so when
we will be actually like calling,
so I will run the server, when we be
actually calling this person pass,
then it's at the time that this function will be executed.
When you query everything, it's quite easy, you just need
to return okay, and then in the body of the
response you will put all the entities that
we found before when you query a single parameter.
What we need to do first is to get, well, the id.
And so for that reason, again, we don't have annotations,
we don't have path params. Now we explicitly
say a request path variable and we bind this
id to this one.
Does it work? Well, let's check. So right now
I'm using the gold old flavor spring web MVC.
The only difference is how I configure
my controller, my routes. Before I used controllers,
now I'm using routes,
so it should do the same. And of course, since I
restarted the gvm, well, the cache and
the database have been emptied. So now I have one miss and one
put. And now if I do it again,
whoops, I have one
bit, so it still works the same.
That's pretty good. What would be the next step?
Well, the next step is when you use like functional API
in general, what we want to do is to move this
code in a dedicated handler class. So let's do
that. So on one side we will have our
roots, and on the other side we will have
the routes themselves. So the routes
and what the routes do takes a bit
of time.
So here I have created this person
handler and I'm just copy pasting the code,
the previous code here, okay body, here, okay body.
And with the request pass variable. And now my routes
looks pretty much cleaner.
Of course, it can be very, very boring to have
one bean per route.
So the next step is to move all those routes
together into a single function so
that you can write code like this. I will have here
the parent, the parent path. And here I have like
hey, at the roots of the person I
will get all. And here if I get the id, so personid,
I will get one. Just let's try bit very quickly
to see how it works.
I don't want to be too fast. I was too
fast. Yes, bit still works.
And normally same here, I have one put,
I have one miss. That's the all. And if I redo it again,
I have one hit. So it still
works as expected. Again. So far
we didn't do anything regarding reactive.
What we just did is change our coding style
from annotation based to functional.
And yes, I agree, we still have a couple of annotations,
but here you can see that from
the routing, we removed most of it.
Now comes the biggest challenge. How do we
migrate now to reactive? It's very easy at this
point. What we can do is just add an additional dependency
and remove the spring MVC dependency.
So if I have a look at the palm. What I
do is I removed the spring
boot starter web and I replaced it with Springboot starter
webflox. And that's all, that's the only thing
that we did. And on the coding side,
what we had to do is just to
change the package, the name of the primitives,
they are exactly the same. So here we
are using router function, server request, server response,
router functions root serverresponse. Okay,
the code is exactly the same. There is slight
change as well. And I mentioned before that when
you are writing reactive code, you are just not just
calling a function that you will get the
response you are subscribing here. We must
understand that our repository is not reactive
yet, so it still returns data as soon as you call
it. And so we have reactive code that calls
non reactive code. So in order to bridge between those two
renas, there must be a change. We change
from body to body value. And now we can put our like
blocking code here. Let's start this.
And normally it should start quite easily.
And again, let's not be too fast. I will query
one for once. Yes, it still works.
And again, I still have all my hibernate
GPA cache integration. So when I query
twice the same entity, I've got it.
So it's nice, I'm happy about it right now.
But you must remember that what I told you before is
if in your reactive chain you have part
of the chain that is non reactive, well, your whole chain
is not reactive. Your whole chain is blocking. And in
that case we moved from spring
webmbc to spring webflox
and we kept the same data access pattern.
So we are still using GPA, still using hibernate,
still using Gdbc under the COVID And Gdbc
right now is blocking. So only part of our
application is reactive, which means that our application is
not reactive at all. So the next step would
be actually to move away from GPA in a hibernate
towards, well, in the springwheel.
There is a project called r two Dbc. And r two Dbc
aims to replace GDbc in order to be
reactive. So let's do that. And here we have a lot
more changes actually.
So the first thing that we might notice here
is there is no starter and there is no spring
boot starter. So I'm using spring data, r two Dbc.
And well, hoping for the best, I had
to remove the hibernate integration. Well,
I could keep it, but actually it doesn't help us because
now it's no more hibernate. And I replace h
two with r two dbc h two which gives me the way
I still have the same h two. Plus I have
the reactive driver on top of it just as before I had
the h two runtime plus the Gtbc driver on top
of it on the
side of the application. Now something very important,
I need to initialize everything myself. I need
to create the schema because before spring data GPA plus
hibernates created that for me. Now I
have to do that by myself. So here
you can see that I had to create the schema and
I need to call it. So before everything I
have something in my application that says okay,
before everything, hey, I will get a handle
on the schema SQl and then I will get a
handle on the data SQL and then I need to pass
them in order. So I have this database client which normally
is a non blocking database client,
which is good. But now I need to block because
I need to execute them in order and I need to make sure that
those scripts, they are executed before the application really
starts. Otherwise I might receive requests when the database
is not ready. So not great.
Otherwise you will be very happy to know that on the person
repository side the only thing that I had to do was
to change the parent. So instead of a
GPA repository, I can use a reactive sorting repository
and still I have spring data r two Dbc
that handles everything for me. And on the class
side now we can see that actually
what we return when we call repository get
all or find by id is
the types that I told about it
before. Like now I have a flux and
here I have a mono. Now everything is reactive from
beginning to end. Now I don't
call body value, I call a body because actually I need
to subscribe and I need to transform this into a publisher.
Let's see how if this still works.
Bit too fast.
Yeah, really too fast. It needs to compile because
I have added new dependencies.
Yes, it still works bit.
Here I lost the caching.
I lost the caching because before I had caching configured
through hibernate and now I don't have hibernate
anymore. So I've lost the caching which is not really,
really super great.
Okay, let's forget the caching for now
and let's continue our work.
As I mentioned, the really hard part in the reactive application
is making sure that you have no blocking calls in your cloud
chain. So of course if you have a thread that
calls a web service,
you don't care. It must be like executed on this thread
and then the rest of the application can run its life.
But you shouldn't have blocking calls where it's not supposed to
be blocking. So here, in order to make sure of
that, we will add one more dependency which is
called blockhound, and blockhounds is an agent, so you
can install it at the start of your application in development
and it will actually throw a runtime
exception every time it sees that
it detects that you had a blocking call where
it was not supposed to block. So let's start it again
and just make sure that we didn't do anything bad.
It's just to make sure that now our code is really really
non blocking. Of course you can trust me, but as developers
we shouldn't trust, we should just check by ourselves.
So let's check it and
let's curl it again and it still works. So that's pretty
good. I'm app about it. I'm sure that
none of my code is blocking right now, so I
did a pretty good job. I can pat myself on the shoulder.
Now I just want to show you a trick that my colleague
showed me is instead of having this command line runner,
there is primitive provided you by spring
that allows you to do that in a more like reactive
way. You have a connection factory and you can
return a connection factory initializer. So bit doesn't
change a lot, it's just that you don't need to write the code yourself.
The populators, they will be handled for you by spring,
which is pretty good. Now I want
to add a new feature.
Before when I queried
for a nonexisting entity, what happened is it returned
me nothing. Now I want it to return a dedicated HTTP
status. So this is an additional feature and fits
HTTP status. In that case will be not found.
We can decide whether it's a good id or not good id, but it
allows me to show you some nice reactive codes
and in normal imperative code what you would do is you would
say hey, like repository find by Id
if it's null, return four, or four if
it's not null, return what I found.
Now we are subscribing so we cannot do those if
l stuff what we need to do. And that's what I meant previously
by you must really know the API is we
have a dedicated function. So here we have repository
find by Id. This returns a mono of person. As I mentioned before,
mono of person can be like inside there can be
nothing or a person. And so
when it will be time to retrieve the value,
if it can be nothing or it can be a person. If it's
nothing then we need to return a new result.
And this new result is accessed by switching if empty
and then this result will be a mono of
error of this supplier.
So if we now run this code must
probably ask for a new computer.
Yes, now I'm asking for an entity that is not
in the database. It tells me four or four and if
I ask for an entity that is existing database it returns
me the correct entity. Now we are nearing completion.
There is just one single stuff. We lost the cache.
As I mentioned, we lost the cache and well in most cases
we would like to get the same functionality and caching
can be very useful. So the last step is to
repeat the cache. So what we
will be doing is well it's a spring application so
we will add a service layer. So between the repository
and the route or the handler.
In that case we will have this caching service. So how does it
work here? Instead of using
the repository directly, my person handler will be using
the caching service. The service will just be a stupid
proxy, but it's not that stupid because it
will a get the entity
from the cache and bit will check if it's null. If it's null,
well it will do the request and if it's not
null then it will return it. So let's see
how it works now. And for
find all we don't check in the cache, we just put in the
cache when we did the query, just like Hibernate did before
for us. So we are just doing manually what hibernates
and hibernate integration with hazelcast did for us before.
Now let's call the first one.
Oh, I have an error. What happened?
Well what happened is actually I
told you about blockhounds that it was like looking for blocking
calls in places where you shouldn't be blocking. And that's
actually exactly what happened here. So first
we are happy because it detected an issue and
we know that before our code was non blocking. That's really
good. And why? Well, everything here
is blocking. Here we see caching service. The first call is
blocking cache get doesn't return something
that is non blocking it return a person. And that's not
what we want to do. What we want to do is to have everything asynchronous,
nothing blocking. So we will migrate to
the real way to do caching and this is how we
should do it. And as you can see, it might be a bit
hard, especially when you are not used to a
reactive API. Here I will just move
it like this so it's better seen.
So I will use the asynchronous
API from hazel costs and I will wrap it
into a supplier anyway and this returns a completion stage.
And so I need to bridge from the reactive,
well one of the reactive ways to well one of
the ways to do reactive on the TDK to
project reactor and there is this from completion stage.
Now if there is something inside that means that the cache
was hot, I got a result. So I will log it, I will say,
hey, I've been found in cache. Now if nothing was found,
I will switch to empty and I
will do the database call. And then if everything
is fine I will probably find the entity in the
database. So I will put it asynchronously in the
cache and it will be the end. Now let's
start this and
let's check how it works. Yes, it works.
Now I'm sure that my code is reactive because
I have black hound installed. Before it told me hey, you did a
big boo boo. Now it doesn't tell me anything anymore so it's fine.
And here I can have the same, I will check
one again and I can check the log and it tells me hey,
like previously, id one was set in cache, now id one is
found in cache so I don't need to go to the database. Now if I
do the request on everything and I call id two,
it should tell me it's found in the cache. Perfect.
Pretty good. So now demo is finished and the
wrap up is the following. First, if you need to migrate your
spring boot application, I would advise you to migrating to functional APIs.
First, don't try to use the annotations with reactive engine.
You might run into problems, you might confuse this
reactive. Is this blocking? I don't know. So just change away your
code entirely. Remember, when you want your application
to be reactive, your whole call chain must be reactive. Your whole
request response chain must be reactive. And you might introduce some blocking
calls. So in order to be sure about it, of course you have code reviews,
but the best way to do that, like 100% solid
way is to use blockhound. If you want to migrating to reactive. It's more work,
but it's not impossible. If you are using Kotlin, I would really really advise
you to look at coroutines. So if you want to check the
next slide, I will have the link to the repository.
I have a dedicated Kotlin branch. Have a look.
And most importantly, you are engineers, you are developers.
So you make like decisions based on trade offs.
Don't use reactive Eco overdo.
Thanks a lot for your attention. You can read my blog, you can follow me
on Twitter. As I mentioned, you can have a look at the repository on
GitHub and and though the talk was not about Hazelcast, if you
are interested about Hazelcast, you can join our slack or you can train
yourself for free. Thanks a lot again and have a good day.