Skip to main content

Do you really need a sledgehammer to crack a nut? Part-2



In part-1, we implemented mpJwt and secured a resource with help of the claim scope present in the JWT token received in the request. In this post, we will make use of other specifications, like mpHealth, mpOpenAPI, and mpMetrics, you can either checkout the source code from branch post-1 and code along or can refer to the final code in branch post-2.


mpHealth


The specification of the used version can be found here, this feature provides a way to check the overall health of an application. The first check is the Liveness check which conforms if the application is running or not and the second check Readiness confirms if the application is ready to serve all requests. The difference here is the second check should provide the confirmation only if all dependencies are available and the application in a state to perform the expected functions. It is also possible to combine both checks into one.

AS per the documentation, the Eclipse MicroProfile Health Check specification defines a single container runtime mechanism for validating the availability and status of a MicroProfile implementation. This is primarily intended as a machine-to-machine (M2M) mechanism for use in containerized environments like cloud providers. Examples of existing specifications from those environments include Cloud Foundry Health Checks and Kubernetes Liveness and Readiness Probes.

Implicit implementation

If we just have the feature (mpHealth-2.2) defined in the server.xml, we get a complimentary general  health check, just run the application as is and try to access the URL showing the below


This just tells that the application is up and running, we would extend this 

Let's add a new class as below 



As per the specification, this adds additional information to the health data, by implementing the HealthCheck interface we override the default behavior, on line 20, we added a check named "live-ness Check" with some payload, on line 22, we invoked method up(), which confirms the successful status and then on line 23, we build this response to return. We annotated the class with @ApplicationScopped which qualifies the class to be a managed bean and the @Liveness annotation tells the implementation to bind this with the health endpoint. 

Accessing the same URL or this, you will something like this 


If you change the invoked method at line 22 to down(), you will see the below on accessing the same URL

IMportant to note here is by doing so the specific check shows the DOWN status, as well as the overall status, is also changed DOWN, as per the definition failure of any single check makes the whole application to be qualified as LIVE

Now let's add one more class to the system as below


AS you can see this is similar to the previous HealtCheck implementation just annotated a different annotation @Readiness with overridden behavior differently to make more sense for readiness definition, if we now access the health endpoint, we would see the below output

We now see that in the overall health check we have two checks, one live-ness check and one readiness check, in this specific case both checks confirm the status as DOWN, and therefore the overall status of the application health is DOWN, we can also access these checks using different endpoints as below




But what if readiness check return UP but liveness check returns DOWN status, the output of health endpoint would change as below


The overall health of the application is still DOWN, the specific endpoints will give you different statuses now, liveness check endpoint would return DOWN but the readiness check endpoint would return UP status. This example was just to explain how these checks perform and contribute to overall application health though this wasn't an appropriate or ideal implementation as per the definitions of Readiness and Liveness as something should not be called ready if it is not live, I am sure you got the point made here.

You can also derive the status of a check based on the specific condition, for example 


In the above example, the status would be derived from the condition specified in the state() method, so we will get below output now


One more thing to mention here is that you can also combine these into one as below


You will see the below output now on accessing the health endpoint

I believe the output should be self-explanatory in terms of the response payload, the only thing to note here is instead of keeping two classed we can make use of CDI to produces both checks.

Note: Pay attention to the import for Produces it's not jax-rs Produces, 😴 once by mistake I did import the wrong one and then waste enough time to figure out the problem 🙈

Now it comes to a specific application that will determine what to put where and how to decide on the different statuses to reflect the overall health of the application. I would suggest determining the liveness based on certain minimum responsible factors\parameters to be available so that the application can function expectedly. On the other hand, the readiness should be based on all responsible factors\parameters to be available so that the application can function expectedly in its completeness. (Like additional checks). You can assume that liveness is based on factors such as the application is up and running, the database is accessible, etc., while the readiness, on the other hand, will have checks like the dependencies (integration service) are LIVE or heap usage is not above the alarming threshold value, etc.


As the last thing to discuss, just imagine if we keep all three classes and the system finds all these classes on the classpath, what would be the behavior? You spot on right, if you think the specification thought about it and handled it gracefully instead of failing rudely, in that case, we would get the output as below


Overall application health status needs no explanation, all checks were considered and included and therefore contributed to overall application health status.


mpOpenAPI


The specification of the used version can be found here, it aims to provide a set of Java interfaces and programming models which allow Java developers to natively produce API contract documents as per OpenAPI v3 specification from their JAX-RS applications. 

Implicit implementation

If you access the URL, you see something like this



