MicroProfile 1.2 Conference App Demo

This blog talks about running an updated version of the MicroProfile 1.2 based conference demo application on a local minishift cluster along with the latest Wildfly-Swarm 2018.2.0 supporting MicroProfile 1.2. You can find a PDF version of this blog along with a video walkthrough at: Release MicroProfile 1.2 Features Companion Demo - Minishift/WildFly Swarm 2018.2.0 · MicroProfileJWT/microprofile-confe…

 

Prerequisites

  1. Install the Minishift binary

  2. Install VirtualBox if needed

  3. Clone the https://github.com/MicroProfileJWT/microprofile-conference.git project

  4. cd microprofile-conference

  5. Start minishift using the config-minishift.sh script in the microprofile-conference root directory

  6. Configure your environment:

[starksm64-microprofile-conference 524]$ eval $(minishift oc-env) [starksm64-microprofile-conference 525]$ type oc oc is /Users/starksm/.minishift/cache/oc/v3.7.1/darwin/oc [starksm64-microprofile-conference 526]$ eval $(minishift docker-env) [starksm64-microprofile-conference 1568]$ oc login $(minishift ip):8443 -u admin -p admin Login successful.  You have access to the following projects and can switch between them with 'oc project <projectname>':      default     kube-public     kube-system   * myproject     openshift     openshift-infra     openshift-node  Using project "myproject".
  1. open the minishift console uisng minishift console

Build and Deploy the Microservices

  1. Build the services:

[starksm64-microprofile-conference 1559]$ mvn clean install -DskipTests=true [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Build Order: [INFO] [INFO] Conference [INFO] Conference :: Bootstrap Data [INFO] Conference :: Authorization [INFO] Conference :: Session [INFO] Conference :: Vote [INFO] Conference :: Speaker [INFO] Conference :: Schedule [INFO] Conference :: Web [INFO] Conference :: Start ... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] Conference ......................................... SUCCESS [  0.693 s] [INFO] Conference :: Bootstrap Data ....................... SUCCESS [  2.593 s] [INFO] Conference :: Authorization ........................ SUCCESS [ 12.907 s] [INFO] Conference :: Session .............................. SUCCESS [  8.802 s] [INFO] Conference :: Vote ................................. SUCCESS [ 12.265 s] [INFO] Conference :: Speaker .............................. SUCCESS [  9.020 s] [INFO] Conference :: Schedule ............................. SUCCESS [ 15.670 s] [INFO] Conference :: Web .................................. SUCCESS [ 33.957 s] [INFO] Conference :: Start ................................ SUCCESS [  0.032 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:36 min [INFO] Finished at: 2018-02-16T00:00:40-08:00 [INFO] Final Memory: 112M/1154M [INFO] ------------------------------------------------------------------------
  1. Install the services into the minishift environment using the cloud-deploy.sh script:

[starksm64-microprofile-conference 1571]$ ./cloud-deploy.sh [INFO] Scanning for projects... [INFO] ... deployment "microservice-vote" created service "microservice-vote" created route "microservice-vote" exposed [starksm64-microprofile-conference 1572]$ oc status In project My Project (myproject) on server https://192.168.99.100:8443  http://microservice-authz-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-authz)   pod/microservice-authz-3124937629-wl8g7 runs example/microservice-authz:latest  http://microservice-schedule-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-schedule)   pod/microservice-schedule-3040366544-n82zt runs example/microservice-schedule:latest  http://microservice-session-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-session)   pod/microservice-session-1164112827-r8z9r runs example/microservice-session:latest  http://microservice-speaker-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-speaker)   pod/microservice-speaker-2311407995-4mt9p runs example/microservice-speaker:latest  http://microservice-vote-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-vote)   pod/microservice-vote-2774736211-wzzhz runs example/microservice-vote:latest  View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'. [starksm64-microprofile-conference 1573]$
  1. Update the web-application/src/main/local/webapp/WEB-INF/conference.properties service URLs to use the value for minishift ip in your environment. In my environment 192.168.99.100 is the IP address. Globally replace 192.168.99.100 with whatever is returned in your minishift setup.

  2. Run the web application front end

mvn package tomee:run -pl :web-application -DskipTests [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Conference :: Web 1.0.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] ... miTargets] to [true] as the property does not exist. INFO - Starting ProtocolHandler [http-nio-8080] INFO - Starting ProtocolHandler [ajp-nio-8009] INFO - Server startup in 3177 ms
  1. Open the web application http://localhost:8080/

Code Walkthrough

In this section we take a look at the code behind the Microprofile features in use in the conference application.

MP-JWT

The JWT RBAC for MicroProfile(MP-JWT) feature defines how JSON web tokens(JWT) may be used for authentication and role based authorization. The MP-JWT feature also defines an API for accessing the claims associated with JWTs. In the conference application demo, the microservice-session uses the JWT groups claim and a custom application claim. The following code snippet demonstrates the MP-JWT API.

Code from: microservice-session/src/main/java/io/microprofile/showcase/session/SessionResource.java
import org.eclipse.microprofile.jwt.JsonWebToken;   @ApplicationScoped public class SessionResource {       /**      * The current MP-JWT for the authenticated user      */     @Inject     JsonWebToken jwt; ...      @GET     @Produces(MediaType.APPLICATION_JSON)     @Timed     public Collection<Session> allSessions(@Context SecurityContext securityContext) throws Exception {         requestCount.inc();         if (jwt == null) { 
            // User was not authenticated             System.out.printf("allSessions, no token\n");             return Collections.emptyList();         }         String userName = jwt.getName();         // Use the isUserInRole of container to check for VIP role in the JWT groups claim         boolean isVIP = securityContext.isUserInRole("VIP");
        System.out.printf("allSessions(%s), isVIP=%s, User token: %s\n", userName, isVIP, jwt);         // Check if the user has a session_time_preference custom claim in the token         Optional<String> sessionTimePref = jwt.claim("session_time_preference");
        if(sessionTimePref.isPresent()) {             // Create a session filter for the time preference...         }          // If the user does NOT have a VIP role, filter out the VIP sessions         Collection<Session> sessions;         if (!isVIP) {
            sessions = sessionStore.getSessions()                 .stream()                 .filter(session -> !session.isVIPOnly())                 .collect(Collectors.toList());         } else {             sessions = sessionStore.getSessions();         }         return sessions;     }
1Injection of the MP-JWT token as a JsonWebToken interface.
2If there is no token, return an empty collection of sessions
3Check for a VIP role in the token using the container’s isUserInRole(String) method. This internally maps to the token’s MP-JWT defined group claims.
4Illustrates programatic lookup of a custom claim not defined by the MP-JWT spec. In this example the session results would be filtered to only return those matching the session time of day preference.
5This code block makes a check of the incoming MP-JWT token to see if it has a group claim that contains the VIP value. If the VIP claim does not exist, the sessions are filtered to remove those that the isVIPOnly property. Otherwise, all sessions are returned if the token has the VIP group.

MP-Configuration

The Microprofile config(MP-Config) supports injection and programtic lookup of external configuration information via a common API. The MP-Config spec defines 3 common configuration sources: * System environment variables * System properties * A META-INF/microprofile-config.properties

SPIs are defined for adding configuration sources as well as for converting from string to arbitrary types.

The microprofile conference app makes use of injection of META-INF/microprofile-config.properties, environment variables, and the conversion SPI. The first code snippet we will look at injects a value from the bundled META-INF/microprofile-config.properties as a java.security.PrivateKey. The AuthzResource from the microservice-authz project shows the injection:

Code from: microservice-authz/src/main/java/io/microprofile/showcase/tokens/AuthzResource.java,PrivateKeyConverter.java
import java.security.PrivateKey;  @ApplicationScoped public class AuthzResource {  /**  * An example of injecting a custom property type  */ @ConfigProperty(name="authz.signingKey") @Inject private PrivateKey signingKey; ...
# The META-INF/microprofile-config.properties entries authz.signingKey=/privateKey.pem 
import java.security.PrivateKey;  import org.eclipse.microprofile.config.spi.Converter;  import static io.microprofile.showcase.tokens.TokenUtils.readPrivateKey;  /**  * A custom configuration converter for {@linkplain PrivateKey} injection using  * {@linkplain org.eclipse.microprofile.config.inject.ConfigProperty}  */ public class PrivateKeyConverter implements Converter<PrivateKey> { 
    /**      * Converts a string to a PrivateKey by loading it as a classpath resource      * @param s - the string value to convert      * @return the PrivateKey loaded as a resource      * @throws IllegalArgumentException - on failure to load the key      */     @Override     public PrivateKey convert(String s) throws IllegalArgumentException {          PrivateKey pk = null;         try {             pk = readPrivateKey(s);         } catch (Exception e) {             IllegalArgumentException ex = new IllegalArgumentException("Failed to parse ");             ex.initCause(e);             throw ex;         }         return pk;     } }
1The config property name reference to match against a config source.
2The custom value PrivateKey value injection site.
3The mapping from the referenced "authz.signingKey" name to a string value in the standard META-INF/microprofile-config.properties.
4The custom converter implementation that takes the input string value and transforms it into a PrivateKey by loading it as a resource from the classpath.

A further example usage of the MP-Config will be seen in the next section on the health check feature.

MP-Health

The Microprofile health check(MP-Health) feature allows on to define application health check endpoints as commonly used in cloud environment to validate avaiability and liveness. The MP-Health feature supports this along with an ability to define a JSON payload that can be used to convey additional information.

The following microservice-session MP-Health code snippet shows an example health implementation that makes use of the MP-Config API to inject configuration that is used during construction the health response.

Code from: microservice-session/src/main/java/io/microprofile/showcase/session/SessionCheck.java
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.health.Health; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse;  @Health 
@ApplicationScoped public class SessionCheck implements HealthCheck {
    @Inject     private SessionStore sessionStore;     @Inject     @ConfigProperty(name = "sessionCountName", defaultValue = "sessionCount")
    private String sessionCountName;     @ConfigProperty(name = "JAR_SHA256")
    @Inject     private String jarSha256;      @Override     public HealthCheckResponse call() {
        return HealthCheckResponse.named("sessions-check")             .withData(sessionCountName, sessionStore.getSessions().size())
            .withData("lastCheckDate", new Date().toString())             .withData("jarSHA256", jarSha256)             .up()             .build();     } }
1The annotation marking the bean as a health check endpoint.
2The HealthCheck interface the endpoint implements to provide the health callback.
3An example of externalizing a data label used in health check response whose value is defined in the application META-INF/microprofile-config.properties.
4An example of injection of a config value whose source is an environment variable that is defined in the microservice-session openshift deployment descriptor.
5The HealthCheck call endpoint that returns the HealthCheckResponse.
6The various withData calls add labelled values, including the injected config values, to the JSON payload.

MP-Metrics

The Microprofile metrics(MP-Metrics) feature aims to provide a unified way for Microprofile services to export Monitoring data via common API.

Code from: microservice-session/src/main/java/io/microprofile/showcase/session/SessionResource.java
import org.eclipse.microprofile.metrics.Counter; import org.eclipse.microprofile.metrics.Histogram; import org.eclipse.microprofile.metrics.Metadata; import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.MetricType; import org.eclipse.microprofile.metrics.annotation.Metric; import org.eclipse.microprofile.metrics.annotation.Timed;  @ApplicationScoped public class SessionResource {      @Inject     @Metric(name = "requestCount", description = "All JAX-RS request made to the SessionResource",         displayName = "SessionResource#requestCount") 
    private Counter requestCount;      /**      * The application metrics registry that allows access to any metric to be accessed/created      */     @Inject     private MetricRegistry metrics;     @PostConstruct     void init() {         Collection<Session> sessions = sessionStore.getSessions();         System.out.printf("SessionResource.init, session count=%d\n", sessions.size());         // Create a histogram of the session abstract word counts         Metadata metadata = new Metadata(SessionResource.class.getName()+".abstractWordCount", MetricType.HISTOGRAM);
        metadata.setDescription("Word count histogram for the session abstracts");         Histogram abstractWordCount = metrics.histogram(metadata);
        for(Session session : sessions) {             String[] words = session.getAbstract().split("\\s+");             abstractWordCount.update(words.length);
        }     }      @GET     @Produces(MediaType.APPLICATION_JSON)     @Timed    public Collection<Session> allSessions(@Context SecurityContext securityContext) throws Exception {         requestCount.inc();...     }      @GET     @Path("/{sessionId}")     @Produces(MediaType.APPLICATION_JSON)     @Timed(6)    public Response retrieveSession(@PathParam("sessionId") final String sessionId) throws Exception {         requestCount.inc();...     }       @GET     @Path("/{sessionId}/speakers")     @Produces(MediaType.APPLICATION_JSON)     @Timed    public Response sessionSpeakers(@PathParam("sessionId") final String sessionId) throws Exception { ...     }
1Define a Counter type metric named requestCount.
2Injection of the MetricRegistry interface allows for programmatic creation and lookup of metrics as will be done in init().
3Sets up the metadata for an abstractWordCount metric of type Histogram.
4The actual creation of the Histogram metric via the injected MetricRegistry instance.
5Population of the abstractWordCount from the various session abstracts.
6The allSessions, retrieveSession and sessionSpeakers endpoint methods are annotated with @Timed to indicate that the MP-Metrics layer should intercept the method invocations and create statistics for them.
7Programmatic updates of the injected requestCount metric are seen in the allSessions and retrieveSession endpoint methods.
Last updated 2018-02-22 08:48:28 PST