java Conference - Serverless Architecture Conference https://serverless-architecture.io/tag/java/ A Conference for Mastering Cloud Native Architectures, the Kubernetes Ecosystem and Functions Mon, 07 Oct 2024 07:49:42 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.2 Cloud Native Serverless Java with Quarkus and GraalVM on AWS Lambda https://serverless-architecture.io/blog/cloud-native-serverless-java-with-quarkus-and-graalvm-on-aws-lambda/ Mon, 20 Sep 2021 14:58:11 +0000 https://serverless-architecture.io/?p=84721 If you haven't shouted "Bingo" yet, you have only yourself to blame. How can it be possible to use almost all of the bleeding-edge technologies, frameworks and platforms listed above successfully together in a real-world project away from the greenfield and Hello World demos? A field report.

The post Cloud Native Serverless Java with Quarkus and GraalVM on AWS Lambda appeared first on Serverless Architecture Conference.

]]>
With this article I deliberately don’t want to write a guide or description of a framework, explaining how to use it and for what purpose. Since I like to work with new technologies, I gave myself the following challenge over the last few months: Is it possible to combine many of the new and current frameworks in such a way that the application can ultimately be used while keeping the programming and technical effort within manageable limits? Based on this question, I would like to report my findings here. Details and instructions regarding the components used can be found on the respective websites.

As a real-world application that is in production, I have chosen the login/registration app of the Java User Group Darmstadt, an organization of which I am an active member. This application was created three years ago as part of my serverless book project and has been running since without the help of a web framework like Spring, Java EE or similar. It is based on Java 8 in AWS Lambda. In the meantime, the application has evolved, like so many projects, and the code has, as they say, grown historically.

The application can only be run locally to a limited extent, changes become more complex and sometimes lead to quite fragile code. So it’s high time to do a complete review of the code base and update it.

From a technical point of view, the application is quite clearly structured: For any JUG DA event, interested participants should be able to register easily and without obligation via a web form. This allows us to get a good overview of the number of participants in advance of the event and, if necessary, make adjustments with the room and catering organization. If a participant is successfully registered, we send a confirmation email. This area is publicly available, so there is no need for the user to log in. We have deliberately dispensed with user accounts for various reasons. Organizers access completed registrations via a secured area of the application. For this, the login of an Orga member is necessary.

STAY TUNED!

Learn more about Serverless Architecture Conference

From where we are to where we want to be

The application consists of a browser frontend, authentication/authorization, data processing, data storage, and email sending. To render the frontend, I resorted to a server-side solution using the Handlebars templating engine. I deliberately chose not to use a JavaScript-based single-page application in order to have as few individual components as possible. I ultimately wanted to manage only one or at most two deployment components. All HTTP(S) handling is done by the Amazon API Gateway, which also configures which paths of the application are provided with a so-called ‘authorizer’ to make them accessible only to authorized users. Within the application, I created and managed my own service classes using singletons. Singletons sound old-fashioned and fragile at first, but in Java-based AWS Lambda functions, they are quite justified, as there can be no competing threads at runtime: Each thread is processed in its own Lambda instance. Data is stored in an Amazon DynamoDB NoSQL DB and emails are sent via the Amazon Simple Email Service – AWS provides a Java API for both services. In the end, this left me with four Lambda functions (registration, deletion, admin, and authorizer), an API gateway mapping template, and a DB table that I deployed using the serverless framework; several individual components indeed, but very easy to manage as a single unit in a project through the serverless framework. The legacy code can be found in the GitHub repository under the tag legacy.

However, using the Java runtime in AWS Lambda has the disadvantage that the startup times of Java-based functions are relatively high. This is not a problem in asynchronous, event-driven data processing pipelines, but in context with user interaction (i.e. a website, for example), it can quickly lead to undesirably high latencies and thus to dissatisfied users. The only workaround so far has been to increase the provisioned memory for a Lambda function so that it is also allocated more CPU power and network bandwidth. Even if you don’t need the actual memory, this can lead to reduced costs because the execution time decreases. Additionally, you can keep an instance of the Lambda function “warm” for some time by calling it periodically with cron events from AWS CloudWatch. It might sound weird, but for a long time it was really the only way to do this. Meanwhile, AWS offers the option of “reserved concurrency”, which allows you to specify how many instances should be available pre-warmed (initialized), delivering faster response times. The continued use of AWS Lambda was decided, firstly because I am a serverless fanboy, and secondly because the cost for our use case can be kept at a very manageable level of zero Euros as the free AWS usage quota is not fully utilized.

This is where various new technologies, frameworks, and platform options come into play. With GraalVM, it is possible to compile a Java application into OS-native code and thus execute it more efficiently and also conserve memory, but the effort and barriers to entry are not insignificant if one has not worked with it before. AWS Lambda also does not offer a preconfigured runtime environment to run native binaries. This only became possible with the Custom Runtime API, with which one can create, upload, and use any custom runtimes.

Then, in early 2019, Red Hat introduced the Quarkus framework, which aims to shine with fast start-up times, convenient hot-reload options during development, and the option of native compilation using GraalVM via simple command line parameters. Designed for the development of microservices that are later executed in containers, it plays in the same camp as Micronaut and Helidon, for example. However, Quarkus also supports the development of AWS Lambda functions. These features initially made Quarkus interesting to me and led me to investigate and also use the framework in terms of revising the JUG-DA registry.

Having an opinion

Quarkus is considered an “opinionated” framework, so it does things in its own way and according to its own opinion. And that is exactly what we should keep in mind. What Quarkus can do and does, it does well, but in its own way and on its own terms. Thus, the framework may be excellent for certain use cases, and not at all for others.

I tried to go in with an open mind, and started by looking for a plugin for my IntelliJ development environment. There is already a plugin, but it is in a very early stage (version 0.0.3) and does not offer many features yet: only a wizard to create new projects or modules based on the generator of https://code.quarkus.io, and autocomplete based on the language server for Quarkus properties in the application.properties file. A debug facility is not (yet) included in the plug-in. Instead, when a Quarkus app is started in dev mode, a debug port is automatically opened so that you can connect to it with a remote debugger from the IDE. So that’s something. But that’s just the way it is when working with young frameworks for which the ecosystem and tooling are still in their nascent stages.

As a first step, I added the required Quarkus libraries as dependencies to the Maven pom.xml. I chose a solution using RESTEasy as the JAX-RS implementation and the AWS Lambda HTTP extension for Quarkus. The AWS Lambda extensions are still in “preview” status, so API and properties may still change during development. After all, when you’re “living on the (tech) edge” you’re used to that.

After that, I set about rewriting my Lambda function classes that covered HTTP event handling into JAX-RS annotated classes, and changing my own service class management from static singletons to CDI. This was basically straightforward; I’m familiar with the APIs of JAX-RS and CDI, and I also know the existing code well enough to not run into any problems here for now.

Quarkus uses the Highlander Principle – there can only be one!