Just like the previous feature mpHealth discussed above, this feature also provides some generic implementation as complementary by following some patterns. As you can see that operationId is generated using method names, path by merging ApplicationPath and resource path, response type from the Produce annotation defined on resource class, and some other by reading the metadata from the actual method service the REST request. 

You also get a web interface to host and render this specification and to provide you more enriched UI on URL as below



Just by specifying the feature, we are given these two endpoints one to provide the contract and other to render the contract in a web interface. You can refer to the OpenApi 3 specification for more details about the possible information to be displayed as an API contract for REST services.  As part of our next exercise, we would see how we can make use of MicroProfile to define some more of these

Let's modify the ProtectedController as below


Now if we access the UI, we see now the first part ie the request as below,


Here are some pointers regarding the numbered points in the above picture

1: derived by the @POST annotation at line 25
2: derived by merging ApplicationPath, resource path, and @Path annotation on the method
3: derived from the defined summary in @Operation at line 28 in resource class
4: derived from the defined description in @Operation at line 28 in resource class
5: derived from the defined value in @QueryParam at line 53 in resource class
6: derived from the defined value in @PathParam at line 54 in resource class
7: derived from the declared annotation @RequestBody at line 49 in resource class
8: derived from the declared annotation @Consumes at line 47 in resource class
9: derived from the defined @schema in content @Content at line 52 in resource class
10: derived from the defined description in @RequestBody at line 50 in resource class
 
Let's now see the response documentation



1: derived by the defined responseCode in @APIResponse at line 32 in resource class
2: derived by the defined description in @APIResponse at line 31 in resource class
3: derived from the mediaType in @Content at line 35 in resource class
4: derived from the defined name in @Link at line 34 in resource class
5: derived from the defined description in @Link at line 34 in resource class
6: derived from the defined operationId in @Link at line 34 in resource class
7: derived from the schema implementation in @Content at line 35 in resource class
8: derived from the defined name in @Header at line 33 in resource class
9: derived from the defined name in @Header at line 33 in resource class
10: derived from the defined responseCode in @APIResponse at line 40 in resource class
11: derived from the defined description in @APIResponse at line 39 in resource class
12: derived from the defined name in @Link at line 42 in resource class
13: derived from the defined description in @Link at line 42 in resource class
14: derived from the defined operationId in @Link at line 42 in resource class
15: derived from the mediaType in @Content at line 43 in resource class
16: derived from the Error schema implementation in @Content at line 43 in resource class
17: derived from the defined name in @Header at line 41 in resource class
18: derived from the defined name in @Header at line 41 in resource class

Let's see now how to define the configurable servers and security schemes, 


Here are some of the points to note

Line 20: Defined the basic information about the API like title, description, version, and contact information
Line 21: Some additional metadata (tags) for classification 
Line 22: Defined the application-level security requirements, along with scopes
Line 23-39: Defined the security scheme of type OAUTH2 and defined the flow with all required details
Line 40-58: Defined the servers, the first one is the server where we defined 2 variables each having 2 possible values and one default. The second server represents a way to generate the URL where variable replacement is not just enough.

This would produce something like this




The last important point about this specification is that it doesn't restrict you to only and always generate the API contracts on the fly by the code. If you also prefer the code by having a contract first and keeping that a static resource, that can be done easily by doing the following 

  1. Define the property (mp.openapi.scan.disable) with value as "true" in src/main/webapp/META-INF/microprofile-config.properties
  2. Copy your API contract in src/main/webapp/META-INF/openapi.yaml
Please note that the name and location of the API contract in step should be exactly the same otherwise it would be ignored

mpMetrics


The specification of the used version can be found here, it aims to provide a set of metrics that can be consumed later by various data collectors like Prometheus to provide some time-series data, which in result can be consumed by the data visualization tools like Grafana. This is something that one would like to check beyond the basic health check and want to monitor on live applications. In this last part of this post, we would look into that aspect and see what all is offered by MicroProfile

Implicit implementation

As we defined the feature (mpMetrics) in the server.xml, we get some complementary implicit implementation that can be verified by accessing the URL, that should look similar to the below one 



These are the default metrics what the system generates if the server is configured to have the mpMetrics feature installed. These metrics are categorized into 3 different types named as base, vendor, and application, and the metrics data is exposed in the OpenMetrics text format, version 0.0.4 as described in OpenMetrics text format

As per the documentation, 

Base metrics is a list of metrics that all vendors need to implement. Optional base metrics are recommended to be implemented but are not required. These metrics are exposed under /metrics/base

Vendor metrics are the ones, MicroProfile server implementors choose to supply their specific metrics data on top of the basic set of required metrics. Vendor-specific metrics are exposed under /metrics/vendor

