 So, hello everyone. Welcome to DevNation, Quarkus for Spring Developers. My name is Daniel Brunsinger. I'll be your moderator for today. So, that means I'll be watching the chat and trying to answer some of your questions. And with me, I have none other than Eric Dandrea, who is developer advocate and acclaimed author of a book of the same topic. So, I think he knows what he's talking about. And, you know, it's really good to see so many Java enthusiasts in the call. And to be honest, I'm a big fanboy of Quarkus. The runtime, you know, just doesn't cease to amaze me. And I often think back, you know, when I was a developer and spent much of my time with these old school legacy Java applications and, you know, how much of my lifetime I would have gotten back if I had this kind of technology back then. So, Eric will be showing us all the goodness that is Quarkus, especially for those that are used to developing on Spring Boot. And before we start into that, maybe some housekeeping again, I'll be watching the chat. So, if you have any questions, just drop them in. And I think at the end, we'll might have some short Q&A. So, if there are any kind of questions that I can answer, I think Eric will help me out there and we'll try to answer anything that's left. And with that, the stage is yours, Eric. I'll hand it over to you. Great. Thank you, Daniel. And thank you, everybody who's attending today. As Daniel said, my name is Eric DeAndrea. I'm a developer advocate here at Red Hat. I, an open source contributor, I've contributed to many different things in my lifetime. I was contributed to some of the Spring projects for a bit. And I'm, I currently contribute to Quarkus. That being said, I am not an engineer on the Quarkus team. So, any real deep questions, I'm probably not going to be able to answer it. But I know people who can and I'm happy to try and fumble some, some conversations if I need to. The QR code that's at the bottom here is a link to the deck. So, you scan it with your phone or I think Daniel or somebody can drop it into the chat to where the slides will be. There's not a whole lot of slides and I'm going to try and actually get through them really quick because I know we're all developers and we'd rather just, you know, get hands-on code here. So, as Daniel mentioned, I did write a book about Quarkus, looking at Quarkus for the lens of a Spring developer. It's a free download on the Red Hat developer site. There's also a QR code there for it. It goes through, you know, different conventions and paradigms that people who are familiar with Spring would be familiar with and then how the equate and what the code looks like and how it would work in Quarkus. And actually the examples and the demo I'm going to do today actually comes right from the book. So, if we talk about, you know, why the need for Quarkus in the first place, it's not like, and again, I'm going to use the word we here and I do not include myself in the word when I say we, but it wasn't like we decided that, hey, we need just yet another Java framework out there, you know, just for the sake of it. There was a real problem that we needed to solve and the problem was the warm-up issue with Java. And I'm certainly not going to do this justice and if you're interested in, you know, what the JVM does and how warm-up works, there's a link at the bottom of the slide to a talk that was done by Simon Ritter at Azul Systems, you know, a few months ago. And like the first 13 minutes of that talk, he really goes into really good detail about what the JVM does, how warm-up works, and whatnot. So, the two-minute spiel I'm going to give it here doesn't do it justice. But the short answer is the fast-year applications can warm-up and that warm-up isn't just startup. It's how long till it can get to service its max SLA, right? So, you know, with Java application, when it first starts, you know, the JVM does a bunch of stuff at the beginning, takes a lot of CPU, takes a lot of memory, and then it kind of trails off. But over time, the just-in-time compiler re-optimizes your application's bytecode at runtime based on what the application is doing, you know, the hot code paths and whatnot. It'll re-optimize the bytecode. And this warm-up period could be minutes, could be even hours, depending on the size of the application or what the application is doing. And this is kind of why it's really important, especially when we talk about containers, because typically, you know, in the old days on bare metal or on VMs, we'd give the JVM huge amounts of heap. And so that that warm-up period, we could actually make it a little bit faster. But when we start running in containers, we're typically giving, you know, a pod on Kubernetes a whole lot less resources, less CPU less memory. So the framework that's at play under the covers really needs to be efficient in what it does and how it gets to that maxed warm-up period. And that's kind of the problem that Corkus was trying to address. And so if you think about what a framework does, or the Java runtime does, there's definitely this clear separation between what happens at build time and what happens at runtime. You know, traditionally, your build time is just pack, you know, compiling, packaging your jar or war or whatever, you know, publishing it to Nexa's Artifactory or whatever. And then at runtime, the JVM would, you know, unpackage it, do all, load all the class metadata, class path scanning, annotation processing, finding classes, reading configuration, all that good stuff before it can finally figure out what it is that it's trying to do. And then it can start servicing requests. And so what Corkus tries to do is kind of flip that model on its head. If you're running applications in containers and each instance of a container, you know, you're going to scale your applications horizontally, each instance of the application is going to be the same as every other instance. So why should you have to pay that penalty for all that warm-up stuff each and every time, right? What Corkus tries to do is not just optimize, not just package your application, but do some of that pre-warm and some of that optimization that the Just-in-Time compiler would do. And then just, and you probably can't see me, but I'm, you know, kind of using my hands here, serve it up on a silver platter to the JVM and say, hey, you know, Mr. Just-in-Time compiler, I've already done some of this work for you. Why, you know, you can just kind of take it and run with it so we can get to that max SLA time. And kind of, and I don't want to say side effect, but it kind of is a side effect. A side effect of the design in the first place is the fact that with Corkus, you can build and deploy as a native binary. And we kind of come full circle in industry. You know, we start JVM, the Java JVM was invented to solve the right once run anywhere. And now we're packaging applications and removing the JVM and packaging them specifically for particular runtime platforms. But the fact that I can do that, and I've been able to do that since 2019, you know, we're, you know, mid 2023. So we're going on four years of being able to do this is just kind of a side effect of the design in the first place. You know, the optimizations that Corkus does for the JVM apply to native image. For those that are familiar with Spring and who have done Spring, the question I'll typically ask if I'm doing this talk in person is, you know, the choice between reactive, you know, if I'm going to do Spring Web Flux or Spring MVC, when do you need to make that choice? And the answer is, when I create my GitHub project, right, I have to create a GitHub project, and I have to make a decision about what the architecture of my application is. But yeah, I don't even really know what it is that I'm building, but I have to choose what the architecture is. And with Corkus, it doesn't really matter. It doesn't matter. Corkus will just, you can do it either way, you can even mix and match within the same classes in Corkus, we'll just figure it out. I'm going to talk about developer joy in the demo, I'm just trying to get through the slides as quick as I can. But one part of Corkus that people that we don't talk often enough about, I think, is the developer joy. You know, how quickly can a developer be productive? I know Daniel alluded to this at the beginning. When he talked about, I wish I had some of these tools at the back in the day, you know, being able to continuously test my applications and being able to run my applications and debug my applications and never have to recompile or restart my application. So that part of it is really, really unique to Corkus. I'm going to show some of this in the demo. But at the end of the day, it's still Java, still using all the same tools and technologies that you're already using today. Microfile, you know, Cassandra, Kubernetes, Kafka, camel, key cloak, you know, whatever the technologies that you're using today is already there. It's not like you have to learn yet, you know, another new framework or anything like that. And so the interesting question that I'm going to ask people to keep in their back of their minds, it's quite a simple question. But the answer is, you know, I typically when I ask this, I have people look at me like I have six heads coming out of my body here is, what if I told you, given your application portfolio that you have today, what if you could run everything that you're running today, but spend 50% less on your infrastructure and operational costs? That would kind of be a no brainer, right? That's kind of the value proposition that we're talking about here. You know, it seems like an easy question, but you know, that that's what we're really trying to get to. And that's that's what I'm going to show in this demo. I'm really going to show that you can really save, you know, 60 or 50, 60, 70% of your resources of what you're actually operating today. And so with that, I'm going to switch over to a demo. Excuse me. Daniel, please keep me honest for time here, but I do I have an application. It's a spring boot application. It actually comes right from my book. So you can see, you know, I've got the GitHub repo from my book here and I'm in the chapter four spring data JPA project. And so I have a pretty, pretty standard spring boot application. It's using the latest, you know, released version spring boot 3.1.1 running Java 17. It's using spring data JPA, spring NBC, some validation stuff. It's got the actuators that uses a Postgres database, and it's even using all that new test container stuff that that spring's been advertising, you know, with the 3.1 release. Configuration is pretty simple. It's got a spring data JPA entity class here with a couple of fields. It's a pretty simple domain model. It has a spring data JPA repository class with an extra method. And then it has a spring NBC controller class for, you know, gets inputs and posts and whatnot. And then it's got a whole bunch of tests. Those who don't know me know I'm very into testing, especially test driven development. So there's a whole bunch of tests here. And I mentioned the spring data or the new spring test containers integration. So I could run the application in test mode if I wanted to. And it would automatically spin up a Postgres container because I've, you know, I've written this test configuration class and I've used all the new service connection stuff and I've told it what database I want and whatnot. So if I run this application, I probably should have compiled it first, but me and we'll skip the tests for now. Believe me, they're there and they work. Trust me, you know, everybody says trust me when they're doing demos, right? So I'm going to start up a database. I'm just going to use a container runtime. And that's not what I wanted. Let me stop that. That's, I just ran this a few minutes ago, maybe let's try that again. Now I'll run a Postgres container. There we go. It's up. So now that I've run my application, I'm going to do a Java dash jar target. And I'm going to start the application up. All right, so it's up. So let's do, I'm actually going to do some note taking while we do this. I'm just going to go into, there's a readme file and I'm just going to make some notes here. So let's talk about startup time. And this is probably not the best way to measure startup. The better way to do it would be to measure time to first request, but that requires some scripting and whatnot. And we're in a limited time demo. So as long as I stay consistent, it's not ideal. But so we've got 3.176 seconds. But now what if I actually interact with that application a little bit? So I'm going to use the HTTP utility 8080. We've got some fruits. This little bigger. So you can see I get some fruits back. If I ask for an apple, I get an apple. If I ask for a pineapple, I get a 404, meaning I don't have one. I do have, if I do a post, I can post some JSON to the application, which adds the pineapple. If I then go back and I ask for all my fruits again, I get a pineapple. And if I ask for my pineapple again, I get it. And so let's compute. So I have a script that will actually compute the RSS or the resident set size. And what the resident set size is the total process space of how much memory the application takes. So in this case, it's 396016 into 1024 is 386.7. 386.7 megabytes, right? All right. So now I kill the application, stop the database. We talked about this whole native thing as well. It's with Spring Boot 3, you can actually compile to native and do native compilation and eliminate the JVM completely. So let's actually do that. So if I come back and I do Maven, so this will take a minute or two. So while this goes, the whole conversation about native image, that wasn't what I wanted to go on command. There we go. That's the right command. So while this goes, this whole native image thing that my own personal opinion with this is that it's like this new shiny widget that's flying throughout our space that everybody's chasing saying, hey, I want to do native image. I want to do native image. Well, the question is, well, why do you want to do native image? What's your use case? I kind of think of it as Kafka was a couple of years ago. If you think about five, six years ago, everybody was saying, hey, I want to do Kafka. I want to do Kafka. Well, why do you want to do Kafka? What's your use case? Kafka may not be the best fit for what it is you're trying to do. And so I almost feel like native image is following that path where native image has a very select set of use cases. And I would venture to say probably 70 to 80% of your applications belong on the JVM and not in native image because especially if your application is long running, the JVM is going to far outperform your native image. If you think back to kind of the first slide I talked about with the just-in-time compiler and the reoptimizations it does, the just-in-time compiler is extremely good at what it does and how it does it. And when you compile the native image, that just isn't there anymore. You've removed that. Plus you've removed all the garbage collectors and whatnot. So for an application that runs for a long period of time, you're probably going to get better performance and better throughput. Regardless of framework, regardless of if it's corkis or micronod or spring or whatever, you're going to get better throughput over time for a longer-running application on the JVM. The native image is going to give you really small memory footprint and really fast startup times. So it's really good for if you need like instant scale-up or resource constrained devices like edge devices or whatnot. Or serverless when I want to run it on Amazon Lambda or Azure Function or something like that. Really good for that. But I would venture to say for most business use cases that the JVM is probably your better option. And so what I'm doing here is I just compiled a native image. I'm not doing containers or anything like that. So it's just I have girl VM on my laptop. And if we take some stats here, so my build time was two minutes, seven seconds. My build RSS. So the process of building a native image is memory intensive. And so in this case, girl VM will tell us it was 8.25 gigabytes of memory. And the binary size that it produced was 131 binary size, 131.55 megabytes. All right. So now take notes here. Startup time. We'll do this kind of the same test that we did before. So now that we have the native image, I'll start my database backup. I then run my application. Let's see. It started up in my startup time where is it 0.256 seconds. So that's a whole lot faster than the JVM. And then if I run the same request, and I actually have a script that will do the same request so that you don't have to watch me type it again, but it basically runs all the same requests in the same order that I did before. And then if I compute the RSS, I'm down to 147.584. So 144.1 megs. I would stop that. But you can see the native image from the JVM, it's way more than a power of 10 times faster. I've shrunk my memory footprint by less than half or more than half. But what might that look like in Corkus? And could I do that in Corkus without touching a whole lot of code? And so let's explore that a little bit. So what I'm going to do is I'm going to come back to my POM. And because we are somewhat limited on time, usually I'll go kind of line by line here, but I'm just going to replace the whole POM. And what I did is I eliminated the parent POM because it's not a spring up anymore. I added some parameters in here. I know Corkus 3.1.3 just shipped this morning. I'm not going to take that for this just because I haven't tried it myself. But then if you notice, I've swapped a whole bunch of the dependencies with some Corkus in spring dependencies. So I've got spring web, spring data, JPA. I've still got the hibernate validator. I've got the small rye health in here. I've got some configuration. I've got a postgres extension. And then I've got some J unit stuff. I've got assert J. And then I've got rest assured. Notice there's no more test containers stuff in here. I've eliminated all the test container stuff. And if I go into the properties file and I can change that up a little bit as well. It looks kind of similar. Basically I'm setting up the location of my database. And then I haven't really touched much code. So before we do anything, let's go into Naven, Corkus, Dev. Let me do a clean. So I'm going to actually start up Corkus in its Dev mode. And it fails. Well, why does it fail? So first it fails because of this application class that every spring application has. Well, Corkus doesn't need that. So I can just go ahead and delete that. Delete anyway. And then there's a corresponding test that goes with it. Now, this is kind of like my favorite thing. When I was a younger developer, I've been doing this for 20-something years. When you're brand new to industry, it's always like you write all this code, and then you go to your team lead, and you show them your code, and you're so proud of all this code that you wrote. But as you get seasoned and been doing this for a really long time, the more code you write is the more maintenance and more surface attack errors. So now I go to my team leads after I write some code, or actually I delete code, and I'm like, hey, I'm so proud. Look at all this code that I deleted that we no longer have to maintain. So now that that's gone, let's start this. And there's a bunch of compilation errors, mostly in the test. So we'll come back to this. But you can actually see the application is up, and it's running. And you notice I did not start my database. So I didn't manually start a database, but Corka saw it's okay, you have this Postgres thing in your palm, you didn't configure it. So let me actually go and start a container for you. And it uses test containers under the covers, but it didn't use any of this code. And in fact, this test containers code that I had to write in Spring Boot, I'm just going to go ahead and delete it because I don't need it. Then I can go into my tests, and I can delete the import for that configuration class I had to create, which I've since deleted, and I can delete that. So I've just gotten rid of a whole bunch of code that I don't even need because Corka is doing it for me for free. And to prove it, I can actually do that same HTTP request. And I actually get nothing back. But if you look, it's because in my configuration, I've said that the import file should be called import.sql, not data.sql. So I could either change my configuration or I could update the name of the file. I'm just going to update the name of the file, call it import. And then if I come back, now I have some fruits. So I didn't even have to recompile or restart anything. Furthermore, I can turn on this little, it says press R to resume testing. I can push this little R, my R key here. And it's actually going to start running my test, which it says it can't find any. Well, that makes sense because my tests actually don't compile with them all. So what we can do here is I can convert this test. So we've got this Spring Boot test annotation. We can get rid of that and just say Corka's test. I've got this transactional annotation. I'm going to swap that for Corka's test transaction. And what that means is every pass is going to run in an isolated transaction. And if you look, as soon as I hit save, it found the test. And now it ran the test. And this test now, this test class now passes. I still haven't touched a line of code in my application, but yet my tests now pass. I can come over and do something similar here in my controller test class that uses mock MVC. So I'm going to say at Corka's test, again, we don't need this mock MVC thing. And instead of mocking my repository class using mock theme, I can say inject mock. There's a whole bunch of tests here. I'm only going to do one of them for time, but you would kind of get the idea. So I'm just going to comment out a whole bunch, those other tests, and I'm only going to work on this one test here. So rather than say use mock MVC, I just have to switch it to use rest assured. So I can say press rest assured.get, not given get fruits status of a, expect a status code of 200. I can expect my content type to be JSON. And then I'm just, I can say, I'm going to copy some of this stuff here instead of, instead of dot and expect, if I could type right, JSON path, going to say dot body. And instead of this, I'm just going to say is, which is from the Ham Crest Matters library. I do the same thing here is, is now if I save that, I can get rid of this throws exception. Don't need that anymore. Now my tests pass. As soon as I save the file, my tests now pass. So I have not touched my application. So it's still a spring data GPA repository class. It's still a spring MVC controller, but now all my tests pass and I've converted to corpus and I've eliminated a whole boatload of code that I've been able to delete, you know, all that new test container stuff that came with spring root 3.1. I don't need it in corpus site. I can, I can delete it. So now that I know my application is good, let's actually compile it. It's not native yet. We'll do native in a second. Back to my, where's my little cheat sheet here where I'm keeping notes. So now that we're going to run the application just as a Java dot jar, now I need to restart my Postgres container. And I can say Java jar. I can run the corpus app. So startup time, where's it here? 1.276 seconds. And let's use the application a bit. So I'm going to run that same script. And then I'm going to compute the RSS. 275232 into 1024268.7. So that's what we'll compare that when we kick off the native build. So let's start the native build. And then let's talk about this. So spring on the JVM was 3.1 seconds, 1.2 seconds. So we're well under half the startup time. The memory usage is 386 to 268. So we're not quite half, but well under 100 megabytes smaller. And again, exactly the same app. I have not touched the line of code of the application. Because the optimization that corpus does on the JVM, it really helps with the JVM as well as native image. Where what spring is doing, yeah, I can compile my application to native using spring, but there's not a whole lot of optimization and benefit on the JVM specifically. And one other kind of interesting things for you. If we go back and we look at the native compilation from when it compiled for spring, scroll up a little bit here. So the grow VM compiler will output some statistics about your application. So it'll tell you about how many classes and methods and how much reflection the application is doing. So the spring application was using 37,000 and change classes, almost 80,000 fields and 260,000 methods. And if you talk look at reflection, you know, 1600 classes, 700 and some odd fields and 8,000 methods registered for reflection. But if we look at what corpus is doing, you know, kind of goes to the design. We've got, you know, 20,000 classes, 28,000 fields and, you know, 98,000. So in only 700 classes, 300 fields and 4000 methods registered for reflection. So just the design in the first place of corpus makes it a whole lot smaller. There's a whole lot more efficient and helps with the startup time and the memory footprint. So if you look at native now, you know, so we've got our build time. Our build time was a minute 22 seconds. Two seconds are build RSS. If I could smell right. Our build RSS was 6.87 gigs. Our binary size is 73.74 meg. So if we compare two minutes seven, so we shaved about 40 some odd seconds off the build time, we've saved about a gig and a half of memory space to actually build the native image in the first place. And our binary is, you know, if we round is, you know, close to half the size, you know, maybe a little bit more than half the size. Again, exactly the same application. I've not touched a line of code. If we look at the startup time, database backup. I do. And our app is up and it started in 0.094 seconds. And if you're looking for that is it's over here. 0.094 seconds. And then if I use the application like I did before, memory size 72, 128 into 1024, 70.4 megs. So let's compare with the springing. So our startup time 0.256 and we're 0.256 milliseconds to 94 milliseconds. So we're two almost three times faster startup and memory footprint is less than half the memory footprint of the spring application. Again, exactly the same application. I have not touched a line of code. I can even prove it. You can look at the change set. You know, there's not, the only thing I deleted was this one class. I have not touched a line of code here. So what does that all mean? So if we come back to my slides here, what does that mean? So, you know, I just kind of showed this to you on my own, on my own laptop. You know, again, this is not a controlled environment. You know, it's on my laptop. I'm running a Mac M1. But, you know, does your mileage vary? And the answer it really is now, if you look at some of these benchmarks that are out there, the top left one is from tech and power. You can totally see, I don't know if you can see where my mouse is, but, you know, spring, the fastest or the most performant was spring web flocks. If we go down to spring data GPA, you're at like 12.3 or 25% efficient to compare to the Corkas with hibernate. What's also very interesting, I just, if you want to look at numbers that we ran in a performance lab, so I ran these numbers yesterday in our performance lab, looking at the latest Corkas versus the latest spring, you can start to see some of these patterns emerge. So like the RSS, so the after startup versus time to first request, I mean, the Corkas time to first request is about half of what the spring version was, the, how much RSS was it using after the first request. The throughput number here is phenomenal. If you look at, you know, if I were to funnel as many requests at it, you know, I could handle with this particular application that we use 28,000 requests per second on the Corkas one, but only 7,000. That's almost four times the amount of throughput that I got from my application. And from a memory perspective, even though the memory was similar when it was at max throughput, the fact that if I wanted to handle the same amount of load, I needed four instances of the spring version to do what one instance of the Corkas version does. But if you then kind of compare over to native as well, I mean, time to first request the native Corkas versus spring, like five times faster, use less than half the amount of memory, still giving you way too much, way more throughput. I mean, if you look at the throughput numbers for spring on the JVM versus spring in native, there's not a whole lot of difference. Like the throughput is pretty consistent, but the throughput that you get with Corkas on the JVM kind of goes back to the design in the first place, you know, making it more efficient and optimizing what Corkas does so that you get the benefits of this throughput and performance on the JVM as well as native. Because I, you know, I'd mentioned, you know, the native was almost like a side effect. But this other thing that's interesting, this throughput, this max throughput density that we talk about is, you know, how many of these applications, if you were to get at your max throughput, how many of these applications could you kind of stuff into a Kubernetes cluster in a pod or whatnot and get the same density of there. And in this case, being able to handle max, I could, I can get 16, what's 67 into 16 here. I get over four times the density on Cork, even on the JVM. So if Corkas on the JVM, I get four times the density, then I do spring native. So if people think that I have to compile the native to get density, well, I'm getting four times the density on the JVM and still using all the same tools that I have. And we've also run tests on, you know, how much carbon emission and carbon footprint and how much more efficient does the framework matter. And I know nothing about how to measure carbon footprint. So there's a there's a link at the bottom here, if you want to read about how it was measured and whatnot. But we actually found that for similar application, Corkas, the Corkas application emitted two to three times less the carbon footprint than the spring version. And we could get the same SLA on a T2 micro that we needed a T2 medium for on the spring version. And as you all know, in the cloud, you know, if you get the bigger the instance type with, you know, more horsepower and memory footprint, the more it costs and the cost is not linear, right? So if you need twice the amount of memory, it costs you way more than twice the cost. And so I guess the question now, you know, I started off, you know, what if I told you that you could run all of your applications that you're running today with half the amount of spend, you know, would you believe that? And, you know, from what I've shown, would you believe that? I mean, it's proven it's like it's been run in the lab, I did it here for you, it really can save you half the amount of your operational costs. And so with that, I think that's about it. So if there are any questions, I'm happy to to take some questions. So thank you, Eric. I mean, that was quite impressive. And I think most people were really, you know, sort of looking at your screen and not really in the comments. There were some questions just around the Quarkus website. And if this recording will be on YouTube, but nothing on Quarkus yet. So if you have any questions, now is your chance to ask Eric, we still have a few minutes. Yeah. Yeah, I cut some I usually do the demo a little bit longer, but I knew I had a shorter time slot here. So I tried to condense it a little bit. But, you know, I'm happy to reach out to me on Twitter, LinkedIn, all my socials, you know, happy to chat with people and talk about this a lot because it's, you know, it's one thing to read about it on blogs and whatnot, but it's another thing like to be in a room with somebody or to actually watch them do it kind of like I've done here and be like, well, that's, you know, there's no tricks at play here, you know, there's no demo engineering going on here. Yeah, and it's super easy to get started, you know, just head over to the website, click on start coding, use a generator, and, you know, you're good to go. Yeah. Yes, I don't see any questions coming in. So I appreciate everybody's time, you know, taking the time to come and listen to me talk. Yeah, I'll be back at, I think, two 15, two o'clock or two 15 Eastern time today. I have another talk I'm talking about contract testing and the packed framework and microservices testing. We use Quarkus there as well. But the focus of that talk really isn't Quarkus. It's about, you know, how to, how to test your microservices in a microservice environment, you know, and be confident that you're when you push things to production, all hell isn't going to break loose and things aren't going to break. Yeah, testing is important, definitely. So yeah, I think with that, probably wraps it up for now. Thank you, Ravi.