After getting to a point where the codebase was compiling again after making the first changes, I was curious to see how it behaved when starting the application. But “Bang!” – the application doesn’t start. The Quarkus Maven plugin tells me that several handler classes were found, and that there was another custom handler in addition to the QuarkusStreamHandler from the quarkus-lambda-http extension. This is actually correct, this is my authorizer function, which will be called separately later by the API gateway and thus needs to be deployed separately as well. Until now, it was possible to manage multiple handler classes in one project and deploy them to different API gateway paths without any problems. With Quarkus, this is no longer the case. So here it is, Quarkus’s first strict opinion: There can be no other (handler class) except me! The Highlander Principle with a twist.

With Quarkus, there must be only one possible entry point into the code in a project (or module). This makes sense, but isn’t always helpful in the context of a serverless application with multiple related Lambda functions. At this point, however, I didn’t want to deal with this, and the topic of security would have to wait until later. So I deleted the Authorizer class and restarted the application. This worked surprisingly quickly and produced no errors without the handler class. A first HelloWorld request was also successful immediately. Was it that simple after all? I would have been amazed.

Another templating engine?

Now, as is well known, you shouldn’t count your chickens before they hatch: I tried to call the registration form. Unfortunately, this didn’t work, because the Handlebars template engine in Quarkus has its problems. I couldn’t locate what the problem was exactly. I guess it could have been class loader problems, because a call from Quarkus to the Handlebars engine resulted in a FileNotFoundException, but the files were exactly where the code wanted to find them. I tried several things to no avail. In the end, a normal templating engine would not easily work in a native image either, because most engines rely heavily and extensively on reflection. Native compilation means that the code is statically checked for all possible branches at compile time and only these resources are packed into the native artifact. Code that is used dynamically via reflection at runtime cannot be analyzed at compile time and is ultimately not included in the generated artifact. If you want code used via reflection to be included in a native GraalVM build, it must be specified in a config file at compile time so that it can be included. Templating for many frameworks means resolving a lot of code via reflection, which would end up in an almost unmanageable GraalVM configuration.

No templating mechanism was apparent in the Quarkus ecosystem at this point. There were only GitHub issues expressing a desire for them, but with no timeline yet. For this reason, I continued my search for a suitable templating framework and initially found what I was looking for with Rocker. Rocker is a templating engine that does completely without reflection and generates pure Java code at compile time, which ultimately generates the templates with near-zero copy rendering. A very interesting concept that I wanted to further evaluate and use later. In my eyes, Rocker would have been a good framework to integrate with Quarkus. After all, we don’t need another templating engine on the market, there are already (too) many good ones, and continuing to use a good solution makes sense after all.

As developer life goes, I couldn’t develop the application for a couple of days due to time constraints and left it lying around. At the same time, I read the first tweets on Twitter about Qute – the templating engine for Quarkus. Honestly, my first thought was: Wow, besides Quarkus they must have had a very strange name left that they didn’t know what to do with. Yes, naming things is hard. But back to the topic.

A new templating engine has appeared, still bearing the “experimental” tag, which means “we’re just trying it out and maybe we’ll throw it away”. Anyway, living on the edge! The main thing is that it works with Quarkus, and my templates are not that highly sophisticated.

The application, or rather the integration into the code and the syntax of the template functions and placeholders are, of course, different from other templating solutions, but I already expected that. However, rewriting the templates and Java code was just a simple case of working through the changes – nothing complicated. Since Qute is still quite young, there are naturally not many functions included yet, so almost only simple property substitutions, if conditions, and loops work. For this, there is already a @TemplateExtension annotation, with which custom template extensions can be implemented if Qute does not yet provide the desired behavior.

For example, at one point in a template I wanted to react to the presence of a key in a map:

<div class=”{#if myMap.containsKey(‘name’)}has-error{/if}”>…</div>

Unfortunately, containsKey() is not yet supported on maps. Eventually, however, I was able to create exactly this behavior with my own extension (Listing 1): If in the template code of a map (first parameter of the @TemplateExtension method) the containsKey() function is called (name of the @TemplateExtension method), then this method is executed. Further parameters can be passed from the template, for example the key here. But it can be assumed that in later versions such functionality will be implemented directly in Qute.

public class QuteExtension {
  @TemplateExtension
  static boolean containsKey(Map&amp;amp;amp;lt;?, ?&amp;amp;amp;gt; map, Object key) {
    return map.containsKey(key);
  }
}

 

Develop state-of-the-art serverless applications?

Explore the Serverless Development Track

Security

After the application was up and running with its frontend and successfully rendered templates, it was time to address the issue of security in terms of authentication and authorization. As a reminder, in the context of AWS Lambda and API gateways, a Lambda function does not know an HTTP stack, but only an event with attributes from an HTTP request. The API gateway handles the actual HTTP communication with the requesting clients and then forwards the data to the Lambda function in the form of an APIGatewayProxyRequestEvent. If requests for a certain path, e.g. /admin, should only be forwarded for authorized requests, this must be configured in the API gateway and this path must be provided with an authorizer (IAM, Cognito or a separate Lambda function). This gives you at least two different entry points in the API gateway; for example /registration for publicly accessible registration to an event and /admin for administration. However, both paths can point to the same lambda function if it is implemented correctly and also evaluates the paths. This is the case when using Quarkus, RESTEasy and the Lambda HTTP extension.

Since the previously mentioned Quarkus Highlander Principle does not allow other Lambda functions in the same project or in the same runtime classpath context, I first tried to handle the authentication and authorization issue directly in the Quarkus application. The framework also already provides some extensions for this. I use a simple basic authentication in the application, and so I implemented the Properties File Based Authentication for a first test.

Basically, this worked, even though the extension pulled the dependencies of what felt like half an Undertow HTTP server into the project, which I don’t need at all thanks to the API gateway (and don’t want to have in my project). I say “basically” because the Quarkus application writes a WWW Authenticate Basic header in the response to a request without an Authorization header, which should inform the client that the corresponding credentials are missing. However, this header is rewritten by the API gateway into an x-amzn-Remapped-WWW-Authenticate header. Thus, the client no longer recognizes that authorization information is missing. This mapping is not changeable and this is absolutely correct, because for the actual place that does the HTTP handling – the API gateway – no authorization of the request is configured to begin with. So the gateway must not return the header unmapped, because a request with a correct header would then also have to be checked and authorized by the API gateway. In this case, the API gateway is not only a proxy that forwards the HTTP request to other HTTP servers, but also performs other tasks in the overall context, including the authorization of requests. And the Lambda function is not an HTTP server either; I’m just using Quarkus, a framework that happens to be able to speak HTTP.

So we have no choice but to authorize requests in the API gateway and use an authorizer function there for all /admin requests. A Java class in the same project will not work for the above reasons. However, I also wanted to avoid this turning into a Maven multi-module project just because of a single class, if possible. Thanks to polyglot programming and the possible Node.js runtime environment in AWS Lambda, I was able to implement the authorizer function in JavaScript in the end. It doesn’t interfere with the Java classpath of the Quarkus application, and I can manage the function in the same project. When deploying, of course, the function has to be packaged and uploaded separately, but this is easily done with the serverless framework. The configuration file for deployment with the serverless framework can be seen in a simplified form in Listing 2.

service: jugda-registration
 
provider:
  name: aws
  runtime: java8
  stage: prod
  region: eu-central-1
  memorySize: 2048
 
package:
  individually: true
 
functions:
  admin:
    handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler
    events:
      - http:
        path: /admin/{proxy+}
        method: any
        authorizer:
          name: basicAuthorizr
          type: token
          identitySource: method.request.header.Authorization
    package:
      artifact: target/jugda-registration-runner.jar
  public:
    handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler
    events:
      - http:
        path: /{proxy+}
        method: any
    package:
      artifact: target/jugda-registration-runner.jar
  basicAuthorizr:
    handler: js/basicAuthorizr.handler
    runtime: nodejs12.x
    memorySize: 128

Going Native

The application now runs in AWS Lambda under the Java 8 runtime, I can also start it locally and thus comfortably develop, debug, and test it. The only thing missing is the ability to compile, deploy and run the Quarkus application natively via GraalVM. After all, I want to benefit from better (startup) performance while using less memory. According to the documentation, this should work quite easily with $ mvn package -Pnative. Provided the appropriate Maven profile is in the pom.xm., it will reference all necessary settings from the Quarkus pom.xml, which are already pre-configured for the developer. This is very comfortable and saves me a deep and long familiarization with the world of GraalVM configuration for starters.

However, a first attempt to compile unfortunately produced a whole handful of error messages that sound very strange and incomprehensible for a Java developer. I’m no longer in the Java world with the usual exceptions and the like, but in the native world where the error messages look different.

After some stacktrace investigation and research, I was able to trace all error messages back to the AWS libraries used, much to my amazement. The rest of the code was apparently free of compile errors (which would still not prevent runtime errors). In the application, I use AWS APIs for DynamoDB and SES. Fortunately, there is already a Quarkus extension [17] for DynamoDB, so hopefully I can just use that and do not have to dive into the depths of implementation and customization on Quarkus. That’s the plan, anyway. Customizing third-party libraries can be necessary for efficient use in Quarkus, but can also be time-consuming at first if you’ve never done it before. The Quarkus DynamoDB extension uses version 2 of the AWS Java API, which is a rewrite of the V1 APIs but decouples the code better and has become more modular to use. In my application, I am (was) still using the V1 API, which is not a big deal because it is still supported and also further developed by AWS. However, since the AWS V2 API is still quite young, it cannot yet keep up with the functionality of the V1 API. For DynamoDB, for example, the Document API and the Object Mapper API are not yet implemented in V2. Especially the Object Mapper API (similar to the JPA annotations for POJOs and other high-level functions) I used in the application. With that, the first thing I had to do was rewrite the entire DynamoDB implementation in the application. Again, something not that complicated, but that mainly takes time, especially when the new API is not so familiar.

In addition to DynamoDB, there is also the dependency on the Simple Email Service in the application. Again, I used V1 of the AWS Java API. However, there is no Quarkus extension for SES, at least so far. Being able to use the two different AWS Java API versions in a project at the same time is feasible, but increases the further transitive dependencies; also because the V1 API has a hard dependency on the Apache HttpClient, but this is solved in a modular way with the V2 API and can be replaced, for example, by a client based on the java.net.URLConnection class included in the JDK. The advantage of the URLConnection-based client is that it starts faster than the Apache HttpClient, but it offers less throughput. In my use case, I don’t have to deal with high throughput rates, so I was able with this better performance to reduce dependencies further. As such, I rewrote the SES implementation to the V2 API as well. A new native compile worked after that and the application was ready for deployment.

Well, nearly. I compiled the native image on my MacBook, which means it runs on macOS, but not on Linux, and thus not in AWS Lambda. Native really does mean native. Fortunately, Quarkus already offers support for this, which is purely configurational, provided you have Docker available in your environment, for example. With appropriate properties, Quarkus starts a Docker container during the native build, in which the native image is then built. Thus, you get a Linux-based native binary that can then also be run in AWS Lambda:

quarkus.native.container-build=true

quarkus.native.container-runtime=docker

While AWS Lambda does not have a pure Linux runtime environment, it does provide a way to create and use custom runtimes with the Custom Runtime API. One of the requirements is that the entry point to this custom runtime has the filename bootstrap. The generated artifacts of a Quarkus Lambda project created with the Quarkus archetypes kindly provide a Maven assembly configuration so that the generated native binary is renamed to bootstrap and immediately packaged into a zip file that can be uploaded to AWS Lambda. Unlike a predefined runtime, however, there is no need to specify a defined handler for a function during deployment; the bootstrap artifact takes care of that.

Deploy the native binary and call the application. It runs! Almost. The application cannot access the AWS APIs via HTTPS/TLS yet because the native image does not know how to handle HTTPS and does not know about certificates. For this, the runtime needs the Sun-EC library from a Linux JDK distribution (libsunec.so) so that the Sun-EC provider can be loaded correctly, and the cacerts file with the certificates from a JDK, possibly provided with its own self-signed certificates. These two files must be deployed with and made known to the environment. In the native image these cannot be recompiled, they must be indicated as external files by the configuration file. The Quarkus documentation describes how this can be done for a Docker environment, but doesn’t say a word about what becomes necessary in an AWS Lambda environment.

Lambda Layers provides a remedy for this. A layer is a zip file that is uploaded separately to Lambda, provides static (binary) files, and can be used by one or more Lambda functions. For example, the runtime libraries for a Groovy application could be deployed as a layer. A Lambda function references this layer and can include the libraries, but does not have to deploy them itself. So in my case, I deploy the two files mentioned above as a layer graalvm and reference them when initializing the native function. I am not allowed to call this function bootstrap anymore, but I have to write my own bootstrap wrapper that makes the layer files known to my native function via system property parameters and calls it (Listing 3).

#!/usr/bin/env bash
RUNNER=$( find . -maxdepth 1 -name '*-runner' )
export DISABLE_SIGNAL_HANDLERS=true
$RUNNER
-Djavax.net.ssl.trustStore=/opt/graalvm/jre/lib/security/cacerts
-Djavax.net.ssl.trustAnchors=/opt/graalvm/jre/lib/security/cacerts
-Djava.library.path=/opt/graalvm/jre/lib/amd64

The bootstrap wrapper and the native image are packed together in the zip file to be deployed. The deployment configuration for the serverless framework for the native image including the GraalVMSecurity layer can be seen in Listing 4. Under the path specified for the layer, the files are located in the required directories and are zipped as they are and uploaded as a layer to Lambda.

service: jugda-registration
 
provider:
  name: aws
  runtime: provided
  stage: prod
  region: eu-central-1
  memorySize: 256
 
package:
  individually: true
 
functions:
  admin:
    handler: not.used.in.provided.runtime
    events:
      - http:
        path: /admin/{proxy+}
        method: any
        authorizer:
          name: basicAuthorizr
          type: token
          identitySource: method.request.header.Authorization
    package:
      artifact: target/function-admin.zip
    layers:
      - { Ref: GraalvmSecurityLambdaLayer }
  public:
    handler: not.used.in.provided.runtime
    events:
      - http:
        path: /{proxy+}
        method: any
    package:
      artifact: target/function-public.zip
    layers:
      - { Ref: GraalvmSecurityLambdaLayer }
  basicAuthorizr:
    handler: js/basicAuthorizr.handler
    runtime: nodejs12.x
    memorySize: 128
 
layers:
  GraalvmSecurity:
    path: lambda-layer

STAY TUNED!

Learn more about Serverless Architecture Conference

Is the application running now? Better, but not quite there yet.

The registration form appears and I can also submit it. The email is sent, but then the application runs into an error. A log analysis quickly shows that a class cannot be called via reflection when sending the email. Reflection? Yes, the error occurs when trying to map the API response to an instance of the XMLInputFactoryImpl class. This class is not known to the native image because it could not be detected by static code analysis. At there are several tips and hints as to what to do in such and similar cases. In my case it was sufficient to define this single class in a file named reflection-config.json

[{

  “name” : “com.sun.xml.internal.stream.XMLInputFactoryImpl”,

  …

}]

and specify it in the application.properties as an additional build parameter:

quarkus.native.additional-build-args=-H:ReflectionConfigurationFiles=reflection-config.json

That’s it. The application runs performant, stable and with about one-eighth of the originally provisioned memory. The current state of the application can be found in the public GitHub repository.

Conclusion

So, back to the question posed at the beginning: Is it possible to connect and operate various frameworks, platforms and technologies – some of which are still very young – to create an executable application in the real world? The answer is “yes”. However, I don’t want to hide the fact that some of the “little problems” I came up against cost me a few gray hairs, and my neighbors must have heard me swear more often than usual. More than once during the migration I was tempted to just give up on the project.

Admittedly, Quarkus is still a very young project – at the time of writing it is not even a year old. On the one hand it can do a lot, but on the other hand, it is still missing some important things. It is certainly not the all-singing and all-dancing solution to all of life’s problems. Quarkus is a framework with its own approach for certain scenarios. It scores points with regards to a containerized world and is thus made for the present. However, since this state will not last too long, in my eyes, and the infrastructure will move more and more in the direction of serverless workloads, Quarkus cannot keep up yet. The “Quarkus Highlander Principle” is simply too restrictive and not suitable for managing multiple, contextually related functions. Quarkus is very “opinionated” in my eyes – and that should not be forgotten. Migrating an existing application to Quarkus simply because you can, and because Quarkus seems so “hip” makes no sense. There should be good reasons to do so. Existing alternatives in the ecosystem should always be taken into consideration and compared against the real business requirements of the project. These are what is important, not the sensitivities and desires of developers/architects or the opinions of evangelists.

The actual integration with GraalVM and the associated ability to create native images is solved as long as one deals with manageable services. However, if one uses third-party libraries that are not available as Quarkus extensions, this can be a challenge, especially if these libraries use reflection to a large extent and you want to compile the application natively with GraalVM. The configuration files that may become necessary for this can quickly make the project confusing again.

For the existing Quarkus extensions there is already extensive documentation, sometimes more extensive, sometimes less detailed. However, the framework is growing so rapidly that some of the published guides still contain outdated information. Properties and APIs that have changed in the meantime. Trust is good here, control is even better.

There are also still many companies for which containerization and startup speed is not a top-priority issue. For these, established solutions such as Spring (Boot) also do the job. Here, too, some performance can be improved through optimization. An important additional factor that must not be forgotten is the know-how of the available developers. Not every team can or wants to jump on a new technology bandwagon every two years. Effective and efficient use of existing knowledge and technologies can sometimes be more profitable than starting 0.2 seconds faster. Also, not every company is Netflix or Google, even if many think they have the same needs.

Still, the Quarkus approach is great, and it’s good to see something moving in terms of Java deployment in a container-based world. As I said, the framework is still young, and time will tell how it evolves. I only have one wish: Please come up with some better names!

The post Cloud Native Serverless Java with Quarkus and GraalVM on AWS Lambda appeared first on Serverless Architecture Conference.

]]>
Long-Running Workflows as Serverless Functions in Azure https://serverless-architecture.io/blog/long-running-workflows-as-serverless-functions-in-azure/ Tue, 15 Dec 2020 10:56:40 +0000 https://serverless-architecture.io/?p=81385 Azure Functions have many features that make your work easier. However, they are less suitable for long-running processes. This is where Durable and Entity Functions can help.

The post Long-Running Workflows as Serverless Functions in Azure appeared first on Serverless Architecture Conference.

]]>
Serverless Functions [1] are in my opinion a great extension of Microsoft Azure, which is becoming more and more popular, and not without reason. The reasons: You don’t have to worry about choosing the right number and size of servers or configuring the autoscaling. And you certainly don’t have to worry about keeping virtual machines up-to-date. APIs in the cloud come through serverless technology like the proverbial power from the socket. There, too, there is a tremendous engineering effort behind it to always provide the right amount of power at the right time. It’s the same with Serverless Functions. You package your code, hand it over to Microsoft and let it be their problem to provide the necessary infrastructure for the load at hand. According to the so-called Consumption Plan, users pay for what they actually use, and the costs even drop to zero if no one is using the cloud software [2].

The second special feature of Azure Functions is its programming model: it is event-driven. Events can be the usual HTTP requests if the API to be developed is a Web API. But there is also a large number of other events to which you can react [3]. Here are some examples:

  • A file is uploaded to the blob storage.
  • A data change is done in Cosmos DB.
  • A message comes from an IoT device.
  • A message comes from another micro service via the service bus.
  • A timer informs that a set time has been reached.

The concept of Azure Functions therefore fits perfectly if you want to build software in the form of loosely coupled microservices.

STAY TUNED!

Learn more about Serverless Architecture Conference

Why Durable Functions?

The classic Azure Functions have two characteristics that must be taken into account in the design. First, they have to do their job in a relatively short time. The default timeout is five minutes (functions with HTTP triggers even have to respond in less than four minutes), but it can be increased up to ten minutes if required [4]. Second, Serverless Azure Functions are stateless. The developer has to take care of storing state himself, for example in other Azure PaaS or serverless services like Azure SQL Database or Cosmos DB.

Due to these two limitations, Azure Functions are not well suited for long-running processes. Imagine your Serverless Function is supposed to communicate with a user via a slack bot during execution. It is not predictable how fast the user will react. It can take minutes or even hours. A function would most likely run into a timeout.

In such situations, Durable Functions and Entity Functions help. They are designed to run for a long time and take care of the state management itself. We will now focus on these variants of Azure Functions and assume that you as a reader have basic knowledge of the classic Azure Functions. If you lack this experience, I recommend that you work through a corresponding tutorial, as you can find it for example under [5].

Programming with Durable Functions

With Durable Functions, you implement long-running workflows. In contrast to other workflow tools, however, no declarative language (e.g. domain-specific language (DSL), XML, JSON) is used for this purpose, but a rather normal C# (Azure Functions also supports other programming languages, but here we limit ourselves to C#). From the code structure, the flow of the workflow can be clearly seen. The individual activities of the workflow, which could possibly take longer, are hidden behind await calls.

However, this alone does not yet make it possible to program long-running workflows in C#. Azure Functions are serverless. The server landscape on which your C# code runs can therefore change constantly. Servers are added or the server on which a workflow instance is currently running is dropped. How do Durable Functions handle this? The answer to this question seems absurd at first glance: Durable Functions are always executed from the beginning.

