This commit is contained in:
jamesfalkner
2019-07-15 21:58:46 -04:00
parent d2554ddbd3
commit fb7e7deba6
15 changed files with 189 additions and 145 deletions

View File

@@ -1,8 +1,9 @@
## Dependency Injection
= Dependency Injection
:experimental:
In the previous step you created a basic RESTful Java application with Quarkus. In this step we'll add a custom bean using dependency injection (DI). Quarkus DI solution is based on the http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html[Contexts and Dependency Injection for Java 2.0 specification,target=_blank]. However, it is not a full CDI implementation verified by the TCK. Only a subset of the CDI features is implemented - see also the https://quarkus.io/guides/cdi-reference#supported_features[list of supported features, target=_blank] and the https://quarkus.io/guides/cdi-reference#limitations[list of limitations, target=_blank].
## Add Custom Bean
== Add Custom Bean
Lets modify the application and add a companion bean. In CodeReady, right-click on the `org.acme.people.service` package in the project browser and select _New_ --> _Java Class_. Name the class `GreetingService`.
@@ -59,11 +60,11 @@ Use the _Assistant_ > _Organize Imports_ menu option to fix this and import the
If you do not get red squigglies, or you can't make them disappear, try to close the file and re-open it, or reload your web browser. This is a known issue with Che on recent versions of Kubernetes.
====
## Run the app
== Run the app
Once again, run the app in _dev_ mode by using the command palette and selecting **Build and Run Locally**.
## Inspect the results
== Inspect the results
Check that it works as expected by loading the new endpoint using the Preview URL and adding the `/hello/greeting/quarkus` to the end of the URL, just as you did before.
@@ -74,6 +75,6 @@ Note we are exercising our new bean using the `/hello/greeting/quarkus` endpoint
In this case, the hostname is the hostname from the pod the app is running on within Kubernetes and will change later on.
====
## Congratulations!
== Congratulations!
It's a familiar CDI-based environment for you Enterprise Java developers out there, with powerful mechanisms to reload your code _as you type_ (or very close to realtime). In the next step, we'll create some tests for our app, which should also be familiar to _all_ developers.

View File

@@ -1,8 +1,9 @@
## Cloud Native
= Cloud Native
:experimental:
In this step we will package the application as a Linux Container image, and deploy it to Kubernetes, and add a few features common to cloud native apps that you as a developer will need to handle. We'll use OpenShift 4 as our deployment target, which is a distribution of Kubernetes from Red Hat.
## Health Probes
== Health Probes
Quarkus application developers can utilize the MicroProfile Health specification to write HTTP health probes for their applications. These endpoints by default provide basic data about the service however they all provide a way to customize the health data and add more meaningful information (e.g. database connection health, backoffice system availability, etc).
@@ -11,7 +12,7 @@ Quarkus application developers can utilize the MicroProfile Health specification
There are of course a category of issues that can't be resolved by restarting the container. In those scenarios, the container never recovers and traffic will no longer be sent to it (which can have cascading effects on the rest of the system, possibly requiring human intervention, which is why monitoring is crucial to availability).
====
### Add Extension
=== Add Extension
Let's build a simple REST application endpoint exposes https://microprofile.io[MicroProfile] Health checks at the `/health` endpoint according to the specification. It will also provide several other REST endpoints to allow us to dynamically query the health of our Quarkus application.
@@ -52,7 +53,7 @@ You'll see:
This default health check will return success as long as the app is running - if it crashes, the health check will fail.
### Add a probe
=== Add a probe
We can now implement a better Health Check using the MicroProfile APIs. Create a new Java class - `org.acme.people.health.SimpleHealthCheck` (hint: right-click on the `org.acme.people.health` package and select _New > Java Class_ and name it `SimpleHealthCheck`). In this file, implement the health check (you can copy/paste this code):
@@ -114,7 +115,7 @@ The new health check procedure is now present in the `checks` array:
Congratulations! Youve created your first Quarkus health check procedure. Lets continue by exploring what else can be done with the MicroProfile Health specification.
=== Custom data in health checks
== Custom data in health checks
In the previous step we created a simple health check with only the minimal attributes, namely, the health check name and its state (`UP` or `DOWN`). However, MicroProfile also provides a way for the applications to supply arbitrary data in the form of key/value pairs sent in the health check response. This can be done by using the `withData(key, value)`` method of the health check response builder API. This is useful to provide additional info about passing or failing health checks, to give some indication of the problem when failures are investigated.
@@ -148,7 +149,7 @@ public class DataHealthCheck implements HealthCheck {
If you rerun the health check procedure again by accessing the `/health` endpoint with `curl` you can see that the new health check Health check with data is present in the `checks` array. This check contains a new attribute called `data` which is a JSON object consisting of the properties we have defined in our health check procedure above.
=== Negative Health Checks
== Negative Health Checks
In this section we create another health check which simulates a connection to an external service provider such as a database. For simplicity reasons, we only determine whether the database is accessible or not by a configuration property.
@@ -213,7 +214,7 @@ If you now rerun the health check (by running the same `curl` command from befor
We shouldnt leave this application with a health check in DOWN state. Because we are running Quarkus dev mode, add `database.up=true` to the end of the `src/main/resources/application.properties` file and rerun the health check againit should be up again.
### Accessing liveness and readiness separately
== Accessing liveness and readiness separately
Quarkus apps can access the two different types using two different endpoints. This is useful when configuring Kubernetes with probes which we'll do later, as it can access each separately (and configure each with different timeouts, periods, failure thresholds, etc). For example, You may want your Readiness probe to wait 30 seconds before starting, but Liveness should wait 2 minutes and only wait 10 seconds between retries.
@@ -330,7 +331,7 @@ We also need to update the functional test to reflect the changes made to endpoi
Since our applcation is still running from before, thanks to Quarkus Live Reload we should immediately see changes. Update `application.properties`, by changing the `greeting.message`, `greeting.name`, or adding `greeting.suffix` and running the same `curl http://localhost:8080/hello` after each change.
=== Quarkus Configuration options
== Quarkus Configuration options
Quarkus itself is configured via the same mechanism as your application. Quarkus reserves the `quarkus.` namespace for its own configuration.
@@ -384,7 +385,7 @@ By default Quarkus has three profiles, although it is possible to use as many as
. `test` - Activated when running tests
. `prod` - The default profile when not running in `dev` or `test` mode
==== Exercise Configuration Profile
=== Exercise Configuration Profile
Let's give this a go. In your `application.properties`, add a different `message.prefix` for the `prod` profile. To do this, change the content of the `greeting.` properties in `application.properties` to be:
@@ -431,10 +432,10 @@ What did you get? You should get `hello production quarkus!` indicating that the
In this example we read configuration properties from `application.properties`. You can also introduce custom configuration sources in the standard MicroProfile Config manner. To do this, you must provide a class which implements either `org.eclipse.microprofile.config.spi.ConfigSource` or `org.eclipse.microprofile.config.spi.ConfigSourceProvider`. Create a service file for the class and it will be detected and installed at application startup. https://microprofile.io/project/eclipse/microprofile-config[More Info]. This would be useful, for example, to read directly from **Kubernetes ConfigMap**s.
====
## Cleanup
== Cleanup
Stop the app for now by pressing kbd:[CTRL+C] in the terminal or closing the Terminal window in which the app runs.
## Congratulations
== Congratulations
Cloud native encompasses much more than health probes and externalized config. With Quarkus' _container and Kubernetes-first philosophy_, excellent performance, support for many cloud native frameworks, it's a great place to build your next cloud native app.

