Conf42 Enterprise Software 2021 - Online

Migrating from Imperative to Reactive

Video size:

Abstract

While Reactive Programming is very different from the usual Imperative way, there’s no denying it fits “the Cloud”, as every bit of resource is used to its fullest. Let’s see how to migrate from the latter to the former using a Spring Boot web app as an example.

Summary

  • Nicola Frankel: How you could migrate to reactive? I work for a company called Hazelcast. We have two products. The first one is an in memory data grid. The other one is Haslko's jet and it allows to do stream processing in memory.
  • The reactive manifesto lets four properties of a system to be considered reactive. The most important characteristic is it's message driven. Non blocking is at the roots of reactive and reactive streams. There are probably good reasons to go reactives but not so good reasons.
  • Project Reactor has no dependency on spring. On the opposite, spring depends on Project Reactor. Spring Webflox allows you to use annotations. It's much easier to say we will use spring webflox with the functional app.
  • In this demo I want to show you how you can easily cache your data from the database. The first step is to move to web MVC FN. At some point we will lose the automatically configured caching. We will need to add it explicitly.
  • What we can do is just add an additional dependency and remove the spring MVC dependency. We move from spring webmbc to spring webflox and we kept the same data access pattern. Next step would be to move away from GPA in a hibernate towards a project called r two DBC.
  • Instead of using the repository directly, my person handler will be using the caching service. What we want to do is to have everything asynchronous, nothing blocking. And as you can see, it might be a bit hard to get used to a reactive API.
  • If you need to migrate your spring boot application, I would advise you to migrating to functional APIs. Don't use reactive annotations with reactive engine. Use blockhound to migrate to reactive. If you are using Kotlin, really really advise to look at coroutines.

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.
...

Nicolas Frankel

Developer Advocate @ Hazelcast

Nicolas Frankel's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways