Jakarta EE based serverless backend-for-frontend
Does this scenario sound familiar to you:
You want a service that runs immediately, is written in Kotlin or Java, and can serve as a backend for your website. You want it to be serverless, as the number of users you have varies significantly. Last but not least, you want a comfortable developer experience.
If so, you are at the right place. It is a path I have taken several times during the past few years. And it is quite a friendly and nice path to walk down. So let us get going.
The runtime
Let us start by getting something up and running on your machine. For that, you will need a runtime. There are lots to choose from, including the Jakarta EE compatible ones and the MicroProfile compatible ones, and also an alternative such as Quarkus.
In order to run serverless, you should choose a runtime that can start immensely fast. That means either running on GraalVM or using a technique such as CRaC or InstantOn. Which one to choose is connected with the choice of runtime as well, as InstantOn for instance works well with OpenLiberty. Helidon and Quarkus are on the other hand battle-tested with GraalVM. There are several options here, of which all are good and sound.
A side note that our industry needs to hear more often: All the options are great tools and great products. Whichever choice you make, it will be a good one that can solve you well.
Once you have made your choice, download the starter from the runtime’s webpage, and get started. It should provide you with a readme on how to get the runtime started on your machine.
Show me the code!
You can get started with Quarkus at https://code.quarkus.io/, and native support comes out of the box. After you download the starter, unzip and do mvnd quarkus:dev, and you are ready.
Java or Kotlin?
As a side note, you can use all the tools, libraries, and ideas in this blog post regardless of whether you prefer to code in Java or Kotlin. I like both. Some runtimes support Kotlin out of the box, while others require some configuration to get going. For more on the topic, see my blog post on this.
Show me the code!
My Kotlin application based on OpenLiberty is available at https://github.com/madsop/enterprise-kotlin
The business logic
What distinguishes your application from mine is the business logic. When you have got the runtime up and running, you can start coding your specific code.
In a backend-for-frontend, you typically need REST endpoints, which you will get from Jakarta REST. You need to either post some data or return some data, probably both, which JSON Binding takes care of. The classes are glued together with CDI Lite. All of these are part of the Jakarta EE Core Profile, so all of the runtimes mentioned above will give you this out of the box.
Most applications also need to store data. In a simple application, storing simply as files on disk might be sufficient, which you can do straight-forward with the Files API that is a part of the JDK. If you need a database, go get one from your cloud provider of choice. I will not dive into the process of setting up your database here, but from your application server, you can use Jakarta Persistence to connect, which is part of the currently newest Jakarta EE, namely 10. You may also use the repository pattern, if you are using Quarkus you can do this with Panache. In the upcoming Jakarta EE release, 11, the specification Jakarta Data will be included, which will give you a standardized way to do this.
A quick detour here brings us to the need for metrics, health checks, config injection, and tracing, which is provided by Eclipse MicroProfile. I find it useful to mention MicroProfile here, as most runtimes that implement Jakarta EE also implement MicroProfile, and the standards fit very well together — Jakara EE Core Profile is also a part of MicroProfile.
That means that you might get away by having no external dependencies at all, and thus, you have minimized the need for maintenance and upgrades.
Show me the code!
I have used Quarkus for a simple backend-for-frontend for some years now. It fetches a list of files from a disk folder, which under the hood is Google Cloud Storage, but could be S3 or whatever. It is not the most stringent code I have written, not at all, but it serves the purpose and has been doing so for years without hardly any caretaking from my side.
You can find it at https://github.com/madsop/viatrumf-scraper-bff , where I am using Jakarta REST, JSON-B as well as a lot of the services from MicroProfile. As I am using files as storage, I have not implemented anything database-related here.
For a more thorough code base, with more code and more of everything, head over to https://github.com/Roedt/ringesentralen-backend . Here, I am using Quarkus as well, and with Panache to connect to a PostgreSQL database running on Google Cloud. As you can see, there is not much configuration required — on the runtime side — to get connected and get going.
Serverless
Granted, you do not strictly need to go native for your Java or Kotlin-based application to act as a backend-for-frontend. If you have a regular stream of users, you can just keep the application running 24/7, and then you do not need to go serverless either. In that case, skip the rest of this section.
If you on the other hand have a use case more like mine, where there is a user dropping by the website every now and then, you can save a lot of money and computing power by going serverless.
The key part of serverless in this context is that your application scales down to zero instances when no one is using it, so you only pay for the actual usage time.
Show me the code!
Be patient, I just introduced serverless here, you will find the code in the next section.
Go native
So how are serverless and native connected? Strictly speaking, they are not connected at all. However, they are mutually reinforcing. When you go serverless, the startup time of your application becomes crucial. Every second spent getting up and running is a second your user is waiting, and the users are in general becoming less and less patient.
A substantial benefit of running your application native is the startup time. Since you do not need to start the JVM and so on, the process of starting your application including the runtime is a fraction of what it would take to start it using normal JVM mode.
If you go native, GraalVM is your friend. All the runtimes that support native mode are using GraalVM under the hood, whether you are talking about Jakarta EE core-based runtimes like Quarkus or Helidon, standalone ones like Micronaut, or even Spring Boot.
Although GraalVM is amazing, the fact is that you are trading build time for runtime. Compiling your code and building your image in native mode takes twice the amount of resources, or even more, than building normally. I have written a post on this tradeoff already if you are interested in more details.
There are alternatives to going native, though. One is to simply not do it, and run your runtime in the regular JVM-based way. If you have patient users, that might be sufficient, but probably not.
I recommend checking out coordinated restore at checkpoint (CRaC) or InstantOn as well. I do not have first-hand experience with either of those two myself, but if you are using OpenLiberty, check out InstantOn, and if you are on anything that does not support GraalVM, or the tradeoffs of going native do not fit your use case, have a close look at CRaC. My impression is that both require a bit of a cumbersome setup, but hopefully, this will become easier and more straightforward as time goes.
Show me the code!
My multi-stage Dockerfile producing a native image is available at https://github.com/Roedt/ringesentralen-backend/blob/main/Dockerfile. Here you can also see how to do things like handling environment variables, certificate files, utilizing the layering mechanisms of containers, and more.
Towards the clouds
Once you have your image ready, it is time to build it and ship it. My guide on how to do exactly that using Quarkus native on Google Cloud gives you what you need.
For a receipt on running the produced native image on Google Cloud Run, look here.
Show me the code!
The cloud build yaml file is available at https://github.com/Roedt/ringesentralen-backend/blob/main/cloudbuild.yaml. Although the syntax may vary for other cloud providers, the concepts are pretty much the same.
So what was the point of all this?
The goals I had when writing this post were to show you that
- It is easy to get started with a Java application, and the path from nothing to production can be short.
- Jakarta EE, perhaps in combination with MicroProfile, and a runtime gets you a lot for free.
- There are many different runtimes, tools, libraries, and cloud providers out there, and many of them are great. By choosing standardized frameworks like Jakarta EE and MicroProfile, you minimize the cost of making a choice, as you can switch to another runtime without changing your code.
Jakarta EE based serverless backend-for-frontend was originally published in Compendium on Medium, where people are continuing the conversation by highlighting and responding to this story.