View File

@@ -1,8 +1,9 @@
## Debugging in Dev Mode
= Debugging in Dev Mode
:experimental:
Debugging is an art of finding errors (bugs) and eradicating (debug) them from a piece of code written in any programming language. Quarkus apps are no different in this regard and you can use the raw Java command line debugger (`jdb`) or, if your IDE supports it, graphical debugging. Most IDEs have integration with Java debuggers, including Eclipse Che, so let's exercise it.
## Write some bad code
== Write some bad code
Let's introduce a subtle off-by-one bug in our application and use the debugger to debug it.
@@ -22,7 +23,7 @@ Open up the `GreetingResource.java` class again, and add another RESTful interfa
A a bug has been reported where the last letter is not working. You can probably spot the bug right away but let's see how you could find this with the debugger, and more importantly fix it and re-test very quickly.
## Run the app in debug mode
== Run the app in debug mode
Before you can debug the app, you need to run it in _debug mode_. Typically this means adding a lot of JVM command line switches, and Quarkus has simplified this for you.
@@ -33,7 +34,7 @@ image::debugcmd.png[test, 600]
You will see the tell-tale Java log statment showing that debugging is enabled: `Listening for transport dt_socket at address: 5005`. Quarkus will wait until you attach a debugger before continuing.
## Attach debugger
== Attach debugger
To connect to it, use the _Run > Edit Debug Configurations_ menu command. In the dialog, Click the `+` button next to `JAVA` to create a new Java debug configuration. You can name it whatever you want, but keep the `localhost` field as-is and select port `5005` in the port selection field:
@@ -101,6 +102,8 @@ We need to pass an offset that is one _before_ the end, to get the last letter.
Click the **Resume** button to let the method continue and return the value (your `curl` command has probably timed out by now).
== Fix the bug
Fix the code by changing the line that calls `substring()` to read:
[source, java, role="copypaste"]
@@ -123,6 +126,10 @@ Click **End Debug Session** button to quit the debugging session.
Quarkus apps are just like any other Java app, so debugging is straightforward and supported by many IDEs and CLIs out there.
## Cleanup
== Cleanup
Go back to your Terminals and stop the app for now by pressing kbd:[CTRL+C] in the terminal or closing the Terminal window in which the app runs.
Go back to your Terminals and stop the app for now by pressing kbd:[CTRL+C] in the terminal or closing the Terminal window in which the app runs.
== Congratulations!
Debugging Quarkus apps is the same as any other Java app. Combined with Live Reload, it makes development quick and (relatively) painless!

View File