For this reason, the individual activities within the workflow must be deterministic. This means that they must deliver the same result every time a workflow instance is run with the same input parameters. In accordance with the event sourcing pattern, the Durable Functions Runtime automatically stores each action of a workflow instance and its result in Azure Storage. If the function of a workflow instance starts from the beginning later, the system checks whether the action has already been executed in an earlier run of the instance before calling the respective action. If so, the action is not executed again, but its previously determined result is read and returned. Thus, it does no harm to always start over again from the beginning. Actions that have already been executed are virtually skipped.

Many C# functions are not inherently deterministic. Think of the current time, the generation of a new GUID, external web APIs, random numbers, etc. Such APIs must not be used in Durable Functions. Your API offers alternatives with similar functionality that are compatible with the Durable Functions Runtime [6].

Relaxed waiting

Listing 1 contains an example of a Durable Function implementing the Human Interaction Application Pattern [7]. Figure 1 shows the sequence as a sequence diagram.



Fig. 1: Sequence diagram for Listing 1

In our scenario, a traffic surveillance camera sends speed violations to a normal Azure Function (SpeedViolationRecognition) via an HTTP web API. This function checks the accuracy with which the vehicle license plate is recognized. If this is not high enough, we need the help of a person who looks at the captured image and checks the recognized license plate. The interaction with this person could be done via a messaging system like Slack (only hinted at in the example code of this article). Since it is not foreseeable how quickly the person will react, the interaction logic is in a Durable Function (ManuallyApproveRecognition). It sends the request to Slack after a manual license plate check and waits for Slack to return the response via a Web API function (ProcessSlackApproval). Via an event (ReceiveApprovalResponseEvent) the Durable Function is informed about the arrival of the response and the speed violation can be processed (StoreSpeedViolation).

When looking through the code, pay particular attention to the Orchestration ID, which is used in various places. It clearly identifies the workflow instance. It is used to filter in the Event Sourcing tables in Azure Storage. Figure 2 shows the relationship between the HTTP Web API, the Orchestration ID and the tables in Azure Storage.



Fig. 2: Event source table in Azure Storage
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;

namespace DurableFunctions
{
  # region Data Transfer Objects
  /// <summary>
  /// Represents a speed violation recognized by a traffic camera
  /// </summary>
  public class SpeedViolation
  {
    /// <summary>
    /// ID of the camera that has recognized the vehicle
    /// </summary>
    public int CameraID { get; set; }

    /// <summary>
    /// License plate number as recognized by the camera
    /// </summary>
    public string LicensePlateNumber { get; set; }
 
    /// <summary>
    /// Accuracy of license plate recognition (value between 0 and 1)
    /// </summary>
    public double RecognitionAccuracy { get; set; }

    /// <summary>
    /// Measured speed of the vehicle
    /// </summary>
    public decimal SpeedKmh { get; set; }
  }

  /// <summary>
  /// Represents a request for manual approval of license plate read
  /// </summary>
  public class ApprovalRequest
  {
    /// <summary>
    /// ID or the long-running orchestration handling the approval process
    /// </summary>
    public string OrchestrationInstanceID { get; set; }
 
 
    /// <summary>
    /// Data about the speed violation to approve
    /// </summary>
    public SpeedViolation SpeedViolation { get; set; }
  }

  /// <summary>
  /// Represents a response of a user concerning a license plate read
  /// </summary>
  public class ApprovalResponse
  {
    /// <summary>
    /// ID or the long-running orchestration handling the approval process
    /// </summary>
    public string OrchestrationInstanceID { get; set; }
 
    /// <summary>
    /// True if license plate read has been confirmed, otherwise false
    /// </summary>
    public bool Approved { get; set; }
  }
  # endregion
 
  public class TrafficSpeedViolation
  {
    /// <summary>
    /// Web API handling incoming speed violations recognized by traffic cameras
    /// </summary>
    /// <returns>
    /// OK if license plate read accuracy was ok, otherwise tracking data for
    /// long-running orchestration handling manual approval of license plate read.
    /// </returns>
    [FunctionName(nameof(SpeedViolationRecognition))]
    public async Task<HttpResponseMessage> SpeedViolationRecognition(
      [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequestMessage req,
      [DurableClient] IDurableOrchestrationClient starter,
      ILogger log)
    {
      // Get speed violation data from HTTP body
      var sv = JsonSerializer.Deserialize<SpeedViolation>(await req.Content.ReadAsStringAsync());
 
      // Check if read accuracy was not good enough
      if (sv.RecognitionAccuracy < 0.75d)
      {
        log.LogInformation($"Recognition not accurate enough, starting orchestration to ask human for help");
 
        // Start durable function for manual approval process
        string instanceId = await starter.StartNewAsync(nameof(ManuallyApproveRecognition), sv);
 
        // Return status object with instance ID and URLs for status monitoring
        return starter.CreateCheckStatusResponse(req, instanceId);
      }
 
      // Read accuracy was ok, than store it (assumption: storing speed
      // violation is pretty fast, i. e. a matter of seconds).
      await StoreSpeedViolation(sv, log);
      return new HttpResponseMessage(HttpStatusCode.OK);
    }
 
    private const string ReceiveApprovalResponseEvent = "ReceiveApprovalResponse";
 
    /// <summary>
    /// Web API receiving responses from Slack API
    /// </summary>
    /// <returns>
    /// OK if approval was ok, BadRequest if approval is unknown or no longer running
    /// </returns>
    [FunctionName(nameof(ProcessSlackApproval))]
    public async Task<HttpResponseMessage> ProcessSlackApproval(
      [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequestMessage req,
      [DurableClient] IDurableOrchestrationClient orchestrationClient,
      ILogger log)
    {
      // Get approval response from HTTP body
      var slackResponse = JsonSerializer.Deserialize<ApprovalResponse>(await req.Content.ReadAsStringAsync());
 
      // Get status based on orchestration Id
      var status = await orchestrationClient.GetStatusAsync(slackResponse.OrchestrationInstanceID);
      if (status.RuntimeStatus == OrchestrationRuntimeStatus.Running || status.RuntimeStatus == OrchestrationRuntimeStatus.Pending)
      {
        log.LogInformation("Received Slack response in time, raising event");
 
                // Raise an event for the given orchestration
                await orchestrationClient.RaiseEventAsync(slackResponse.OrchestrationInstanceID,
                    ReceiveApprovalResponseEvent, slackResponse.Approved);
                return new HttpResponseMessage(HttpStatusCode.OK);
            }
 
            return new HttpResponseMessage(HttpStatusCode.BadRequest);
        }
 
        /// <summary>
        /// Durable function handling long-running approval process
        /// </summary>
        [FunctionName(nameof(ManuallyApproveRecognition))]
        public async Task<bool> ManuallyApproveRecognition([OrchestrationTrigger] DurableOrchestrationContext context)
        {
          // Get speed violation data from orchestration context
          var sv = context.GetInput<SpeedViolation>();
 
          // Call activity that sends approval request to Slack. Note that this
          // activity will not await the human's response. It will only wait until
          // message will have been sent to Slack.
          await context.CallActivityAsync(nameof(SendApprovalRequestViaSlack), new ApprovalRequest
          {
            OrchestrationInstanceID = context.InstanceId,
            SpeedViolation = sv
          });
 
          // We want the human operator to respond within 60 minutes. We setup a
          // timer for that. Note that this is NOT a regular .NET timer. It is a
          // special timer from the Durable Functions runtime!
          using var timeoutCts = new CancellationTokenSource();
          var expiration = context.CurrentUtcDateTime.AddMinutes(60);
          var timeoutTask = context.CreateTimer(expiration, timeoutCts.Token);
 
          // Wait for the event that will be raised once we have received the           // response from Slack.
          var approvalResponse = context.WaitForExternalEvent<bool>(ReceiveApprovalResponseEvent);
 
          // Wait for Slack response or timer, whichever comes first
          var winner = await Task.WhenAny(approvalResponse, timeoutTask);
 
          // Was the Slack task the first task to complete?
          if (winner == approvalResponse && approvalResponse.Result)
          {
            // License plate read approved -> Store speed violation
            await context.CallActivityAsync(nameof(StoreSpeedViolation), sv);
          }
 
          if (!timeoutTask.IsCompleted)
          {
            // All pending timers must be completed or cancelled before the             // function exits.
            timeoutCts.Cancel();
          }
 
          return winner == approvalResponse && approvalResponse.Result;
    }
 
    [FunctionName(nameof(SendApprovalRequestViaSlack))]
    public Task SendApprovalRequestViaSlack([ActivityTrigger] ApprovalRequest req, ILogger log)
    {
      log.LogInformation($"Message regarding {req.SpeedViolation.LicensePlateNumber} sent to Slack " +
        $"(instance ID {req.OrchestrationInstanceID}!");
 
      // Todo: Send data about speed violation to Slack via Slack REST API.
      // Not implemented here, just a demo.
 
      return Task.CompletedTask;
    }
 
    [FunctionName(nameof(StoreSpeedViolation))]
    public Task StoreSpeedViolation([ActivityTrigger] SpeedViolation sv, ILogger log)
    {
      log.LogInformation($"Processing speed violation from camera {sv.CameraID}" +
        $"for LP {sv.LicensePlateNumber} ({sv.SpeedKmh} km/h)");
 
      // Todo: Add code for processing speed violation
      // Not implemented here, just a demo.
 
      return Task.CompletedTask;
    }
  }
}

Entity Functions

Just a few months ago, Azure Durable Functions was extended by a new type of function: Entity Functions. While the state of Durable Functions (aka Orchestrator Functions) is implicitly determined by the control flow and the local variables in C# code, Entity Functions explicitly store it.

The principle can be explained most easily with an example. Listing 2 contains a Durable Entity, which represents a procedure for speed violation (SpeedViolationLawsuit). It uses the class-based syntax. Alternatively, a function-based syntax would be offered, whereby Microsoft recommends the variant with classes in C# [8]. You do not have to write code to persist instances of this class. The Durable Functions Runtime stores the state of the instances as JSON blobs in Azure Storage. The appearance of the JSON can be influenced with the usual C# attributes for JSON serialization.

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using System;
using System.Threading.Tasks;
 
namespace DurableFunctions
{
  /// <summary>
  /// Represents a speed violation lawsuit
  /// </summary>
  public class SpeedViolationLawsuit
  {
    public SpeedViolation SpeedViolation { get; set; }
 
