Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Microprofile (updated) #117

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,25 @@ To build this project:
./gradlew build
docker build -t gameontext/gameon-map map-wlpcfg

## [MicroProfile](https://microprofile.io/)
MicroProfile is an open platform that optimizes the Enterprise Java for microservices architecture. In this application, we are using [**MicroProfile 1.3**](https://github.com/eclipse/microprofile-bom).

### Features
1. [MicroProfile Metrics](https://github.com/eclipse/microprofile-metrics) - This feature allows us to expose telemetry data. Using this, developers can monitor their services with the help of metrics.

The application uses the `Timed`, `Counted` and `Metered` metrics. To access these metrics, go to https://localhost:9447/metrics.
The Metrics feature is configured with SSL and can only be accessed through https. You will need to login using the username and password configured in the server.xml. The default values are `admin` and `admin`.

2. [MicroProfile Health Check](https://github.com/eclipse/microprofile-health) - This feature helps us to determine the status of the service as well as its availability. This can be checked by accessing the `/health` endpoint.

3. [MicroProfile Fault Tolerance](https://github.com/eclipse/microprofile-fault-tolerance) - These features help reduce the impact of failure and ensure continued operation of services. This project uses Fallback, Retry, and Timeout.

4. [MicroProfile OpenAPI](https://github.com/eclipse/microprofile-open-api) - This feature, built on Swagger, provides a set of Java interfaces and programming models that allow Java developers to natively produce OpenAPI v3 documents from their JAX-RS applications.

This project uses Swagger and JAX-RS annotations to document the application endpoints. To view the API docs, go to https://localhost:9447/openapi/ui/.

## Contributing

Want to help! Pile On!
Want to help! Pile On!

[Contributing to Game On!](CONTRIBUTING.md)
27 changes: 12 additions & 15 deletions map-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@ apply plugin: 'jacoco'
sourceCompatibility = 1.8

repositories {
mavenCentral()
maven { url = "http://repo.maven.apache.org/maven2" }
maven { url "https://jitpack.io" }
}

configurations {
jmockit
testCompile.extendsFrom jmockit
}


dependencies {
compile 'com.github.gameontext:signed:v1.0.3'

Expand All @@ -29,7 +23,7 @@ dependencies {

// the entire couch & jackson dependency collection will not
// makes a mess with slf4j and jackson..

providedCompile group: 'org.eclipse.microprofile', name: 'microprofile', version: '1.3'
compile ('org.apache.httpcomponents:httpclient:4.5.2') {
exclude group: 'com.fasterxml.jackson'
}
Expand Down Expand Up @@ -63,29 +57,32 @@ dependencies {
// hystrix
compile group:'com.netflix.hystrix', name:'hystrix-core', version:'1.5.8'

jmockit 'org.jmockit:jmockit:1.41'
// https://mvnrepository.com/artifact/org.jmockit/jmockit
testCompile group: 'org.jmockit', name: 'jmockit', version: '1.41'

testCompile 'junit:junit:4.12'
testCompile 'org.glassfish:javax.json:1.0.4'
testRuntime 'org.apache.cxf:cxf-rt-rs-client:3.1.1'
testRuntime 'org.apache.httpcomponents:httpclient:4.5.2'
}

test {
jvmArgs "-javaagent:${configurations.jmockit.find { it.name.startsWith("jmockit") }.absolutePath}"
jvmArgs "-javaagent:${classpath.find { it.name.contains("jmockit") }.absolutePath}"
}

sourceSets {
endToEndTest {
endToEnd {
java.srcDir file('/src/endToEnd/java')
compileClasspath += sourceSets.main.output + sourceSets.test.output
}
}

task endToEndTest(type: Test) {
testClassesDir = sourceSets.endToEndTest.output.classesDir
classpath = sourceSets.endToEndTest.runtimeClasspath
task endToEnd(type: Test) {
testClassesDir = sourceSets.endToEnd.output.classesDir
classpath = sourceSets.endToEnd.compileClasspath
}

check.dependsOn(endToEndTest)
check.dependsOn(endToEnd)


// Keep test and application binaries separate
Expand Down
53 changes: 53 additions & 0 deletions map-app/src/main/java/org/gameontext/map/MapHealth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.gameontext.map;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import org.eclipse.microprofile.health.Health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
import org.gameontext.map.auth.PlayerClient;
import org.gameontext.map.db.MapRepository;
import org.gameontext.map.kafka.Kafka;

@Health
@ApplicationScoped
public class MapHealth implements HealthCheck {

@Inject
protected MapRepository mapRepository;

@Inject
Kafka kafka;

@Inject
PlayerClient playerClient;

@Override
public HealthCheckResponse call() {

HealthCheckResponseBuilder builder = HealthCheckResponse.named(MapResource.class.getSimpleName())
.withData("mapRepository", mapRepository.connectionReady() ? "available" : "down")
.withData("playerClient", playerClient.isHealthy() ? "available" : "down")
.withData("kafka", kafka.isHealthy() ? "available" : "down");

if ( mapRepositoryReady() && playerClientReady() && kafkaReady() ) {
return builder.up().build();
}

return builder.down().build();
}

private boolean mapRepositoryReady() {
return mapRepository != null && mapRepository.connectionReady();
}

private boolean playerClientReady() {
return playerClient != null && playerClient.isHealthy();
}

private boolean kafkaReady() {
return kafka != null && kafka.isHealthy();
}
}
15 changes: 8 additions & 7 deletions map-app/src/main/java/org/gameontext/map/MapResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import javax.ws.rs.core.Response.Status;

import org.gameontext.map.auth.PlayerClient;
import org.gameontext.map.db.CouchDbHealth;
import org.gameontext.map.db.MapRepository;
import org.gameontext.map.kafka.Kafka;

Expand All @@ -38,7 +39,7 @@
public class MapResource {

@Inject
protected MapRepository mapRepository;
protected CouchDbHealth dbHealth;

@Inject
Kafka kafka;
Expand All @@ -60,16 +61,16 @@ public Response basicGet() {
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value="health check", hidden = true)
public Response healthCheck() {
if ( mapRepository != null && mapRepository.connectionReady()
&& playerClient != null && playerClient.isHealthy()
&& kafka != null && kafka.isHealthy() ) {
if ( dbHealth.isHealthy()
&& playerClient.isHealthy()
&& kafka.isHealthy() ) {
return Response.ok().entity("{\"status\":\"UP\"}").build();
} else {
Map<String,String> map = new HashMap<>();
map.put("status", "DOWN");
map.put("mapRepository", mapRepository == null ? "null" : ""+mapRepository.connectionReady());
map.put("playerClient", playerClient == null ? "null" : ""+playerClient.isHealthy());
map.put("kafka", kafka == null ? "null" : ""+kafka.isHealthy());
map.put("mapRepository", dbHealth.isHealthy()+"");
map.put("playerClient", playerClient.isHealthy()+"");
map.put("kafka", kafka.isHealthy()+"");

return Response.status(Status.SERVICE_UNAVAILABLE).entity(map).build();
}
Expand Down
82 changes: 78 additions & 4 deletions map-app/src/main/java/org/gameontext/map/SitesResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.gameontext.map;

import java.net.URI;
import java.time.temporal.ChronoUnit;
import java.util.List;

import javax.inject.Inject;
Expand All @@ -34,6 +35,16 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;

import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.faulttolerance.Timeout;
import org.eclipse.microprofile.metrics.annotation.Counted;
import org.eclipse.microprofile.metrics.annotation.Metered;
import org.eclipse.microprofile.metrics.annotation.Timed;
import org.eclipse.microprofile.opentracing.Traced;
import org.gameontext.map.auth.ResourceAccessPolicy;
import org.gameontext.map.auth.ResourceAccessPolicyFactory;
import org.gameontext.map.db.MapRepository;
Expand All @@ -42,9 +53,6 @@
import org.gameontext.map.model.Site;
import org.gameontext.signed.SignedRequest;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
Expand Down Expand Up @@ -85,6 +93,20 @@ private enum AuthMode { AUTHENTICATION_REQUIRED, UNAUTHENTICATED_OK };
@ApiResponse(code = HttpServletResponse.SC_NO_CONTENT, message = Messages.NOT_FOUND)
})
@Produces(MediaType.APPLICATION_JSON)
@Timed(name = "listAll_timer",
reusable = true,
tags = "label=listAll")
@Counted(name = "listAll_count",
monotonic = true,
reusable = true,
tags = "label=listAll")
@Metered(name = "listAll_meter",
reusable = true,
tags = "label=listAll")
@Traced(value = true, operationName = "SitesResource.listAll")
@Fallback(fallbackMethod="listAllFallback")
@Timeout(value = 2, unit = ChronoUnit.SECONDS)
@Retry(maxRetries = 2, maxDuration= 10000)
public Response listAll(
@ApiParam(value = "filter by owner") @QueryParam("owner") String owner,
@ApiParam(value = "filter by name") @QueryParam("name") String name) {
Expand All @@ -103,6 +125,12 @@ public Response listAll(
}
}

public Response listAllFallback(
@ApiParam(value = "filter by owner") @QueryParam("owner") String owner,
@ApiParam(value = "filter by name") @QueryParam("name") String name) {
return Response.noContent().build();
}

/**
* POST /map/v1/sites
* @throws JsonProcessingException
Expand All @@ -124,6 +152,17 @@ public Response listAll(
})
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Timed(name = "createRoom_timer",
reusable = true,
tags = "label=createRoom")
@Counted(name = "createRoom_count",
monotonic = true,
reusable = true,
tags = "label=createRoom")
@Metered(name = "createRoom_meter",
reusable = true,
tags = "label=createRoom")
@Traced(value = true, operationName = "SitesResource.createRoom")
public Response createRoom(
@ApiParam(value = "New room attributes", required = true) RoomInfo newRoom) {

Expand All @@ -148,6 +187,19 @@ public Response createRoom(
@ApiResponse(code = HttpServletResponse.SC_NOT_FOUND, message = Messages.NOT_FOUND, response = ErrorResponse.class)
})
@Produces(MediaType.APPLICATION_JSON)
@Timed(name = "getRoom_timer",
reusable = true,
tags = "label=getRoom")
@Counted(name = "getRoom_count",
monotonic = true,
reusable = true,
tags = "label=getRoom")
@Metered(name = "getRoom_meter",
reusable = true,
tags = "label=getRoom")
@Traced(value = true, operationName = "SitesResource.getRoom")
@Timeout(value = 2, unit = ChronoUnit.SECONDS)
@Retry(maxRetries = 2, maxDuration= 10000)
public Response getRoom(
@ApiParam(value = "target room id", required = true) @PathParam("id") String roomId) {
String authenticatedId = getAuthenticatedId(AuthMode.UNAUTHENTICATED_OK);
Expand Down Expand Up @@ -177,6 +229,17 @@ public Response getRoom(
})
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Timed(name = "updateRoom_timer",
reusable = true,
tags = "label=updateRoom")
@Counted(name = "updateRoom_count",
monotonic = true,
reusable = true,
tags = "label=updateRoom")
@Metered(name = "updateRoom_meter",
reusable = true,
tags = "label=updateRoom")
@Traced(value = true, operationName = "SitesResource.updateRoom")
public Response updateRoom(
@ApiParam(value = "target room id", required = true) @PathParam("id") String roomId,
@ApiParam(value = "Updated room attributes", required = true) RoomInfo roomInfo) {
Expand All @@ -202,6 +265,17 @@ public Response updateRoom(
@ApiResponse(code = HttpServletResponse.SC_NOT_FOUND, message = Messages.NOT_FOUND, response = ErrorResponse.class),
@ApiResponse(code = HttpServletResponse.SC_CONFLICT, message = Messages.CONFLICT, response = ErrorResponse.class)
})
@Timed(name = "deleteRoom_timer",
reusable = true,
tags = "label=deleteRoom")
@Counted(name = "deleteRoom_count",
monotonic = true,
reusable = true,
tags = "label=deleteRoom")
@Metered(name = "deleteRoom_meter",
reusable = true,
tags = "label=deleteRoom")
@Traced(value = true, operationName = "SitesResource.deleteRoom")
public Response deleteRoom(
@ApiParam(value = "target room id", required = true) @PathParam("id") String roomId) {

Expand All @@ -219,7 +293,7 @@ private String getAuthenticatedId(AuthMode mode){
//else we don't allow unauthenticated, so if auth id is absent
//throw exception to prevent handling the request.
throw new MapModificationException(Response.Status.BAD_REQUEST,
"Unauthenticated client", "Room owner could not be determined.");
"Unauthenticated client", "Room owner could not be determined.");
}
break;
}
Expand Down
27 changes: 27 additions & 0 deletions map-app/src/main/java/org/gameontext/map/SwapSitesResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

import org.eclipse.microprofile.metrics.annotation.Timed;
import org.eclipse.microprofile.metrics.annotation.Metered;
import org.eclipse.microprofile.metrics.annotation.Counted;
import org.eclipse.microprofile.opentracing.Traced;

/**
* Root of CRUD operations on or with sites
*/
Expand Down Expand Up @@ -71,6 +76,17 @@ private enum AuthMode { AUTHENTICATION_REQUIRED, UNAUTHENTICATED_OK };
@SignedRequest
@ApiOperation(value="deprecated", hidden=true)
@Produces(MediaType.APPLICATION_JSON)
@Timed(name = "put_swapSites_timer",
reusable = true,
tags = "label=swapSites")
@Counted(name = "put_swapSites_count",
monotonic = true,
reusable = true,
tags = "label=swapSites")
@Metered(name = "put_swapSites_meter",
reusable = true,
tags = "label=swapSites")
@Traced(value = true, operationName = "SwapSitesResource.swapSites")
public Response swapSites(@QueryParam("room1Id") String room1Id,
@QueryParam("room2Id") String room2Id) {

Expand Down Expand Up @@ -103,6 +119,17 @@ public Response swapSites(@QueryParam("room1Id") String room1Id,
})
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Timed(name = "post_swapSites_timer",
reusable = true,
tags = "label=swapSites")
@Counted(name = "post_swapSites_count",
monotonic = true,
reusable = true,
tags = "label=swapSites")
@Metered(name = "post_swapSites_meter",
reusable = true,
tags = "label=swapSites")
@Traced(value = true, operationName = "SwapSitesResource.swapSites")
public Response swapSites(
@ApiParam(value = "Sites to swap", required = true) SiteSwap siteSwap) {

Expand Down
Loading