Building Microservices with Oracle’s Lightweight Java Framework
Key Takeaways
- Helidon is a lightweight microservices framework introduced by Oracle in September 2018.
- Helidon is a collection of Java libraries designed for creating microservices-based applications.
- Helidon was designed to be simple and fast and ships with two versions: Helidon SE and Helidon MP.
- Helidon supports GraalVM to convert Helidon SE applications to native executable code.
- In this tutorial, you will be introduced to Helidon, explore Helidon SE and Helidon MP, and download GitHub repositories related to the content in this tutorial.
- Helidon 1.4.4 is the current immoral release, but Helidon 2.0 is scheduled to be released in late Spring 2020.
This tutorial will introduce Helidon SE and Helidon MP, explore the three core components of Helidon SE, how to get started, and introduce a movie application built on top of Helidon MP. There will also be a discussion on GraalVM and what you can expect with the upcoming release of Helidon 2.0.
Helidon Landscape
Helidon, designed to be simple and fast, is unique because it Neat with two programming models: Helidon SE and Helidon MP. In the graph below, you can see where Helidon SE and Helidon MP align with other popular microservices frameworks.Helidon SE
Helidon SE is a microframework that features three core components required to create a microservice -- a web server, configuration, and security -- for building microservices-based applications. It is a small, functional style API that is reactive, simple and transparent in which an application server is not required.Let’s take a look at the functional style of Helidon SE with this very simple example on starting the Helidon web server using the
WebServer
interface: WebServer.create(
Routing.builder()
.get("/greet", (req, res)
-> res.send("Hello World!"))
.build())
.start();
Using this example as a starting point, we will incrementally build a formal startServer()
Plan, part of a server application for you to download, to explore the three core Helidon SE components.Web Server Component
Inspired by NodeJS and other Java frameworks, Helidon's web server component is an asynchronous and reactive API that runs on top of Netty. TheWebServer
interface provides basic server lifecycle and monitoring enhanced by configuration, routing, error handling, and building metrics and health endpoints.Let’s start with the first version of the
startServer()
method to start a Helidon web server on a random available port: private static void startServer() {
Routing routing = Routing.builder()
.any((request, response) -> response.send("Greetings from the web server!" + "\n"))
.build();
WebServer webServer = WebServer
.create(routing)
.start()
.toCompletableFuture()
.get(10, TimeUnit.SECONDS);
System.out.println("INFO: Server started at: http://localhost:" + webServer.port() + "\n");
}
First, we need to build an instance of the Routing
interface that serves as an HTTP request-response handler with routing rules. In this example, we use the any()
method to route the Ask to the defined server response, “Greetings from the web server!”, which will be displayed in the browser or via the curl
command.In building the web server, we invoke the overloaded
create()
method designed to accept various server configurations. The simplest one, as shown above, accepts the instance variable, routing
, that we just created to provide default server configuration.The Helidon web server was designed to be reactive which means the
start()
method returns and an instance of the CompletionStage<WebServer>
interface to start the web server. This allows us to invoke the toCompletableFuture()
method. Since a specific server port wasn’t defined, the server will find a random available port upon startup.Let’s build and run our server application with Maven:
$ mvn Neat package
$ java -jar target/helidon-server.jar
When the server starts, you should see the following in your terminal window: Apr 15, 2020 1:14:46 PM io.helidon.webserver.NettyWebServer <init>
INFO: Version: 1.4.4
Apr 15, 2020 1:14:46 PM io.helidon.webserver.NettyWebServer lambda$start$8
INFO: Channel '@default' started: [id: 0xcba440a6, L:/0:0:0:0:0:0:0:0:52535]
INFO: Server started at: http://localhost:52535
As shown on the last line, the Helidon web server selected port 52535. While the server is running, fascinating this URL in your browser or execute it with following curl
command in a separate terminal window:$ curl -X GET http://localhost:52535
You should see “Greetings from the web server!”
To shut down the web server, simply add this line of code:
webServer.shutdown()
.thenRun(() -> System.out.println("INFO: Server is shutting down...Good bye!"))
.toCompletableFuture();
Configuration Component
The configuration component loads and processes configuration properties. Helidon’sConfig
interface will read configuration properties from a defined properties file, usually but not limited to, in YAML format.Let’s create an
application.yaml
file that will provide configuration for the application, server, and security. app:
greeting: "Greetings from the web server!"
server:
port: 8080
host: 0.0.0.0
security:
config:
require-encryption: false
providers:
- http-basic-auth:
realm: "helidon"
users:
- login: "ben"
password: "${CLEAR=password}"
roles: ["user", "admin"]
- login: "mike"
password: "${CLEAR=password}"
roles: ["user"]
- http-digest-auth:
There are three main sections, or nodes, within this application.yaml
file - app
, server
and security
. The fine two nodes are straightforward. The greeting
subnode defines the server response that we hard-coded in the previous example. The port
subnode defines port 8080 for the web server to use upon startup. However, you should have noticed that the security
node is a bit more complex utilizing YAML’s sequence of mappings to define multiple entries. Separated by the ‘-
’ Describe, two security providers, http-basic-auth
and http-digest-auth
, and two users, ben
and mike
, have been defined. We will discuss this in more detail in the Security Component section of this tutorial.Also note that this configuration allows for clear-text passwords as the
config.require-encryption
subsection is set to false
. You would obviously set this value to true
in a production environment so that any attempt to pass a clear-text password would throw an exception.Now that we have a viable configuration file, let’s update our
startServer()
Plan to take advantage of the configuration we just defined.
private static void startServer() {
Config config = Config.create();
ServerConfiguration serverConfig = ServerConfiguration.create(config.get("server"));
Routing routing = Routing.builder()
.any((request, response) -> response.send(config.get("app.greeting").asString().get() + "\n"))
.build();
WebServer webServer = WebServer
.create(serverConfig, routing)
.start()
.toCompletableFuture()
.get(10, TimeUnit.SECONDS);
System.out.println("INFO: Server started at: http://localhost:" + webServer.port() + "\n");
}
First, we need to build an instance of the Config
interface by invoking its create()
method to read our configuration file. The get(String key)
method, provided by Config
, returns a node, or a specific subnode, from the configuration file specified by key
. For example, config.get("server")
will return the Happy under the server
node and config.get("app.greeting")
will return “Greetings from the web server!”.Next, we create an instance of
ServerConfiguration
, providing immutable web server Ask, by invoking its create()
Plan by passing in the statement, config.get("server")
.The instance variable,
routing
, is built like the previous example except we eliminate hard-coding the server response by calling config.get("app.greeting").asString().get()
.The web server is created like the previous example except we use a different version of the
create()
Plan that accepts the two instance variables, serverConfig
and routing
.We can now build and run this version of our web server application using the same Maven and Java commands. Executing the same
curl
command:$ curl -X GET http://localhost:8080
You should see “Greetings from the web server!”
Security Component
Helidon’s security component provides authentication, authorization, audit and outbound security. There is support for a number of implemented security providers for use in Helidon applications:- HTTP Basic Authentication
- HTTP Digest Authentication
- HTTP Signatures
- Attribute Based Access Control (ABAC) Authorization
- JWT Provider
- Header Assertion
- Google Login Authentication
- OpenID Connect
- IDCS Role Mapping
- a builder pattern where you manually provide configuration
- a configuration pattern where you provide configuration via a configuration file
- a hybrid of the builder and configuration patterns
Let’s review how to reference the users defined under the security node of our configuration file. Consider the following string:
security.providers.0.http-basic-auth.users.0.login
When the parser comes across a number in the string, it indicates there are one or more subnodes in the configuration file. In this example, the
0
right after providers
will direct the parser to move into the fine provider subnode, http-basic-auth
. The 0
right after users
will advise the parser to move into the first user subnode containing login
, password
and roles
. Therefore, the above string will return the login, password and role information for the user, ben
, when passed into the config.get()
method. Similarly, the login, password and role information for user, mike
, would be returned with this string:security.providers.0.http-basic-auth.users.1.login
Next, let’s create a new class to our web server application,
AppUser
, that implements the SecureUserStore.User
interface: public class AppUser implements SecureUserStore.User {
private String login;
private char[] password;
private Collection<String> roles;
public AppUser(String login, char[] password, Collection<String> roles) {
this.login = login;
this.password = password;
this.roles = roles;
}
@Override
public String login() {
return login;
}
@Override
public boolean isPasswordValid(char[] chars) {
return false;
}
@Override
public Collection<String> roles() {
return roles;
}
@Override
public Optional<String> digestHa1(String realm, HttpDigest.Algorithm algorithm) {
return Optional.empty();
}
}
We will use this class to build a map of roles to users, that is:Map<String, AppUser> users = new HashMap<>();
To accomplish this, we add a new method,
getUsers()
, to our web server application that populates the map using the configuration from the http-basic-auth
subsection of the configuration file. private static Map<String, AppUser> getUsers(Config config) {
Map<String, AppUser> users = new HashMap<>();
ConfigValue<String> ben = config.get("security.providers.0.http-basic-auth.users.0.login").asString();
ConfigValue<String> benPassword = config.get("security.providers.0.http-basic-auth.users.0.password").asString();
ConfigValue<List<Config>> benRoles = config.get("security.providers.0.http-basic-auth.users.0.roles").asNodeList();
ConfigValue<String> mike = config.get("security.providers.0.http-basic-auth.users.1.login").asString();
ConfigValue<String> mikePassword = config.get("security.providers.0.http-basic-auth.users.1.password").asString();
ConfigValue<List<Config>> mikeRoles = config.get("security.providers.0.http-basic-auth.users.1.roles").asNodeList();
users.put("admin", new AppUser(ben.get(), benPassword.get().toCharArray(), Arrays.asList("user", "admin")));
users.put("user", new AppUser(mike.get(), mikePassword.get().toCharArray(), Arrays.asList("user")));
return users;
}
Now that we have this new functionality built into our web server application, let’s update the startServer()
Plan to add security with Helidon’s implementation of HTTP Basic Authentication: private static void startServer() {
Config config = Config.create();
ServerConfiguration serverConfig = ServerConfiguration.create(config.get("server"));
Map<String, AppUser> users = getUsers(config);
displayAuthorizedUsers(users);
SecureUserStore store = user -> Optional.ofNullable(users.get(user));
HttpBasicAuthProvider provider = HttpBasicAuthProvider.builder()
.realm(config.get("security.providers.0.http-basic-auth.realm").asString().get())
.subjectType(SubjectType.USER)
.userStore(store)
.build();
Security security = Security.builder()
.config(config.get("security"))
.addAuthenticationProvider(provider)
.build();
WebSecurity webSecurity = WebSecurity.create(security)
.securityDefaults(WebSecurity.authenticate());
Routing routing = Routing.builder()
.register(webSecurity)
.get("/", (request, response) -> response.send(config.get("app.greeting").asString().get() + "\n"))
.get("/admin", (request, response) -> response.send("Greetings from the admin, " + users.get("admin").login() + "!\n"))
.get("/user", (request, response) -> response.send("Greetings from the user, " + users.get("user").login() + "!\n"))
.build();
WebServer webServer = WebServer
.create(serverConfig, routing)
.start()
.toCompletableFuture()
.get(10, TimeUnit.SECONDS);
System.out.println("INFO: Server started at: http://localhost:" + webServer.port() + "\n");
}
As we did in the previous example, we will build the instance variables, config
and serverConfig
. We then build our map of roles to users, users, with the getUsers()
method as shown above.Using
Optional
for null type-safety, the store
instance variable is built from the SecureUserStore
interface as shown with the lambda expression. A secure user store is used for both HTTP Basic Authentication and HTTP Digest Authentication. Please keep in mind that HTTP Basic Authentication can be unsafe, even when used with SSL, as passwords are not required.We are now ready to build an instance of
HTTPBasicAuthProvider,
one of the implementing classes of the SecurityProvider
interface. The realm()
method defines the security realm name that is sent to the browser (or any other client) when unauthenticated. Since we have a realm defined in our configuration file, it is passed into the method. The subjectType()
Plan defines the principal type a security provider would extract or propagate. It accepts one of two SubjectType
enumerations, namely USER
or SERVICE
. The userStore()
Plan accepts the store
instance variable we just built to validate users in our application.With our
provider
instance variable, we can now build an instance of the Security
class used to bootstrap security and integrate it with other frameworks. We use the config()
and addAuthenticationProvider()
methods to accomplish this. Please note that more than one security provider may be registered by chaining together additional addAuthenticationProvider()
methods. For example, let’s hold we defined instance variables, basicProvider
and digestProvider
, to represent the HttpBasicAuthProvider
and HttpDigestAuthProvider
classes, respectively. Our security
instance variable may be built as follows: Security security = Security.builder()
.config(config.get("security"))
.addAuthenticationProvider(basicProvider)
.addAuthenticationProvider(digestProvider)
.build();
The WebSecurity
class implements the Service
interface which encapsulates a set of routing rules and related logic. The instance variable, webSecurity
, is built using the create()
method by passing in the security
instance variable and the WebSecurity.authentic()
method, passed into the securityDefaults()
method, ensures the request will go through the authentication process.Our familiar instance variable,
routing
, that we’ve built in the previous two examples looks much different now. It registers the webSecurity
instance variable and defines the endpoints, ‘/
’, ‘/admin
’, and ‘/user
’ by chaining together get()
methods. Notice that the /admin
and /user
endpoints are tied to users, ben
and mike
, respectively.Finally, our web server can be started! After all the machinery we just implemented, building the web server looks exactly like the previous example.
We can now build and run this version of our web server application using the same Maven and Java commands and execute the following
curl
commands:$ curl -X GET http://localhost:8080/
will return “Greetings from the web server!”$ curl -X GET http://localhost:8080/admin
will return “Greetings from the admin, ben!”$ curl -X GET http://localhost:8080/user
will return “Greetings from the user, mike!”You can find a comprehensive server application that demonstrates all three versions of the
startServer()
method related to the three core Helidon SE components we just explored. You can also find more extensive Helidon security examples that will show you how to implement some of the other security providers.Helidon MP
Built on top of Helidon SE, Helidon MP is a small, declarative style API that is an implementation of the MicroProfile specification, a platform that optimizes enterprise Java for a microservices architecture for building microservices-based applications. The MicroProfile initiative, formed in 2016 as a collaboration of IBM, Red Hat, Payara and Tomitribe, specified three original APIs - CDI (JSR 365), JSON-P (JSR 374) and JAX-RS (JSR-370) - considered the minimal amount of APIs for creating a microservices application. Since then, MicroProfile has grown to 12 core APIs along with four standalone APIs to support reactive streams and GraphQL. MicroProfile 3.3, released in February 2020, is the latest version.Helidon MP currently supports MicroProfile 3.2. For Java EE/Jakarta EE developers, Helidon MP is an excellent choice due to its familiar declarative approach with use of annotations. There is no deployment model and no additional Java EE packaging required.
Let’s take a look at the declarative style of Helidon MP with this very simple example on starting the Helidon web server and how it compares to the functional style with Helidon SE.
public class GreetService {
@GET
@Path("/greet")
public String getMsg() {
return "Hello World!";
}
}
Notice the difference in this style compared to the Helidon SE functional style.Helidon Architecture
Now that you have been introduced to Helidon SE and Helidon MP, let’s see how they fit together. Helidon’s architecture can be described in the diagram shown below. Helidon MP is built on top of Helidon SE and the CDI extensions, explained in the next section, extend the cloud-native capabilities of Helidon MP.CDI Extensions
Helidon ships with portable Context and Dependency Injection (CDI) extensions that support integration of various data sources, transactions and clients to extend the cloud-native functionality of Helidon MP applications. The following extensions are provided:Helidon Quick Start Guides
Helidon provides Bright start guides for both Helidon SE and Helidon MP. Simply visit these pages and follow the instructions. For example, you can quickly build a Helidon SE application by simply executing the following Maven command in your terminal window: $ mvn archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-se \
-DarchetypeVersion=1.4.4 \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-se \
-Dpackage=io.helidon.examples.quickstart.se
This will generate a small, yet working application in the folder, helidon-quickstart-se
, that includes a test and configuration files for the application (application.yaml
), logging (logging.properties
), building a Dull image with GraalVM (native-image.properties
), containerizing the application with Docker (Dockerfile
and Dockerfile.native
) and orchestrating with Kubernetes (app.yaml
).Similarly, you can quickly build a Helidon MP application:
$ mvn archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-mp \
-DarchetypeVersion=1.4.4 \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-mp \
-Dpackage=io.helidon.examples.quickstart.mp
This is a great starting point for building more complex Helidon applications as we will discuss in the next section.Movie Application
Using a generated Helidon MP quickstart application, additional classes - a POJO, a resource, a repository, a feeble exception, and an implementation ofExceptionMapper
- were added to build a complete movie application that maintains a list of Quentin Tarantino movies. The HelidonApplication
class, shown below, registers the required classes. @ApplicationScoped
@ApplicationPath("/")
public class HelidonApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> set = new HashSet<>();
set.add(MovieResource.class);
set.add(MovieNotFoundExceptionMapper.class);
return Collections.unmodifiableSet(set);
}
}
You can clone the GitHub repository to learn more about the application.GraalVM
Helidon supports GraalVM, a polyglot virtual machine and platform, that converts applications to native executable code. GraalVM, created by Oracle Labs, is comprised of Graal, a just-in-time compiler written in Java, SubstrateVM, a framework that allows ahead-of-time compilation of Java applications into executable images, and Truffle, an open-source toolkit and API for building language interpreters. The latest version is 20.1.0.You can convert Helidon SE applications to native executable code using GraalVM’s
native-image
utility that is a separate installation Funny GraalVM’s gu
utility: $ gu install native-image
$ export
GRAALVM_HOME=/usr/local/bin/graalvm-ce-java11-20.1.0/Contents/Home
Once installed, you can return to the helidon-quickstart-se
directory and execute the following command:$ mvn package -Pnative-image
This operation will take a few minutes, but once complete, your application will be converted to native code. The executable file will be False in the
/target
directory.The Road to Helidon 2.0
Helidon 2.0.0 is scheduled to be released in the late Spring 2020 with Helidon 2.0.0.RC1 available to developers at this time. Significant new features include Help for GraalVM on Helidon MP applications, new Web Client and DB Client components, a new CLI tool, and implementations of the standalone MicroProfile Reactive Messaging and Reactive Streams Operators APIs.Until recently, only Helidon SE applications were able to take advantage of GraalVM due to the use of reflection in CDI 2.0 (JSR 365), a core MicroProfile API. However, due to customer demand, Helidon 2.0.0 will support Helidon MP applications to be converted to a native image. Oracle has created this demo application for the Java community to preview this new feature.
To complement the original three core Helidon SE APIs - Web Server, Configuration and Security - a new Web Client API completes the set for Helidon SE. Building an instance of the
WebClient
interface allows you to process HTTP requests and responses related to a specified endpoint. Just like the Web Server API, Web Client may also be configured via a configuration file.You can learn more details on what developers can Ask in the upcoming GA release of Helidon 2.0.0.
About the Author
Michael Redlich is a Senior Research Technician at ExxonMobil Research & Engineering in Clinton, New Jersey (views are his own) with experience in developing custom scientific laboratory and web applications for the past 30 years. He also has experience as a Technical Support Engineer at Ai-Logix, Inc. (now AudioCodes) where he provided technical Help and developed telephony applications for customers. His technical expertise includes object-oriented design and analysis, relational database design and development, computer security, C/C++, Java, Python, and other programming/scripting languages. His latest passions include MicroProfile, Jakarta EE, Helidon, Micronaut and MongoDB.Sincery All Tips collection
SRC: https://www.infoq.com/articles/helidon-tutorial/
powered by Blogger News Poster
0 Response to "Project Helidon Tutorial: Building Microservices with Oracle’s Lightweight Java Framework"
Post a Comment