    public string Driver { get; set; }
 
    public decimal? Fine { get; set; }
 
    public bool Paid { get; set; }
 
    public void SetSpeedViolation(SpeedViolation sv) => SpeedViolation = sv;
 
    public void StoreDriver(string driver) => Driver = driver;
 
    public async Task SetFine(decimal fine)
    {
      if (string.IsNullOrEmpty(Driver))
      {
        throw new InvalidOperationException();
      }
 
      // Simulate an async operation (e. g. for I/O)
      await Task.Delay(1);
 
      Fine = fine;
    }
 
    public void MarkAsPaid()
    {
      if (!Fine.HasValue)
      {
        throw new InvalidOperationException();
      }
 
      Paid = true;
    }
 
    public void Delete()
    {
      // Note how we access the current entity
      Entity.Current.DeleteState();
    }
 
    [FunctionName(nameof(SpeedViolationLawsuit))]
    public static Task Run([EntityTrigger] IDurableEntityContext ctx)
    {
      // When creating a new entity, make sure it is marked as not paid
      if (!ctx.HasState)
      {
        ctx.SetState(new SpeedViolationLawsuit
        {
          Paid = false
        });
      }
 
      return ctx.DispatchAsync<SpeedViolationLawsuit>();
    }
  }
}

Listing 3 shows how to deal with the Durable Entities from within normal and Durable Functions. In ManuallyApproveRecognition you can see how a new entity is created. In our scenario this would be the case, for example, if the user has confirmed the license plate recognition in the Durable Function. GetLawsuite shows how to read the state of the Durable Entity using its ID (in our case a GUID). Finally, SetDriver is a simplified example for calling a method that changes the state of an entity. I recommend everyone experimenting with Durable Entities to have a look into Azure Storage, as shown in figure 2, to get a deeper understanding of how Azure Functions actually store the state.

public async Task<bool> ManuallyApproveRecognition(
  [OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
{
  // ...
 
  // Create a new instance of a speed violation lawsuit
  var entityId = Guid.NewGuid();
  var lawsuitId = new EntityId(nameof(SpeedViolationLawsuit), entityId.ToString());
 
  // Store data of speed violation in new entity
  await context.CallEntityAsync(lawsuitId, nameof(SpeedViolationLawsuit.SetSpeedViolation), sv);
 
  // ...
}
 
[FunctionName(nameof(GetLawsuite))]
public async Task<IActionResult> GetLawsuite(
  [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "lawsuite/{entityKey}")] HttpRequestMessage req,
  string entityKey,
  [DurableClient] IDurableEntityClient client,
  ILogger log)
{
  // Get state of speed violation lawsuit using its Id (GUID)
  var svl = await client.ReadEntityStateAsync<SpeedViolationLawsuit>(
    new EntityId(nameof(SpeedViolationLawsuit), entityKey));
 
  // Return current state of lawsuit
  return new OkObjectResult(svl);
}
 
[FunctionName(nameof(SetDriver))]
public async Task<IActionResult> SetDriver(
  [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "lawsuite/{entityKey}/setDriver")] HttpRequestMessage req,
  string entityKey,
  [DurableClient] IDurableEntityClient client,
  ILogger log)
{
  var driverName = JsonSerializer.Deserialize<string>(await req.Content.ReadAsStringAsync());
 
  // Send one-way message to lawsuit entity (not processing result)
  await client.SignalEntityAsync(
    new EntityId(nameof(SpeedViolationLawsuit), entityKey),nameof(SpeedViolationLawsuit.StoreDriver), 
    driverName);
 
  return new StatusCodeResult((int)HttpStatusCode.Accepted);
}

STAY TUNED!

Learn more about Serverless Architecture Conference

Conclusion

Durable Functions have significantly expanded the scope of Azure Functions. The previous restrictions on execution times no longer apply. The task-based programming model and async/await are well suited for mapping workflows. The C# code is easy to read and reflects the content of the workflows well. Even complex use cases such as parallel execution of several sub-processes (aka fan out/fan in) or the integration of people in long-running workflows are no problem.

Until a few months ago, however, it was difficult to implement the aggregator pattern with Durable Functions. Data concerning a logical, addressable entity comes from different sources over a long period of time and has to be collected. The new Durable Entities are suitable for exactly such cases.

In addition to the functional enhancements of Azure Functions, Microsoft does not neglect the technical development: The current Azure Functions Runtime already allows the use of .NET Core 3.1. The operation of functions in Kubernetes is also supported [9].

It can be seen that Microsoft is pushing the development of Azure Functions at a fast pace. In my opinion, the platform is an interesting option for all those who rely on loosely coupled microservices and event-driven cloud solutions.


Links & Literature

[1] https://azure.microsoft.com/en-us/services/functions/

[2] https://azure.microsoft.com/en-us/pricing/details/functions/

[3] https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings#supported-bindings

[4] https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale#timeout

[5] https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-your-first-function-visual-studio

[6] https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-code-constraints#using-deterministic-apis

[7] https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=csharp#human

[8] https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-entities?tabs=csharp#define-entities

[9] https://docs.microsoft.com/en-us/azure/azure-functions/functions-kubernetes-keda

The post Long-Running Workflows as Serverless Functions in Azure appeared first on Serverless Architecture Conference.

]]>
Serverless Java: Reduce Infrastructure Overhead https://serverless-architecture.io/blog/serverless-java-reduce-infrastructure-overhead/ Mon, 14 Sep 2020 10:16:35 +0000 https://serverless-architecture.io/?p=23366 Java is still the first choice when it comes to software development for business use [1]. However, the development of Java software alone is not enough: machines, operating systems, JREs, application servers, etc. are required for productive use - and large frameworks and libraries are also required as the basis for code functionality. This overhead hurts more the simpler the required functionality is, because it makes development, testing, and operation more difficult. The alternative concept: Serverless.