@@ -1,10 +1,11 @@
== Deploy to OpenShift
= Deploy to OpenShift
:experimental:
With our app fully ready for its first cloud native deployment, let's package it up for deployment to our Kubernetes platform as a native image. We'll use some OpenShift tooling to accomplish this.
OpenShift is a commercially supported distribution of Kubernetes from Red Hat. The platform is also available as open source, in the form of https://www.okd.io/[OKD], the Origin Community Distribution of Kubernetes that powers Red Hat OpenShift.
=== Build native image
== Build native image
Let's rebuild our native image with all our changes thus far. Using the Command Palette, select **Build Native Quarkus App** and wait 3-4 minutes for it to finish (this runs `mvn clean package -Pnative` under the hood).
@@ -12,7 +13,7 @@ As you recall, the output of this process is a native Linux binary. To package i
Now that we have our app built as a container, let's deploy it to our cluster.
=== Login to OpenShift
== Login to OpenShift
Although your Eclipse Che workspace is running on the Kubernetes cluster, it's running with a default restricted _Service Account_ that prevents you from creating most resource types. So we'll log in with your workshop user. Open a Terminal and issue the following command.
@@ -51,7 +52,7 @@ their content in isolation from other communities. OpenShift _projects_ provide
For this scenario, a project has been created for you called `userNN-project` (where `NN` is your assigned student number). You will use this project to deploy your developed project in the next step.
=== Deploy to OpenShift
== Deploy to OpenShift
First, create a new _binary_ build within OpenShift
[source,sh,role="copypaste"]
@@ -150,7 +151,7 @@ image::route.png[route,600]
You can click on the route link to open up the default Quarkus page that's packaged as part of our workshop application.
# Connect MicroProfile health check
== Connect MicroProfile health check
Earlier you implemented a series of MicroProfile health checks. To make OpenShift aware of these available health checks and begin using them, run the following two commands:
@@ -167,7 +168,6 @@ This configures both a _readiness_ probe (is the app initialized and ready to se
oc rollout status -w dc/people
----
## Congratulations!
== Congratulations!
This step covered the deployment of a native Quarkus application on OpenShift. However, there is much more, and the integration with these cloud native platforms (through health checks, configuration management, and monitoring which we'll cover later) has been tailored to make Quarkus applications execution very smooth.

View File

@@ -1,4 +1,5 @@
## Quarkus Extensions
= Quarkus Extensions
:experimental:
One of the main goals of Quarkus is ease of extensibility and to build a vibrant ecosystem.
@@ -6,6 +7,8 @@ Think of Quarkus extensions as your project dependencies. Extensions configure,
Quarkus aims to provide a support for a full https://quarkus.io/extensions/[extension ecosystem], to make it easy to discover and consume 3rd party extensions, as well as providing easier version management.
== List extensions
Retrieve the list of possible extensions using the Maven plugin. Run this in the Terminal:
[source,sh,role="copypaste"]
@@ -17,7 +20,7 @@ You can see the list of ~50 different extensions available to you.
Adding an extension is simiarly easy. With Maven, you can add extensions using `mvn quarkus:add-extension -Dextensions="extension1,extension2,..."`. The extension name can be the maven groupId/artifactId name of the extension: e.g. `io.quarkus:quarkus-agroal`. But you can pass a partial name and Quarkus will do its best to find the right extension. For example, `agroal`, `Agroal` or `agro` will expand to `io.quarkus:quarkus-agroal`. If no extension is found or if more than one extensions match, you will see a warning and a list of possible matches in the command result.
## Add an extension
== Add an extension
Later on in this lab we'll be using Hibernate with Panache, so let's add that extension here. In the Terminal, run the following command to add the _Hibernate ORM with Panache_ extension to your project:
@@ -61,7 +64,7 @@ INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-
During development, when you add new extensions you'll need to stop and re-start Quarkus apps, as it will not monitor the state of `pom.xml` and bring in new dependencies during live-reload mode. So be sure to stop and restart whenever you add new extensions!
====
## Writing your own extension
== Writing your own extension
Quarkus extensions add a new developer focused behavior to the core offering, and consist of two distinct parts, buildtime augmentation and runtime container. The augmentation part is responsible for all metadata processing, such as reading annotations, XML descriptors etc. The output of this augmentation phase is recorded bytecode which is responsible for directly instantiating the relevant runtime services.
@@ -69,7 +72,7 @@ This means that metadata is only processed once at build time, which both saves
Writing a Quarkus extension is beyond the scope of this lab, so consult the https://quarkus.io/guides/extension-authors-guide[Extension Author's Guide] for more detail on writing your own extension.
## Congratulations
== Congratulations
Quarkus aims to provide a support for a full https://quarkus.io/extensions/[extension ecosystem,target=_blank], to make it easy to discover and consume 3rd party extensions, as well as providing easier version management.

View File

@@ -1,4 +1,5 @@
## What is Quarkus?
= What is Quarkus?
:experimental:
image::logo.png[]
@@ -6,7 +7,8 @@ Quarkus is a Kubernetes Native Java stack tailored for GraalVM & OpenJDK HotSpot
Throughout this lab you'll discover how Quarkus can make your development of cloud native apps faster and more productive.
## Click-to-Copy
== Click-to-Copy
You will see various code and command blocks throughout these exercises. Some of
the command blocks can be copy/pasted directly. Others will require modification
of the command before execution, for example to add your unique user ID assigned by the instructor. If you see a command block with a red border
@@ -47,5 +49,5 @@ After a minute or two, you'll be placed in the workspace:
image::che-workspace.png[workspace,800]
Users of Eclipse, IntelliJ IDEA or Visual Studio Code will see a familiar layout: a project/file browser on the left, a code editor on the right, and a terminal at the bottom. You'll use all of these during the course of this workshop, so keep this browser tab open throughout. If things get weird, you can simply reload the browser tab to refresh the view.
Users of Eclipse, IntelliJ IDEA or Visual Studio Code will see a familiar layout: a project/file browser on the left, a code editor on the right, and a terminal at the bottom. You'll use all of these during the course of this workshop, so keep this browser tab open throughout. **If things get weird, you can simply reload the browser tab to refresh the view.**

View File

@@ -1,8 +1,9 @@
## Reactive Streams with Quarkus and Kafka
= Reactive Streams with Quarkus and Kafka
:experimental:
In this exercise, you will use the Quarkus Kafka extension to build a streaming application using MicroProfile Reative Streams Messaging and https://kafka.apache.org[Apache Kafka], a distributed streaming platform. You will also use https://strimzi.io/[Strimzi], which provides an easy way to run an Apache Kafka cluster on Kubernetes using [Operators](https://operatorhub.io/what-is-an-operator).
### What is Apache Kafka?
== What is Apache Kafka?
Apache Kafka is a distributed streaming platform. A streaming platform has three key capabilities:
@@ -15,7 +16,7 @@ Kafka is generally used for two broad classes of applications:
* Building real-time streaming data pipelines that reliably get data between systems or applications
* Building real-time streaming applications that transform or react to the streams of data
### What is Strimzi?
== What is Strimzi?
Strimzi provides a way to run an Apache Kafka cluster on Kubernetes in various deployment configurations.
@@ -27,13 +28,13 @@ Strimzi provides three operators:
* **Topic Operator** - Responsible for managing Kafka topics within a Kafka cluster running within an OpenShift or Kubernetes cluster.
* **User Operator** - Responsible for managing Kafka users within a Kafka cluster running within an OpenShift or Kubernetes cluster.
## The Goal
== The Goal
In this exercise, we are going to generate (random) names in one component. These names are written in a Kafka topic (`names`). A second component reads from the `names` Kafka topic and applies some magic conversion to the name (adding an honorific). The result is sent to an _in-memory stream_ consumed by a JAX-RS resource. The data is sent to a browser using _server-sent events_ and displayed in the browser. It will look like this:
image::names.png[names,800]
## Deploy Kafka via Strimzi
=== Create Kafka Cluster
The Strimzi operator installs and manages Kafka clusters on Kubernetes. It's been pre-installed for you, so all you have to do is create a Kafka cluster inside your namespace.
@@ -48,6 +49,8 @@ image::createkafka.png[createkafka,600]
This will create a new Kafka Kubernetes object in your namespace, triggering the Operator to deploy Kafka.
After clicking **Create** you will be taken to the list of objects created by the Kafka operator.
== Create Kafka Topic
Next, click on the **Kafka Topic** tab:
image::topictab.png[topictab,600]
@@ -81,7 +84,7 @@ oc login https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --insecure-sk
Don't worry if they're not all in the _Running_ status, they will eventually complete and we'll use them later on in this exercise.
## Add Quarkus Kafka Extension
== Add Quarkus Kafka Extension
With Kafka installing, turn your attention back to the app. Like other exercises, we'll need another extension to integrate with Kafka. Install it with:
@@ -92,7 +95,7 @@ mvn quarkus:add-extension -Dextensions="kafka"
This will add the necessary entries in your `pom.xml` to bring in the Kafka extension.
## Create name generator
== Create name generator
To start building the app, create a new Java class in the `org.acme.people.stream` called `NameGenerator`. This class will generate random names and publish them to our Kafka topic for further processing. Use this code:
@@ -122,7 +125,7 @@ public class NameGenerator {
The method returns a Reactive Stream. The generated items are sent to the stream named `generated-name`. This stream is mapped to Kafka using the application.properties file that we will create soon.
## Add honorifics
== Add honorifics
The name converter reads the names from Kafka, and transforms them, adding a random (English) honorific to the beginning of the name.
@@ -157,7 +160,7 @@ public class NameConverter {
The process method is called for every Kafka record from the `names` topic (configured in the application configuration). Every result is sent to the my-data-stream in-memory stream.
## Expose to front end
== Expose to front end
Finally, lets bind our stream to a JAX-RS resource. Create a new Java class in the same package called `NameResource`. Use this code:
@@ -194,7 +197,7 @@ public class NameResource {
<2> Indicates that the content is sent using _Server Sent Events_
<3> Returns the stream (Reactive Stream)
## Configure application
== Configure application
We need to configure the Kafka connector. This is done in the `application.properties` file (in the `src/main/resources` directory). The keys are structured as follows:
@@ -228,11 +231,11 @@ More details about this configuration is available on the https://kafka.apache.o
What about `my-data-stream`? This is an in-memory stream, not connected to a message broker.
====
## Rebuild Executable JAR
== Rebuild Executable JAR
Now we are ready to run our application. Using the command palette, select **Create Executable JAR**. You should see a bunch of log output that ends with a `SUCCESS` message.
## Deploy
== Deploy
And now start the build using our executable JAR:
@@ -243,7 +246,7 @@ oc start-build people --from-file target/*-runner.jar --follow
The build should take a minute or two to complete.
## Test
== Test
Our application should be up and running in a few seconds after the build completes and generating names. To see if it's working, run this command in a Terminal to generate the URL to the sample visualization of the stream of names being generated:
@@ -258,7 +261,7 @@ image::names.png[names,800]
These are the original names streamed through Kafka, altered to add a random honorific like "Sir" or "Madam", and displayed in a "word cloud" for you to enjoy!
## Congratulations
== Congratulations!
This guide has shown how you can interact with Kafka using Quarkus. It utilizes MicroProfile Reactive Messaging to build data streaming applications.

View File

@@ -1,4 +1,5 @@
## Asynchronous messaging between beans
= Asynchronous messaging between beans
:experimental:
Quarkus allows different beans to interact using asynchronous messages, enforcing loose-coupling. The messages are sent to _virtual_ addresses. It offers 3 types of delivery mechanism:
@@ -15,7 +16,7 @@ All these delivery mechanism are non-blocking, and provide one of the fundamenta
The asynchronous message passing feature in Quarkus allows _replying_ to messages -- which is not supported by Reactive Messaging. However, it is limited to single-event behavior (no stream) and to local messages.
====
## Add extension
== Add extension
This mechanism uses the https://vertx.io/docs/vertx-core/java/#event_bus[Vert.x EventBus], so you need to enable the `vertx` extension to use this feature. Add the extension in the Terminal using this command:
@@ -37,11 +38,11 @@ Quarkus provides 3 Vert.x APIs:
We're using the _Axle_ variant here, which provides good support for async operations in HTTP resources.
## Run the app
== Run the app
Next, Run the app once more by using the command palette and select **Build and Run Locally** to start the app up in dev local mode.
## Create RESTful resource
== Create RESTful resource
We'll start by creating a new asynchronous endpoint. Open the `PersonResource` class and add a new field which will provide access to the Vert.x event bus which is used to pass messages between components:
@@ -86,7 +87,7 @@ The `EventBus` object provides methods to:
With this endpoint we can POST to the `/person/joe` endpoint to create a new user given the name.
### Try it, and fail
== Try it, and fail
With our endpoint, confirm it fails using the Terminal to execute:
@@ -97,7 +98,7 @@ curl -X POST http://localhost:8080/person/joe
**This will fail** with a hang/timeout - we posted the message to the Vert.x event bus, but there's nothing to receive it!
### Create consumer
== Create consumer
Create a new class in the `org.acme.people.service` package called `PersonService`. Use the following code to implement our message consumer:
@@ -172,10 +173,10 @@ To better understand, lets detail how the HTTP request/response has been hand
. This response is sent back using the reply mechanism
. Once the reply is received by the sender, the content is written to the HTTP response
## Cleanup
== Cleanup
Stop the app for now by pressing kbd:[CTRL+C] in the terminal or closing the Terminal window in which the app runs.
## Congratulations!
== Congratulations!
In this exercise you learned how Quarkus allows different beans to interact using asynchronous messages. We'll take this to the next level in the next exercise.

View File

@@ -1,4 +1,5 @@
## Monitoring Quarkus Apps using MicroProfile Metrics
= Monitoring Quarkus Apps using MicroProfile Metrics
:experimental:
This exercise demonstrates how your Quarkus application can utilize the https://github.com/eclipse/microprofile-metrics[MicroProfile Metrics] specification through the SmallRye Metrics extension.
@@ -6,7 +7,7 @@ MicroProfile Metrics allows applications to gather various metrics and statistic
The metrics can be read remotely using JSON format or the https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format[OpenMetrics text format], so that they can be processed by additional tools such as Prometheus, and stored for analysis and visualisation. You can then use tools like http://prometheus.io[Prometheus] and http://grafana.com[Grafana] to collect and display metrics for your Quarkus apps.
### Install Prometheus
== Install Prometheus
First, let's install Prometheus. Prometheus is an open-source systems monitoring and alerting toolkit featuring:
@@ -62,7 +63,7 @@ oc rollout status -w dc/prometheus
You should see `replication controller "prometheus-2" successfully rolled out`.
## Add Metrics to Quarkus
== Add Metrics to Quarkus
Like other exercises, we'll need another extension to enable metrics. Install it with:
@@ -73,11 +74,11 @@ mvn quarkus:add-extension -Dextensions="metrics"
This will add the necessary entries in your `pom.xml` to bring in the Metrics capability. It will import the `smallrye-metrics` extension which is an implementation of the MicroProfile Metrics specification used in Quarkus.
## Start the app
== Start the app
Next, Run the app once more by using the command palette and select **Build and Run Locally** to start the app up in dev local mode.
## Test Metrics endpoint
== Test Metrics endpoint
You will be able to immediately see the raw metrics generated from Quarkus apps. Run this in the Terminal:
@@ -100,7 +101,7 @@ base:gc_ps_mark_sweep_count 2.0
This is what Prometheus will use to access and index the metrics from our app when we deploy it to the cluster.
## Add additional metrics
== Add additional metrics
Out of the box, you get a lot of basic JVM metrics which are useful, but what if you wanted to provide metrics for your app? Let's add a few using the MicroProfile Metrics APIs.
@@ -148,7 +149,7 @@ The comments in the metrics output starting with `#` are part of the format and
In the OpenMicroProfile Metrics names are prefixed with things like `vendor:` or `application:` or `base:`. These _scopes_ can be selectively accessed by adding the name to the accessed endpoint, e.g. `curl http://localhost:8080/metrics/application` or `curl http://localhost:8080/metrics/base`.
====
## Add a few more
== Add a few more
Let's add a few more metrics for our Kafka stream we setup in the previous exercise. Open the `NameConverter` class (in the `org.acme.people.stream` package), and add these metrics annotations to the `process()` method:
@@ -160,11 +161,11 @@ Let's add a few more metrics for our Kafka stream we setup in the previous exerc
Don't forget to import the correct classes as before.
## Rebuild Executable JAR
== Rebuild Executable JAR
Now we are ready to run our application on the cluster. Using the command palette, select **Create Executable JAR**. You should see a bunch of log output that ends with a `SUCCESS` message.
## Deploy
== Deploy
Let's deploy our app to the cluster and see if Prometheus picks up our metrics! To do this, start the container build using our executable JAR:
@@ -173,7 +174,7 @@ Let's deploy our app to the cluster and see if Prometheus picks up our metrics!
oc start-build people --from-file target/*-runner.jar --follow
----
## Confirm deployment
== Confirm deployment
Run and wait for the app to complete its rollout:
@@ -182,7 +183,7 @@ Run and wait for the app to complete its rollout:
oc rollout status -w dc/people
----
## Test
== Test
You'll need to trigger the methods that we've instrumented, so first run this command to get the URL to the word cloud page we previously created, and then open it in your browser, which will start producing names (and generating metrics):
@@ -222,21 +223,21 @@ image:promg2.png[names,800]
Of course Quarkus apps use very little memory, even for apps stuffed with all sorts of extensions and code.
## Beyond Prometheus
== Beyond Prometheus
Prometheus offers a powerful way to capture metrics, a rich query language and alerting of unusual or unexpected conditions. Another open source project, https://grafana.com/[Grafana] is commonly used as a flexible, graphical frontend which has support for Prometheus (and many other data sources) and can display https://prometheus.io/docs/visualization/grafana/[customized, realtime dashboards]:
image::https://grafana.com/api/dashboards/3308/images/2099/image[Grafana dashboard,800]
### Extra Credit after this workshop
== Extra Credit after this workshop
If you have time after completing this workshop, try running grafana and hooking it up to your Prometheus instance you've created. Hint: `oc new-app grafana/grafana && oc expose svc/grafana` and then access Grafana at the URL emitted by this command: `echo http://$(oc get route grafana -o=go-template --template='{{ .spec.host }}')`, add Prometheus as a data source at `http://prometheus:9090`, and create a custom dashboard showing some of your application metrics, or import an existing dashboard from the Grafana community.
## Cleanup
== Cleanup
Go to the first Terminal tab and press kbd:[CTRL+C] to stop our locally running app (or close the Terminal window).
## Congratulations!
== Congratulations!
This exercise demonstrates how your Quarkus application can utilize the https://github.com/eclipse/microprofile-metrics[MicroProfile Metrics] specification through the SmallRye Metrics extension.

View File

@@ -1,4 +1,5 @@
== Building Native Images
= Building Native Images
:experimental:
Lets now produce a native executable for our application. It improves the startup time of the application, and produces a minimal disk and memory footprint. The executable would have everything to run the application including the "JVM" (shrunk to be just enough to run the application), and the application. This is accomplished using https://graalvm.org[GraalVM].
@@ -13,7 +14,7 @@ GraalVM is already installed for you. Inspect the value of the `GRAALVM_HOME` va
echo $GRAALVM_HOME
----
## Build the image
== Build the image
Within the `pom.xml`{{open}} is the declaration for the Quarkus Maven plugin which contains a profile for `native-image`:
@@ -61,7 +62,7 @@ Since we are on Linux in this environment, and the OS that will eventually run o
In addition to the regular files, the build will produce `target/people-1.0-SNAPSHOT-runner`. This is a native Linux binary. Not a shell script, or a JAR file, but a native binary.
## Run native image
== Run native image
Since our environment here is Linux, you can _just run it_. In the terminal, run:
@@ -118,10 +119,10 @@ hello quarkus from master
Nice!
## Cleanup
== Cleanup
Go to the first Terminal tab and press kbd:[CTRL+C] to stop our native app (or close the Terminal window).
## Congratulations!
== Congratulations!
You've now built a Java application as an executable JAR and a Linux native binary. We'll explore the benefits of native binaries later in when we start deploying to Kubernetes.

View File

@@ -1,4 +1,5 @@
## Documenting APIs in Quarkus
= Documenting APIs in Quarkus
:experimental:
Exposing APIs has become an essential part of all modern applications. At the center of this revolution known as the _API Economy_ we find _RESTful_ APIs, which can transform any application into language agnostic services that can be called from anywhere: on-premises, private cloud, public cloud, etc.
@@ -12,7 +13,7 @@ The MicroProfile OpenAPI specification requires vendors to produce a valid OpenA
Therefore you as a developer get a _lot_ of functionality out of the box without doing anything. Let's test this out.
## Out of the box APIs
== Out of the box APIs
If your application is still running from the previous exercise, go back to its terminal and type kbd:[CTRL+C] (or just close the terminal window). We need to add another extension for OpenAPI. Run the following command:
@@ -81,7 +82,7 @@ curl -H "Accept: application/json" http://localhost:8080/openapi
So out of the box all your endpoints are fully documented and will be emitted in a programmatic format from the `/openapi` endpoint. CLIs are great, but for quick testing of APIs, wouldn't it be great if that programmatic API was turned into an interactive, forms-based web page for quickly trying out endpoints? That's what _Swagger UI_ does.
## Access Swagger UI
== Access Swagger UI
When building APIs, developers want to test them quickly. Swagger UI is a great tool for visualizing and interacting with your APIs. The UI is automatically generated from your OpenAPI specification.
@@ -123,9 +124,10 @@ image::swaggeryeartestresult.png[swaggeryeartestresult,600]
Pretty handy way to test out your APIs!
## Documenting your APIs
== Documenting your APIs
OpenAPI and Swagger are useful tools but they become even more useful to 3rd party consumers of your API if you properly document them. With OpenAPI, you as a developer can add additional annotations right into the code to document them (which is a good practice anyway - when the code changes, the docs can too).
You as an application developer have a few choices in documenting your APIs:
. Augment your JAX-RS annotations with the OpenAPI https://github.com/eclipse/microprofile-open-api/blob/master/spec/src/main/asciidoc/microprofile-openapi-spec.adoc#annotations[Annotations]. Using annotations means you dont have to re-write the portions of the OpenAPI document that are already covered by the JAX-RS framework (e.g. the HTTP method of an operation).
@@ -136,9 +138,11 @@ Additionally, a https://github.com/eclipse/microprofile-open-api/blob/master/spe
So let's add a bit more documentation to our `/person/birth/before/{year}` endpoint using the first option (annotations). Open the `PersonResource` class, and find the `getBeforeYear` method -- this method implements our endpoint.
=== Add OpenAPI annotations
Add a few annotations:
1. Add an `@Operation` annotation on the method to provide a brief summary and description. You'll need to _Assistant > Organize Imports_ to complete the import statements after adding the code:
**1. Add an `@Operation` annotation on the method to provide a brief summary and description. You'll need to _Assistant > Organize Imports_ to complete the import statements after adding the code:**
[source,java,role="copypaste"]
----
@@ -146,7 +150,7 @@ Add a few annotations:
description = "Search the people database and return a list of people born before the specified year")
----
2. Add an `@ApiResponses` annotation below the `@Operation` that documents two of the possible HTTP return values ("200" and "500") to the method signature:
**2. Add an `@ApiResponses` annotation below the `@Operation` that documents two of the possible HTTP return values ("200" and "500") to the method signature:**
[source,java,role="copypaste"]
----
@@ -159,7 +163,7 @@ Add a few annotations:
})
----
3. Add an additional bit of documentation before the existing `@PathParam` method parameter:
**3. Add an additional bit of documentation before the existing `@PathParam` method parameter:**
[source,java,role="copypaste"]
----
@@ -172,11 +176,11 @@ Now reload the same Swagger UI webpage. Notice the Swagger documention is more f
image::swaggerparams.png[swaggerparams,600]
## Cleanup
== Cleanup
Stop the app for now by pressing kbd:[CTRL+C] in the terminal or closing the Terminal window in which the app runs.
## Congratulations
== Congratulations
In this exercise you learned more about the MicroProfile OpenAPI specification and how to use it to do in-place documentation of your RESTful microservice APIs.

View File

@@ -1,4 +1,5 @@
## Hibernate ORM with Panache
= Hibernate ORM with Panache
:experimental:
Hibernate ORM is the de facto JPA implementation and offers you the full breadth of an Object Relational Mapper. It makes complex mappings possible, but many simple and common mappings can also be complex. Hibernate ORM with **Panache** focuses on making your entities trivial and fun to write and use with Quarkus.
@@ -14,7 +15,7 @@ With Panache, we took an opinionated approach to make hibernate as easy as possi
Thats all there is to it: with Panache, Hibernate ORM has never looked so trim and neat.
## People
== People
You may have wondered why we are using `people` in our Java APIs, and now you'll find out; we'll be creating a simple data-driven application that manages a set of people, who have a name, birth date, and eye color. In the previous lab we added the extension for Panache to our project, but we will need a few more for connecting to databases. Add these extensions with the following command:
@@ -44,7 +45,7 @@ This causes the `%dev` properties (using H2 database) to be active in developmen
These names are known to Quarkus, and you can introduce your own profiles and use them in a similar way for other environments (and can be overridden at runtime). You can read more about externalized config in the https://quarkus.io/guides/application-configuration-guide[Quarkus Application Configuration Guide].
====
### Create Entity
== Create Entity
With our extension installed, we can now define our entity using Panache.
@@ -102,7 +103,7 @@ You will incrementally replace the `// TODO` elements as we go along, so don't d
As you can see we've defined the three fields `name`, `birth`, and `eyes`. We're using the Java Persistence API's `@Enumerated` field type for our eye color.
### Define RESTful endpoint
== Define RESTful endpoint
Next, we'll create a `PersonResource` class which we will use for our RESTful endpoint. Create a new Java class in the `src/main/java/org/acme/people/rest` directory called `PersonResource` with the following code:
@@ -159,7 +160,7 @@ You may see lots of warnings about unused imports. Ignore them, we'll use them l
As you can see we've implemented our first Panache-based query, the `getAll` method, which will return our list of people as a JSON object when we access the `GET /person` endpoint. This is defined using standard JAX-RS `@Path` and `@GET` and `@Produces` annotations.
### Add sample data
== Add sample data
Let's add some sample data to the database so we can test things out. Create a new file `src/main/resources/import.sql` and add some SQL statements to the file to run on startup:
@@ -176,7 +177,7 @@ INSERT INTO person(id, name, birth, eyes) VALUES (nextval('hibernate_sequence'),
These statements will add some fake people to our database on startup.
### Run the application
== Run the application
Now we are ready to run our application. Using the command palette, select **Build and Run Locally** (if you had a previous app running, kbd:[CTRL+C] or close the old Terminal before running it again). You should see a bunch of log output that ends with:
@@ -235,7 +236,7 @@ Advanced use cases may require a custom ID strategy, which can by done by extend
public Integer id;
----
## Add Basic Queries
== Add Basic Queries
Lets modify the application and add some queries. Much like traditional object-oriented programming, Panache and Quarkus recommend you place your custom entity queries as close to the entity definition as possible, in this case in the entity definition itself. Open the `Person` entity class (it's in the `org.acme.person.model` package), and add the following code under the `// TODO: Add more queries` comment:
@@ -280,7 +281,7 @@ public List<Person> getBeforeYear(@PathParam(value = "year") int year) {
}
----
### Inspect the results
== Inspect the results
Since we still have our app running using `mvn quarkus:dev`, when you make these changes and reload the endpoint, Quarkus will notice all of these changes and live reload them.
@@ -339,17 +340,17 @@ The `Person` entity's superclass comes with lots of super useful static methods
In the next step we'll show you how Panache can help to adapt entities to high performance frontends, even in the face of millions of records.
## Add Paging and Filtering
== Add Paging and Filtering
In the previous step you added a few more custom queries to your entity and the associated RESTful endpoints. In this step we'll build a slightly more complex query including filtering, searching and paging capabilities.
### Showing data in tables
=== Showing data in tables
Earlier we used `curl` to access our data, which is very useful for testing, but for real applications you will usually surface the data in other ways, like on web pages using tables, with options for searching, sorting, filtering, paging, etc. Quarkus and Panache make this easy to adapt your application for any display library or framework.
Let's use a popular jQuery-based plugin called https://www.datatables.net[DataTables,target=_blank]. It features a *server-side* processing mode where it depends on the server (in this case our Quarkus app) to do searching, filtering, sorting, and paging. This is useful for very large datasets, on the order of hundreds of thousands of records or more. Transmitting the entire data set to the client browser is ineffecient at best, and will crash browsers, increase networking usage, and frustrate users at worst. So let's just return the exact data needed to be shown.
### Add Datatables endpoint
=== Add Datatables endpoint
https://www.datatables.net/manual/server-side[DataTables documentation] shows that its frontend will call an endpoint on the backend to retrieve some amount of data. It will pass several query parameters to tell the server what to sort, filter, search, and which data to return based on the page size and current page the user is viewing. For this example, we'll only support a subset:
@@ -385,6 +386,8 @@ Here we are using JAX-RS `@QueryParam` values to specify the incoming parameters
We'll fill in the `TODO`s to build this method.
=== Implement `/datatable` endpoint
DataTables requires a specific JSON payload to be returned from this, and we've pre-created a POJO `DataTable` class representing this structure in `src/main/java/org/acme/people/model/DataTable.java`. This simple structure includes these fields:
* `draw` - The async processing record id
@@ -402,6 +405,8 @@ result.setDraw(draw); // <1>
----
<1> We initialize the `DataTable` return object with the value passed in, to ensure DataTables redraws in the correct order in case of async returns.
=== Implement search logic
Next, if the request includes a search parameter, let's take care of that by including a search query, otherwise just collect all records. Add this code below the `// TODO: Filter based on search` marker:
[source,java,role="copypaste"]
@@ -417,6 +422,8 @@ if (searchVal != null && !searchVal.isEmpty()) { // <1>
----
<1> If a search value was passed in, use it to search using the Panache `find` method. Otherwise, use `findAll` to skip filtering.
=== Implement paging logic
And finally, we use the built-in Panache `page` operator to seek to the correct page of records and stream the number of entries desired, set the values into the `result` and return it. Add this code below the `// TODO: Page and return` marker:
[source,java,role="copypaste"]
@@ -431,6 +438,8 @@ result.setRecordsTotal(Person.count());
return result;
----
=== Test the result
Let's test out our new endpoint using `curl` to search for names with `yan` in their name. Execute this in the Terminal:
[source,sh,role="copypaste"]
@@ -458,7 +467,7 @@ This should return a single entity (since in our 3-person sample data, only one
}
----
### Add lifecycle hook
=== Add lifecycle hook
You often need to execute custom actions when the application starts and clean up everything when the application stops. In this case we'll add an action that will pre-generate a lot of fake data.
@@ -485,7 +494,9 @@ void onStart(@Observes StartupEvent ev) {
This code will insert 1,000 fake people with random birthdates, eye colors, and names at startup. Note the use of the `@Transactional` annotation - this is required for methods that make changes to the underlying database (and automatically executes the method in a Transaction for you).
Although this is listening for `StartupEvent`, and our application has already started, in `quarkus:dev` mode Quarkus will still fire this event once. So let's test it out and see if it picks up our new data. We'll search for a single letter `F` and limit the results to `2`:
=== Access new data
Although our lifecycle code is listening for `StartupEvent`, and our application has already started, in `quarkus:dev` mode Quarkus will still fire this event once. So let's test it out and see if it picks up our new data. We'll search for a single letter `F` and limit the results to `2`:
[source,sh,role="copypaste"]
----
@@ -524,11 +535,11 @@ You should get up to 2 records returned, but the total number available should s
Note the values for `recordsFiltered` (the number of records with the letter `F` in the name), and `recordsTotal`. The value you see for `recordsFiltered` may be different than the above value, since the number of records with an `F` in the name may vary since the data is random. But the `recordsTotal` shows our initial 3 values, plus the 1000 additional values we added in the lifecycle hook code.
## Cleanup
== Cleanup
We're done with development, so go back to the first Terminal tab and press kbd:[CTRL+C] (or just close the running tab) to stop our running application.
## Deploy to OpenShift
== Deploy Postgres database
In previous steps we deployed our sample application as a native binary. Now let's switch to a JVM-based deployment. Our production environment needs a "real" database so let's deploy a Postgres database. Run the following command to startup a database in our cluster:
@@ -542,6 +553,8 @@ oc new-app \
openshift/postgresql
----
== Rebuild and redeploy app
With our database running, re-build the application as an executable JAR using the command palette and selecting **Create Executable JAR**.
Next, re-define the container build to use the OpenJDK image using these commands:
@@ -568,6 +581,8 @@ This will also trigger a re-deployment of our existing app. Verify the app start
oc rollout status -w dc/people
----
== Access deployed app
And now we can access using `curl` once again to find everyone born in or before the year 2000 (there will be many).
[source,sh,role="copypaste"]
@@ -592,6 +607,6 @@ Notice the total number of records reported at the bottom. Type in a single lett
Skip around a few pages, try some different searches, and notice that the data is only loaded when needed. The overall performance is very good even for low-bandwidth connections or huge data sets.
## Congratulations
== Congratulations
In this exercise you got a glimpse of the power of Quarkus apps when dealing with large amounts of data. There is much more to Quarkus than fast startup times and low resource usage, so keep going!

View File

@@ -1,8 +1,9 @@
## Securing Quarkus APIs
= Securing Quarkus APIs
:experimental:
Bearer Token Authorization is the process of authorizing HTTP requests based on the existence and validity of a bearer token representing a subject and her access context, where the token provides valuable information to determine the subject of the call as well whether or not a HTTP resource can be accessed. This is commonly used in OAuth-based identity and access management systems like https://keycloak.org[Keycloak], a popular open source project. In this exercise we'll show you how to use https://github.com/eclipse/microprofile-jwt-auth/releases/download/1.1.1/microprofile-jwt-auth-spec.pdf[Microprofile JSON Web Token (JWT) RBAC], https://keyloak.org[Keycloak] and https://en.wikipedia.org/wiki/OAuth[OAuth] to secure your Quarkus applications.
## Add JWT to Quarkus
== Add JWT to Quarkus
Like other exercises, we'll need another extension to enable the use of MicroProfile JWT. Install it with:
@@ -13,7 +14,7 @@ mvn quarkus:add-extension -Dextensions="jwt"
This will add the necessary entries in your `pom.xml` to bring in JWT support.
## Configure Quarkus for MicroProfile JWT
== Configure Quarkus for MicroProfile JWT
Some configuration of the extension is required. Add this to your `application.properties`:
@@ -29,7 +30,7 @@ quarkus.smallrye-jwt.enabled=true
<2> Issuer URL. This must match the incoming JWT `iss` _claims_ or else authentication fails.
<3> Sets authentication mechanism name to `MP-JWT`, the MicroProfile JWT RBAC specification standard name for the token based authentication mechanism.
# Create protected endpoints
== Create protected endpoints
We'll create 2 JWT-protected endpoints. Create a new class `JWTResource` in the `org.acme.people.rest` package with the following code:
@@ -103,7 +104,7 @@ public class JWTResource {
<4> The `/me` and `/me/jwt` endpoints demonstrate how to access the security context for Quarkus apps secured with JWT. Here we are using a `@RolesAllowed` annotation to make sure that only users granted a specific role can access the endpoint.
<5> Use of injected JWT Claim to print the all the claims
## Rebuild and redeploy app
== Rebuild and redeploy app
First, re-build the app using the command palette and selecting **Create Executable JAR**. Once that's done, run the following command to re-deploy:
@@ -112,7 +113,7 @@ First, re-build the app using the command palette and selecting **Create Executa
oc start-build people --from-file target/*-runner.jar --follow
----
## Confirm deployment
== Confirm deployment
Run and wait for the app to complete its rollout:
@@ -121,7 +122,7 @@ Run and wait for the app to complete its rollout:
oc rollout status -w dc/people
----
## Test endpoints
== Test endpoints
The first thing to do to test any endpoint is obtain an access token from your authentication server in order to access the application resources. We've pre-created a few users in Keycloak for you to use:
@@ -151,7 +152,7 @@ Not authorized
Let's try with an authenticated user next.
### Test Alice
=== Test Alice
Get a token for user `alice` with this command:
@@ -211,7 +212,7 @@ Access forbidden: role not allowed
Alice is not an admin. Let's try with admin!
### Test Admin
=== Test Admin
Obtain an Admin token:
@@ -255,7 +256,7 @@ Claim Name: [preferred_username] Claim Value: [admin]
Success! We dump all of the claims from the JWT token for inspection.
## Using Keycloak Authentication
== Using Keycloak Authentication
Keycloak provides similar features as other OAuth/Open ID Connect servers, but can also do implicit authentication without you as a developer needing to explicitly declare protection on endpoints.
@@ -266,7 +267,7 @@ First, you'll need to enable the Keycloak extension:
mvn quarkus:add-extension -Dextensions="keycloak"
----
### Configuring Keycloak
=== Configuring Keycloak
Next, add these to your `application.properties` for Keycloak:
@@ -283,7 +284,7 @@ quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE
This configures the extension with the necessary configuration ( https://www.keycloak.org/docs/latest/securing_apps/index.html#_java_adapter_config[read more] about what these do).
### Create Keycloak endpoints
=== Create Keycloak endpoints
Create a new class called `KeycloakResource` in the `org.acme.people.rest` package with the following code:
@@ -325,7 +326,7 @@ There are other APIs you can use if you try to auto-complete the method name usi
image::secapis.png[apis, 800]
====
## Rebuild and redeploy app
=== Rebuild and redeploy app
First, re-build the app using the command palette and selecting **Create Executable JAR**. Once that's done, run the following command to re-deploy:
@@ -334,7 +335,7 @@ First, re-build the app using the command palette and selecting **Create Executa
oc start-build people --from-file target/*-runner.jar --follow
----
## Confirm deployment
=== Confirm deployment
Run and wait for the app to complete its rollout:
@@ -343,7 +344,7 @@ Run and wait for the app to complete its rollout:
oc rollout status -w dc/people
----
### Test confidential
=== Test confidential
The `/secured/confidential` endpoint is protected with a policy defined in the Keycloak Server. The policy only grants access to the resource if the user is granted with a `confidential` role. The difference here is that the application is delegating the access decision to Keycloak, so no explicit source code instrumentation is required.
@@ -404,7 +405,7 @@ confidential access for: jdoe from issuer:{{KEYCLOAK_URL}}/auth/realms/quarkus
Success! Even though our code did not explicitly protect the `/secured/confidential` endpoint, we can protect arbitrary URLs in Quarkus apps when using Keycloak.
## Congratulations!
== Congratulations!
This exercise demonstrated how your Quarkus application can use MicroProfile JWT in conjunction with Keycloak to protect your JAX-RS applications using JWT claims and bearer token authorization.

View File

@@ -1,4 +1,5 @@
## Testing
= Testing
:experimental:
In this step we'll show you how to effectively write functional and unit tests for your Quarkus Apps.
@@ -43,7 +44,7 @@ public class GreetingResourceTest {
When creating new tests, always double-check to be sure you're in the `src/test` directory tree, not `src/main`!
====
## Add a new test
== Add a new test
Add a new test in the `GreetingResourceTest` class that tests the `/hello/greeting` endpoint by copying this code below the existing test:
@@ -61,7 +62,7 @@ Add a new test in the `GreetingResourceTest` class that tests the `/hello/greeti
}
----
## Test the app
== Test the app
Let's test our app. In Che, using the same command palette, double-click on **Run Quarkus Tests**:
@@ -80,7 +81,7 @@ Expected: is "hello"
This is because you changed the greeting in an earlier step. In `GreetingResource`, change `hola` back to `hello` (in the `GreetingResource` class) and re-run the test and confirm it passes with `BUILD SUCCESS`.
## Controlling the test port
== Controlling the test port
While Quarkus will listen on port `8080` by default, when running tests it defaults to `8081`. This allows you to run tests while having the application running in parallel (which you just did - your app is still running from the previous exercises).
@@ -98,7 +99,7 @@ Now re-run the tests and look for
INFO [io.quarkus] (main) Quarkus x.xx.x started in 1.997s. Listening on: http://[::]:8083
----
## Injecting a URI
== Injecting a URI
It is also possible to directly inject the URL into the test which can make is easy to use a different client. This is done via the `@TestHTTPResource` annotation.
@@ -146,7 +147,7 @@ The `@TestHTTPResource` annotation allows you to directly inject the URL of the
Re-run the tests to ensure they're still passing.
## Injection into tests
== Injection into tests
So far we have only covered integration style tests that test the app via HTTP endpoints, but what if we want to do unit testing and test our beans directly?
@@ -226,7 +227,7 @@ public class TestStereotypeTestCase {
----
====
## Mock support
== Mock support
Quarkus supports the use of mock objects using the CDI `@Alternative` mechanism. To use this simply override the bean you wish to mock with a class in the `src/test/java` directory, and put the `@Alternative` and `@Priority(1)`` annotations on the bean. Alternatively, a convenient `io.quarkus.test.Mock` stereotype annotation could be used. This built-in stereotype declares `@Alternative`, `@Priority(1)` and `@Dependent`.
@@ -276,7 +277,7 @@ Now run the tests again (using the command palette as usual), and watch the outp
This confirms that our `MockGreetingService` is being used instead of the original `GreetingService`.
## Summary
== Congratulations!
In this section we covered basic testing of Quarkus Apps using the `@QuarkusTest` and supporting annotations. This is an important part of any software engineering project and with Quarkus, testing has never been easier. For more information on testing with Quarkus, be sure to review the https://quarkus.io/guides/getting-started-testing[Quarkus Testing Guide].

View File

@@ -1,10 +1,11 @@
## Tracing with MicroProfile OpenTracing
= Tracing with MicroProfile OpenTracing
:experimental:
This exercise shows how your Quarkus application can utilize https://github.com/eclipse/microprofile-opentracing/blob/master/spec/src/main/asciidoc/microprofile-opentracing.asciidoc[Eclipse MicroProfile OpenTracing] to provide distributed tracing for interactive web applications.
In a distributed cloud-native application, multiple microservices are collaborating to deliver the expected functionality. If you have hundreds of services, how do you debug an individual request as it travels through a distributed system? For Java enterprise developers, the Eclipse MicroProfile OpenTracing specification makes it easier. Let's find out how.
## Install Jaeger
== Install Jaeger
https://www.jaegertracing.io/[Jaeger], inspired by Dapper and OpenZipkin, is a distributed tracing system released as open source by Uber Technologies. It is used for monitoring and troubleshooting microservices-based distributed systems, including:
@@ -34,7 +35,7 @@ oc process -f https://raw.githubusercontent.com/jaegertracing/jaeger-openshift/m
Jaeger exposes its collector at different ports for different protocols. Most use the HTTP collector at `jaeger-collector:14268` but other protocols like gRPC are also supported on different ports.
## Add Tracing to Quarkus
== Add Tracing to Quarkus
Like other exercises, we'll need another extension to enable tracing. Install it with:
@@ -45,6 +46,8 @@ mvn quarkus:add-extension -Dextensions="opentracing, rest-client"
This will add the necessary entries in your `pom.xml` to bring in the OpenTracing capability, and anm HTTP REST Client we'll use pater.
== Configure Quarkus
Next, open the `application.properties` file (in the `src/main/resources` directory). Add the following lines to it to configure the default Jaeger tracer in Quarkus:
[source,none,role="copypaste"]
@@ -58,7 +61,7 @@ quarkus.jaeger.endpoint=http://jaeger-collector:14268/api/traces # <3>
<2> How Jaeger samples traces. https://www.jaegertracing.io/docs/1.7/sampling/#client-sampling-configuration[Other options exist] to tune the performance.
<3> This is the default HTTP-based collector exposed by Jaeger
## Test it out
== Test it out
Like many other Quarkus frameworks, sensible defaults and out of the box functionality means you can get immediate value out of Quarkus without changing any code. By default, all JAX-RS endpoints (like our `/hello` and others) are automatically traced. Let's see that in action by re-deploying our traced app.
@@ -69,7 +72,7 @@ First, re-build the app using the command palette and selecting **Create Executa
oc start-build people --from-file target/*-runner.jar --follow
----
## Confirm deployment
== Confirm deployment
Run and wait for the app to complete its rollout:
@@ -78,7 +81,7 @@ Run and wait for the app to complete its rollout:
oc rollout status -w dc/people
----
## Trigger traces
== Trigger traces
You'll need to trigger some HTTP endpoints to generate traces, so let's re-visit our earlier DataTables-powered app. Run this command to open the table with our people database:
@@ -91,7 +94,7 @@ Open that URL in a separate browser tab, and exercise the table a bit by paging
image::paging.png[paging,600]
## Inspect traces
== Inspect traces
Use this command to generate the URL to Jaeger:
@@ -119,13 +122,13 @@ You can see that this trace (along with the others) shows the incoming HTTP GET
Service Mesh technologies like https://istio.io[Istio] can provide even more tracing prowess as the calls across different services are traced at the network level, not requiring _any_ frameworks or developer instrumentation to be enabled for tracing.
====
## Tracing external calls
== Tracing external calls
This exercise showa how to use the https://github.com/eclipse/microprofile-rest-client[MicroProfile REST Client] with Quarkus in order to trace _external_, outbound requests with very little effort.
We will use the publicly available https://swapi.co[Star Wars API] to fetch some characters from the Star Wars universe. Our first order of business is to setup the model we will be using, in the form of a StarWarsPerson POJO.
### Create model
=== Create model
Create a new class in the `org.acme.people.model` package called `StarWarsPerson` with the following content:
@@ -158,7 +161,7 @@ public class StarWarsPerson {
This contains a subset of the full Star Wars model, just enough to demonstrate tracing.
### Create interface
=== Create interface
Using the https://github.com/eclipse/microprofile-rest-client[MicroProfile REST Client] is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations. Create a new Java class in the `org.acme.people.service` package called `StarWarsService` with the following content:
@@ -192,7 +195,7 @@ public interface StarWarsService {
<4> The Star Wars API requires a `User-Agent` header, so with Quarkus we add that with `@ClientHeaderParam`. Other parameters can be added here as needed.
<5> The `getPerson` method gives our code the ability to query the Star Wars API by `id`. The client will handle all the networking and marshalling leaving our code clean of such technical details.
### Configure endppint
=== Configure endppint
In order to determine the base URL to which REST calls will be made, the REST Client uses configuration from `application.properties`. To configure it, add this to your `application.properties` (in `src/main/resource`):
@@ -207,7 +210,7 @@ Note that `org.acme.people.service.StarWarsService` must match the fully qualifi
Using the configuration above, calling the `getPerson(int)` method of StarWarsService with a value of `1` would result in an HTTP GET request being made to `https://swapi.co/api/people/1/` (you can use `curl https://swapi.co/api/people/1/ | jq` to verify this URL produces a real character)
### Final step: add endpoint
=== Final step: add endpoint
We need to `@Inject` an instance of our new `StarWarsService` and call it. Open the existing `PersonResource` class and add the following injected field and method:
@@ -231,7 +234,7 @@ public List<StarWarsPerson> getCharacters() {
<3> For each of the integers, call the `StarWarsService::getPerson` method
<4> Collect the results into a list and return it
## Test it out
== Test it out
First, re-build the app using the command palette and selecting **Create Executable JAR**. Once that's done, run the following command to re-deploy:
@@ -240,7 +243,7 @@ First, re-build the app using the command palette and selecting **Create Executa
oc start-build people --from-file target/*-runner.jar --follow
----
## Confirm deployment
== Confirm deployment
Run and wait for the app to complete its rollout:
@@ -249,7 +252,7 @@ Run and wait for the app to complete its rollout:
oc rollout status -w dc/people
----
## Trigger traces
== Trigger traces
Try out our new endpoint by running the following command:
@@ -286,7 +289,7 @@ You should see:
]
----
## Inspect traces
== Inspect traces
Go back to the Jaeger console tab, and click **Find Traces**. The new trace should appear the top with multiple spans. Click on it to display details:
@@ -296,7 +299,7 @@ You can see that this trace (along with the others) shows multiple spans: the in
image::swpeopleext.png[swpeopleext,800]
## Extra credit: Explicit method tracing
== Extra credit: Explicit method tracing
An annotation is provided to define explicit Span creation. This works on top of the "no-action" setup we did in the previous steps.
@@ -304,7 +307,7 @@ The `@Traced` annotation, applies to a class or a method. When applied to a clas
If you have time after this workshop, add a `@Traced` annotation to some of the other methods and test them out.
## Congratulations!
== Congratulations!
You've seen how to enable automatic tracing for JAX-RS methods as well as create custom tracers for non-JAX-RS methods and external services by using MicroProfile OpenTracing. This specification makes it easy for Quarkus developers to instrument services with distributed tracing for learning, debugging, performance tuning, and general analysis of behavior.