Application metrics are the ones that are baked into the server as they are supposed to be provided by the application at runtime. Application-specific metrics are exposed under /metrics/application

Let's now try to use one of the annotations provided by MicroProfile to track the count of how many times the method is called. In ProtectedController resource class add annotation @Counted as per below


Now let's be a little impatient and try to access the URL, surprised? It's ok if you do not see anything this is actually expected as the method is not being invoked yet and hence we do not have any statistics, so, get a token and call the service, and then access the URL, you should see the below

If we call the service again this counter would increase. The tags are used just to organize the different counters in some categories if required. 

Let modify the same resource method as below


On accessing the URL for the application we should see something similar to the one below


These metrics are really useful for the live application running on production and one needs to generate some visual dashboards for monitoring and alerting purposes. The data collector can scrap the real-time data from running applications and a visualization tool can feed that data into live statistics visual graph or gauge. I cannot tell you how crucial this type of report or data can be when you encounter some issues on prod, it can give a quick glance of the current snapshot or for the past period of time in no time. 

I remember an incident when a bank reported that they are seeing outages during the month-end time, the systems were deployed in the client's environment and we had to finally visit the client premises to collect the data based on what investigation can be started. Because of the nature and confidentiality of the data, it was a super restricted area and we were only allowed to see very limited information, I felt like even those people were keeping the records of our mouse clicks 😆, jokes apart we used the JMX console and collected the data and believe me it was not a pleasant experience, especially for a person who just would have joined the support team. By then if we would have the tools that we have nowadays our life would have become a lot easier.

mpConfig

We did not specify this feature specifically and the reason is that it is one of the dependencies of one of the other features and so installed by default. The specification of the used version can be found here, this specification defines an easy-to-use and flexible system for application configuration. It also defines ways to extend the configuration mechanism itself via an SPI (Service Provider Interface) in a portable fashion.

There are different levels of configs, in this section, we will go through those in detail. Internally, the core MicroProfile Config mechanism is purely String/String based. Type-safety is intentionally only provided on top of that by using the proper Converters before handing the value out to the caller. The configuration key might use dot-separated blocks to prevent name conflicts, similar to Java package namespacing. Let's define a config property in microprofile-config.properties file as below


And updated ProtectedController as below


On line 73, we get the config object programmatically, and on line 75, we retrieved the value of the property. If we now call the method we would be able to see the value of the property.

Another way of getting the Config object is via CDI, let's update the ProtectedController as below



On lines 67-68, we injected the Config object, and on line 80, we retrieved the value in the same way as before, the response of service print the fetched value. 

Let's see now another more convenient way to fetch the config property, update the ProtectedController as below


On lines 70-72, instead of the Config object this time we injected the value of the property itself, and it also gives the same result. 

Let's now see how the properties can be overridden depending on the position they are defined.

Case 1: config property defined only in @ConfigProperty using the default value, as below


The response, in this case, would be as 

I am accessible: config_property_annotation

Case 2: now add this property in microprofile-config.properties as below


Response, in this case, would be

I am accessible: microprofile-config-properties

Case 3: now create a file jvm.options in src/main/liberty/config, and add the property as below


The output would be now as below

I am accessible: jvm-option

Case 4: add the following line in server.xml 

<variable name="property.location" value="server-xml"/>

The output now would be 

I am accessible: server-xml

Case5: update the server.xml as below


Output, in this case, would be the following

I am accessible: web-application-app-property

Below is the pic from the open liberty guide which explains it really well, check the page for more details 




It's time to finish this post, I hope I could add few drops to your knowledge sea, the working code having all the discussed concepts can be found at GitHub on branch post-2, checkout using the below command

git clone -b post-2 git@github.com:manish2aug/minor-consent.git

Also, feel free to refer to the fully functional production code on the Main branch, checkout using the below command

git clone git@github.com:manish2aug/minor-consent.git

Thank you for your time to read!

Comments

Popular posts from this blog

Do you really need a sledgehammer to crack a nut?

Intended audience: Developers Read time : 30 min Requirement : A reusable component required to record and retrieve consents for minors  In nutshell, we required an independently deployable service to record and query consent for a minor to or from a DB which would later be consumed in registration and other app access requests. Service also requires to implement authentication and RBAC security (as per configured scope) with JWT authentication as all other integrations are secured and implemented SSO using OpenID connect protocol.  In Dec 2020, we got this requirement, fortunately, we had the option to choose from available frameworks. Following parameters were set to pick the most suited one The size and scope of the application The nature of complexity in the application Objective achievement with shippable dependencies vs provided by the runtime Testability Development effort Then the requirement at hand was evaluated based on these parameters, and we came up with below  The scope