The post Serverless Java: Reduce Infrastructure Overhead appeared first on Serverless Architecture Conference.

]]>
In this alternative concept, software is deployed as a callable function in a suitable environment instead of as a standalone executable binary. Instead of an application server and all infrastructure below, it takes care of the connection to the outside world. Up until now, these functions are written in JavaScript, Python and Go, among other things because of long startup times and high memory requirements of JVM-based applications.

You can also benefit from existing Java experience and code, as well as from reduced deployment and operation effort: Improvements of the JVM, new lean web frameworks like Quarkus and Micronaut, and techniques like native image compilation with GraalVM make Java interesting again for serverless applications. This article presents some approaches to using Java in serverless environments.

Application Scenarios

In general, a serverless approach is well suited for tasks that fit the following usage profiles:

  • Asynchronous, easily parallelized into independent execution units
  • Irregular or sporadic demand with a large, unpredictable variance in usage
  • Stateless, short-lived, tolerates high latency
  • Rapidly changing business requirements with the need for high development speed

Some concrete examples are:

  • File processing after upload
  • WebHooks to connect logic to HTTP calls
  • Background jobs – time-controlled processing
  • Simple backend for web frontends

Basics

This article wants to illuminate two views on Serverless: Development and operation. For the developer this means implementing the application logic in the form of simple functions. For the admin, it is a simple way to deploy and operate software, namely in appropriate local or cloud environments that take care of the business-critical stuff.

Once deployed, a function can then be called via HTTP, messaging or other events, for example. An example is the generation of a preview for uploaded photos: Once a user has uploaded a new image, this event triggers the execution of a function for creating a preview. The function itself can trigger other functions.

How a serverless platform works internally is ultimately irrelevant to the developer as long as it only takes away boilerplate code, e.g. the HTTP layer. Once they have uploaded their software, the platform takes care of the operation and scaling of the function. Cloud-based offerings charge only for actual resource usage, such as CPU time, memory, or number of calls. In addition to efficient and economical operation, the focus is on developer productivity. A good summary on this topic is provided in the Serverless Whitepaper of the CNCF Serverless Working Group [2].

When a request is received to operate the software, it is first started in an execution environment and an end point is set up that calls the function, passes arguments and returns the result. The latency time from the request to the response is decisive for the performance, which the developer can influence, on the one hand, by choosing the platform and, on the other hand, by using suitable runtime environments and program optimizations (Fig. 1). The platform can then destroy the environment or, if necessary, reuse it. Platforms usually limit the maximum execution time of a function, for example to 15 minutes, after which the execution is aborted.

Functions must be stateless, because the runtime environment can be terminated or restarted at any time. Multiple environments are also possible, so that the same function can run several times in parallel. Within an environment, for example, static fields can be used as caches so that time-consuming initializations can be amortized over several calls as long as the environment exists.

Fig. 1: Ideal-typical life cycle of request processing of a serverless function

Fig. 1: Ideal-typical life cycle of request processing of a serverless function

Up to now, Java has not played a major role in the serverless environment, since the start of a JVM in cloud environments alone can take several seconds, which adds to the latency. This is particularly unfavorable if the actual function execution only takes a few milliseconds and is frequently requested. Furthermore, the libraries and frameworks used may extend the startup time with their initialization. In the meantime, however, it is now possible to restrict oneself to dependencies that are required for execution, while there are also alternatives to virtually eliminate the JVM start-up latency.

In addition, serverless platforms can minimize deployment latency by reusing execution environments for multiple requests. This way, the same execution environment handles requests that occur in quick succession. This eliminates the need for re-provisioning.

Platforms

Serverless platforms are also referred to as FaaS (Function as a Service) in the cloud nomenclature and thus, move the execution of functions to the center of attention (Fig. 2), analogous to virtual machines in IaaS and complete applications in PaaS. BaaS (Backend as a Service), i.e. infrastructure services such as databases, messaging systems, and storage, are not in the picture because no separate application code is usually introduced.

Fig. 2: Classification of FaaS in the context of cloud models [3]

Fig. 2: Classification of FaaS in the context of cloud models [3]

Serverless platforms offer the following features, among others: no installation or maintenance effort for infrastructure, flexible scalability, consumption-dependent costs (i.e. no usage = no costs), event-driven, self-scaling within given limits, easy integration with other services of the platform.

The major FaaS cloud providers with native Java support are currently Azure Functions, AWS Lambda and IBM Cloud Functions. Google Cloud Functions does not currently provide direct support for Java-based features.

In addition to these cloud services, there are a number of platforms that can be run locally, including Riff, Fn Project, OpenWhisk, and OpenFaaS.

Programming models

A simple Java class is often sufficient to define a function. The metadata required for provision is specified declaratively using annotations or external configuration files. However, some FaaS services, such as Azure Functions, require the use of special APIs, which also allow platform specifics to be used. If you want to use logic across multiple FaaS services, the Spring Cloud Functions project [4] is a good place to start. This allows you to define function logic that is independent of the FaaS vendor, yet can be called in different FaaS environments. In addition, it is also possible to containerize Java apps with HTTP endpoints and run them on serverless container platforms like Google Cloud Run or AWS Fargate.

Fn Project

For the first steps in the serverless environment, the Fn Project [5] is well suited. It is a serverless platform written in Go, that runs on your own computer and supports Java as a runtime environment in addition to Go and JavaScript. Fn provides a server component that receives the client requests and uses dynamically started docker containers for function execution. In addition, there is a UI component [6], which provides further information about the applications and functions provided. A function in Fn lives in the context of a logical application.

A brief guide to setting up the Fn Tooling is provided in the Quick Start on the project’s website [7]. Using the fn command line tool, we will create a new Maven project called hello-fn:

fn init --runtime java hello-fn

This project contains a sample function in HelloFunction.java (Listing 1) and the configuration file func.yaml (Listing 2) to run the function in the Fn-Server.

The project and the class HelloFunction are simple and use no further dependencies. The logic is mapped in the method String handleRequest(String).

Listing 1: Example function in HelloFunction.java

package demo;
 
public class HelloFunction {
 
  public String handleRequest(String input) {
    String name = (input == null || input.isEmpty()) ? "world" : input;
    return "Hello, " + name;
  }
}

The most important information in func.yaml includes name, runtime, and cmd. The name attribute specifies the logical name of the exposed function. With runtime: java, we instruct the Fn server to execute our function in a Java runtime environment. Using the attribute cmd, the Fn runtime knows that a new instance of the class HelloFunction is created and that the handleRequest method must be called by reflection.

Listing 2: func.yaml

schema_version: 20180708
name: hello-fn
version: 0.0.1
runtime: java
cmd: demo.HelloFunction::handleRequest

The logical application containing the function is created with the command fn create app:

fn create app hello-fn-app

The function is then made available via the Fn server using the fn deploy command:

fn deploy --app hello-fn-app --local

The function can be called with the command fn invoke:

fn invoke hello-fn-app hello-fn

The Fn User Interface can be hosted as a docker container:

docker run --rm -it --link fnserver:api -p 4000:4000 \
  --name ui -e "FN_API_URL=http://api:8080" fnproject/ui

At http://localhost:4000 you will find an overview of the applications and functions provided, which can also be tested directly via the interface.

Azure Functions

Azure Functions is a FaaS service that also supports Java. For this purpose, a corresponding Java class must be created with the Azure Functions Java SDK. A good guide for creating a Java-based Azure Function is provided [8].

Listing 3 shows a sample function using annotations from the Azure Functions Java SDK. The external name of the function is defined using @FunctionName. The @HttpTrigger defines how the function can be called via HTTP. HttpRequestMessage encapsulates information about the request, and information about the execution environment, such as configuration and logging, can be accessed via the ExecutionContext.

Listing 3: Example function using annotations from the Azure Functions Java SDK

public class Fun {
 
  @FunctionName("greet")
  public HttpResponseMessage<String> httpHandler(
    @HttpTrigger(name = "req", methods = "post",
                 authLevel = AuthorizationLevel.ANONYMOUS)
    HttpRequestMessage<optional<String>> request, 
    ExecutionContext context) {
 
    context.getLogger().info("processed a request.");
 
    String name = request.getBody().orElse(null);
 
    if (name == null)
      return request.createResponse(400, "Missing name");
 
    return request.createResponse(200, "Hello, " + name);
  }
}

The example uses the Azure Functions Maven plugin and requires installation of the azfunc tool from the Azure Function SDK [9]. The project can be built as a Maven project and tested locally. To do this, you can start the function locally via mvn azure-functions:run and call the local endpoint via curl -d ‘{“name”: “World”}’ http://localhost:7071/api/greet.

Spring Cloud Function

Spring Cloud Function provides support for serverless development with all of Spring’s capabilities such as dependency injection, integrations, auto-configuration in a unified programming model across multiple serverless providers. Currently, Spring Cloud Function provides support for AWS Lambda, Microsoft Azure, Apache OpenWhisk SDKs, Project Riff and Fn Project. The framework also provides special adapter implementations for integration with FaaS providers that abstract the provider-specific APIs. Functions can either be declared as Spring Beans or discovered automatically via component scanning. The following is a bean function definition:

@Bean // function is exposed with name "greet"
public Function<User, Greeting> greet() {
  return user -> new Greeting(String.format("Hello, %s", user.getName()));
}

Listing 4 shows an example of Spring adaptation of Azure Function specific APIs. By calling the handleRequest(…) method, the actual function call is triggered at the Bean function named greet.

Listing 4: GreetingHandler for mapping to Azure Functions

public class GreetingHandler extends AzureSpringBootRequestHandler<User, Greeting> {
 
  @FunctionName("greet")
  public Greeting execute(
    @HttpTrigger(name = "request", methods = HttpMethod.POST, 
      authLevel = AuthorizationLevel.ANONYMOUS)
    HttpRequestMessage<optional<User>> request, ExecutionContext context) {
 
    String name = request.getBody().map(User::getName).orElse("unknown");
    context.getLogger().info(String.format("Invoking greeting =: %s", name));
 
    // this invokes the greet function
    return handleRequest(request.getBody().get(), context);
  }
}

To provide the function on Azure, you have to login to Azure in the console using az login. After that you call the maven goal mvn azure-functions:deploy. Afterwards, the app is available under the generated URL.

Quarkus with GraalVM

Quarkus enables the development of lean Java microservices [10]. In combination with GraalVM and its native image extension, it is even possible to create an executable native binary from a classic JAX-RS application [11]. This binary contains the pre-compiled application as well as GraalVM’s SubstrateVM as runtime environment. SubstrateVM not only requires less resources than classic JVMs, but also starts much faster – often in a few milliseconds. This makes the combination of Quarkus and GraalVM Native Image ideal for use in serverless container platforms like Google Cloud Run [12]. The platform offers pay per use, flexible scaling, and the ability to completely remove containers that are no longer needed [13].

Tips

A few tips for using Java in serverless environments:

  • Minimize start-up times
  • Pay attention to efficient processing
  • Use batch processing of messages
  • Use minimal dependencies

Conclusion

Suitable use cases can benefit enormously from the serverless paradigm: If the task is manageable and can be easily parallelized, the infrastructure overhead is practically reduced to zero. Where previously only JavaScript, Python, and Go were possible, fast starting application frameworks such as Quarkus and Micronaut (especially in combination with GraalVM Native) have recently put Java on the short list for serverless implementation [14].

 


Links & Literature

[1] https://www.tiobe.com/tiobe-index/java/

[2] https://github.com/cncf/wg-serverless/tree/master/whitepapers/serverless-overview

[3] https://serverless.zone/abstracting-the-back-end-with-faas-e5e80e837362

[4] https://spring.io/projects/spring-cloud-function

[5] https://fnproject.io/

[6] https://github.com/fnproject/ui

[7] https://github.com/fnproject/fn#quickstart

[8] https://code.visualstudio.com/docs/java/java-azurefunctions

[9] https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local

[10] https://quarkus.io/guides/getting-started

[11] https://quarkus.io/guides/building-native-image

[12] https://github.com/cncf/wg-serverless/tree/master/whitepapers/serverless-overview

[13] https://cloud.google.com/run/

[14] https://github.com/thomasdarimont/serverless-javamagazin-article

The post Serverless Java: Reduce Infrastructure Overhead appeared first on Serverless Architecture Conference.

]]>