If you are new to QBit. It might make more sense to skim the overview. We suggest reading the landing page of the
QBit Microservices Lib's wiki for a background on QBit. This will let you see the forrest while the tutorials are inspecting the trees. There is also a lot of documents linked to off of the wiki landing page as well as in the footer section of the tutorials.
QBit Microservices Java Lib RESTful and Swaggerific API Gateway Support- Part 3
QBit Microserivces Lib provides three way to remotely talk to microservices out of the box, WebSocket, HTTP REST and the QBit event bus. (Communication is also pluggable so it is easy to send QBit calls or events over a message bus, or other means.)
This tutorial is going to focus on QBit and its REST support. It covers QBit REST support and its support for runtime stats and API Gateway support with Swagger. It just works.
Here is an example program that we are going to examine.
REST API for Microservices
packagecom.mammatustech.todo;
importio.advantageous.qbit.annotation.RequestMapping;
importio.advantageous.qbit.annotation.RequestMethod;
importio.advantageous.qbit.annotation.RequestParam;
importjava.util.*;
@RequestMapping(value ="/todo-service", description ="Todo service")
publicclassTodoService {
privatefinalMap<String, Todo> todoMap =newTreeMap<>();
@RequestMapping(value="/todo", method=RequestMethod.POST,
description="add a todo item to the list", summary="adds todo",
returnDescription="returns true if successful")
publicbooleanadd(finalTodotodo) {
todoMap.put(todo.getId(), todo);
returntrue;
}
@RequestMapping(value="/todo", method=RequestMethod.DELETE,
description="Deletes an item by id", summary="delete a todo item")
publicvoidremove(@RequestParam(value="id", description="id of Todo item to delete")
finalStringid) {
todoMap.remove(id);
}
@RequestMapping(value="/todo", method=RequestMethod.GET,
description="List all items in the system", summary="list items",
returnDescription="return list of all items in system")
publicList<Todo>list() {
returnnewArrayList<>(todoMap.values());
}
}
Let's start with the addTodo method
.
Add TODO
@RequestMapping(value ="/todo", method =RequestMethod.POST,
description ="add a todo item to the list", summary ="adds todo",
returnDescription ="returns true if successful")
publicboolean add(finalTodo todo) {
todoMap.put(todo.getId(), todo);
returntrue;
}
The
RequestMapping
is inspired from
Spring MVC's REST support. In fact, if you use Spring's annotation, QBit can use it as is.
RequestMapping
annotation
packageio.advantageous.qbit.annotation;
...
/**
* Used to map Service method to URIs in an HTTP like protocol.
* @author Rick Hightower
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
public@interfaceRequestMapping {
/**
* Primary mapping expressed by this annotation.
* For HTTP, this would be the URI. Or part of the URI after the parent URI context
* be it ServletApp Context or some other parent context.
*
* @return a request mapping, URIs really
*/
String[] value() default {};
/**
* HTTP request methods must be:
* GET, or POST or WebSocket.
*
* @return or RequestMethods that are supported by this end point
*/
RequestMethod[] method() default {RequestMethod.GET};
/**
* Used to document endpoint
* @return description
*/
Stringdescription() default "no description";
/**
* Used to document endpoint
* @return description
*/
StringreturnDescription() default "no return description";
/**
* Used to document endpoint
* @return summary
*/
Stringsummary() default "no summary";
}
Both Boon and QBit have the same philosophy on annotations. You don't have to use our annotations. You can create your own and as long as the class name and attributes match, we can use the annotations that you provide. This way if you decide to switch away from Boon or QBit, it is easier and you can keep your annotations.
QBit added
description
,
summary
and
returnDescription
attributes to our
RequestMapping
annotation. QBit does this to capture the meta-data and expose it for API-Gateway style client generation and documentation using tools like
Swagger. QBit is not tied to Swagger. It has its own service meta-data format, but QBit converts its service meta-data to
Swagger format to get access to the wealth of Swagger tools and clients. Swagger, and tools like Swagger, makes documenting your API easy and accessible. QBit fully embraces generating service meta-data and Swagger because it embraces the concepts behind building
Microserivce API Gateways, which is essential part of
Microservices Architecture to support
web and mobile Microservices.
The add method specifies the HTTP method as POST, and the URI is mapped to"/todo"
Add TODO
@RequestMapping(value ="/todo", method =RequestMethod.POST, ...)
publicboolean add(finalTodo todo) {
todoMap.put(todo.getId(), todo);
returntrue;
}
You can specify the HTTP methods POST
, GET
, DELETE
, PUT
, etc. Generally speaking you use GET
to read, POST
to create new, PUT
to update or add to a list, and DELETE
to destroy, delete or remove. It is a bad idea to use a GET to modify something (just in case a tool crawls your web service).
The next method is a DELETE operation which removes a Todo item.
Remove TODO
@RequestMapping(value ="/todo", method =RequestMethod.DELETE...)
publicvoid remove(@RequestParam(value ="id", description ="id of Todo item to delete")
finalString id) {
todoMap.remove(id);
}
Notice that the remove
method also takes an id
of the Todo item, which we want to remove from the map. The @RequestParam
is also modeled after the one from Spring MVC as that is the one that most people are familiar with. It just pulls the id
from a request parameter.
Notice that the method returns
void
. Whenever you provide a method that provides a
void
, it does not wait for the method to return to send a
HTTP response code 202
Accepted. If you want the service to capture any exceptions that might occur and send the exception message, then return anything but
void
from a method call. Returning
void
means fire and forget which is very fast, but does not have a way to notify the client of errors.
Let's show an example that would report errors.
Remove TODO with error handling and a return
@RequestMapping(value ="/todo", method =RequestMethod.DELETE)
publicboolean remove(final @RequestParam("id") String id) {
Todo remove = todoMap.remove(id);
return remove!=null;
}
Briefly, as you know you might have to call a downstream service to save the actual Todo item in Cassandra or a relational database. In this case, you would use a Callback
as follows:
Callback version of remove
@RequestMapping(value ="/todo", method =RequestMethod.DELETE)
publicvoid remove(finalCallback<Boolean> callback,
final @RequestParam("id") String id) {
Todo remove = todoMap.remove(id);
callback.accept(remove!=null);
}
A Callback
allows you to response async to a request so that the method call does not block the IO threads. We will cover callbacks more later in the tutorial series.
Lastly in this example we have list todos.
List Todos
@RequestMapping(value ="/todo", method =RequestMethod.GET... )
publicList<Todo> list() {
returnnewArrayList<>(todoMap.values());
}
Since the list
method is not modifying the Todo items but merely returning them, then we can return the entire list.
Using Callbacks
In general you use a Callback to handle multiple downstream services that may be doing additional processing or IO but in separate services and then in a non-blocking way return the result to the original client.
This service could be rewritten using Callback
s as follows:
REST API with callback for Microservices
packagecom.mammatustech.todo;
importio.advantageous.qbit.annotation.RequestMapping;
importio.advantageous.qbit.annotation.RequestMethod;
importio.advantageous.qbit.annotation.RequestParam;
importio.advantageous.qbit.reactive.Callback;
importjava.util.*;
@RequestMapping("/todo-service")
publicclassTodoService {
privatefinalMap<String, Todo> todoMap =newTreeMap<>();
@RequestMapping(value="/todo", method=RequestMethod.POST)
publicvoidadd(finalCallback<Boolean>callback, finalTodotodo) {
todoMap.put(todo.getId(), todo);
callback.accept(true);
}
@RequestMapping(value="/todo", method=RequestMethod.DELETE)
publicvoidremove(finalCallback<Boolean>callback,
final@RequestParam("id") Stringid) {
Todo remove = todoMap.remove(id);
callback.accept(remove!=null);
}
@RequestMapping(value="/todo", method=RequestMethod.GET)
publicvoidlist(finalCallback<ArrayList<Todo>>callback) {
callback.accept(newArrayList<>(todoMap.values()));
}
}
Again callbacks would make more sense if we were talking to a downstream service that did additional IO as follows:
Callback
@RequestMapping(value ="/todo", method =RequestMethod.POST)
publicvoid add(finalCallback<Boolean> callback, finalTodo todo) {
todoMap.put(todo.getId(), todo);
todoRepo.add(callback, todo);
}
Of course there are a lot more details to cover then this and we will cover them in due course.
TODO Info how to run the example
Running the todo service with gradle is shown as follows:
Running the todo service
$ gradle run
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder forfurther details.
log4j:WARN No appenders could be found forlogger (io.netty.util.internal.logging.InternalLoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig formore info.
Todo Server and Admin Server started
The TODO service has a gradle build file that uses the Gradle application plugin as follows:
group 'qbit-ex'
version '1.0-SNAPSHOT'
apply plugin:'java'
apply plugin:'application'
mainClassName ="com.mammatustech.todo.TodoServiceMain"
compileJava {
sourceCompatibility =1.8
}
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
testCompile group:'junit', name:'junit', version:'4.11'
compile group:'io.advantageous.qbit', name:'qbit-admin', version:'0.9.0.M2'
compile group:'io.advantageous.qbit', name:'qbit-vertx', version:'0.9.0.M2'
}
In the main method that launches the app we setup some meta-data about the service so we can export it later via Swagger.
TodoServiceMain
packagecom.mammatustech.todo;
importio.advantageous.qbit.admin.ManagedServiceBuilder;
importio.advantageous.qbit.meta.builder.ContextMetaBuilder;
publicclassTodoServiceMain {
publicstaticvoidmain(finalString... args) throwsException {
/* Create the ManagedServiceBuilder which manages a clean shutdown, health, stats, etc. */
finalManagedServiceBuilder managedServiceBuilder =
ManagedServiceBuilder.managedServiceBuilder()
.setRootURI("/v1") //Defaults to services
.setPort(8888); //Defaults to 8080 or environment variable PORT
/* Context meta builder to document this endpoint. */
ContextMetaBuilder contextMetaBuilder = managedServiceBuilder.getContextMetaBuilder();
contextMetaBuilder.setContactEmail("lunati-not-real-email@gmail.com");
contextMetaBuilder.setDescription("A great service to show building a todo list");
contextMetaBuilder.setContactURL("http://www.bwbl.lunati/master/of/rodeo");
contextMetaBuilder.setContactName("Buffalo Wild Bill Lunati");
contextMetaBuilder.setLicenseName("Commercial");
contextMetaBuilder.setLicenseURL("http://www.canttouchthis.com");
contextMetaBuilder.setTitle("Todo Title");
contextMetaBuilder.setVersion("47.0");
managedServiceBuilder.getStatsDReplicatorBuilder().setHost("192.168.59.103");
managedServiceBuilder.setEnableStatsD(true);
/* Start the service. */
managedServiceBuilder.addEndpointService(newTodoService()) //Register TodoService
.getEndpointServerBuilder()
.build().startServer();
/* Start the admin builder which exposes health end-points and swagger meta data. */
managedServiceBuilder.getAdminBuilder().build().startServer();
System.out.println("Todo Server and Admin Server started");
}
}
The Todo class is just a POJO.
Todo class
packagecom.mammatustech.todo;
publicclassTodo {
privateString id;
privatefinalString name;
privatefinalString description;
privatefinallong createTime;
publicTodo(Stringname, Stringdescription, longcreateTime) {
this.name = name;
this.description = description;
this.createTime = createTime;
this.id = name +"::"+ createTime;
}
publicStringgetId() {
if (id ==null) {
this.id = name +"::"+ createTime;
}
return id;
}
publicStringgetName() {
return name;
}
publicStringgetDescription() {
return description;
}
publiclonggetCreateTime() {
return createTime;
}
@Override
publicbooleanequals(Objecto) {
if (this== o) returntrue;
if (o ==null|| getClass() != o.getClass()) returnfalse;
Todo todo = (Todo) o;
if (createTime != todo.createTime) returnfalse;
return!(name !=null?!name.equals(todo.name) : todo.name !=null);
}
@Override
publicinthashCode() {
int result = name !=null? name.hashCode() :0;
result =31* result + (int) (createTime ^ (createTime >>>32));
return result;
}
}
The TodoService
is as follows.
TodoService without callbacks
packagecom.mammatustech.todo;
importio.advantageous.qbit.annotation.RequestMapping;
importio.advantageous.qbit.annotation.RequestMethod;
importio.advantageous.qbit.annotation.RequestParam;
importjava.util.*;
/**
* Default port for admin is 7777.
* Default port for main endpoint is 8080.
*
* <pre>
* <code>
*
* Access the service:
*
* $ curl http://localhost:8888/v1/...
*
*
* To see swagger file for this service:
*
* $ curl http://localhost:7777/__admin/meta/
*
* To see health for this service:
*
* $ curl http://localhost:8888/__health
* Returns "ok" if all registered health systems are healthy.
*
* OR if same port endpoint health is disabled then:
*
* $ curl http://localhost:7777/__admin/ok
* Returns "true" if all registered health systems are healthy.
*
*
* A node is a service, service bundle, queue, or server endpoint that is being monitored.
*
* List all service nodes or endpoints
*
* $ curl http://localhost:7777/__admin/all-nodes/
*
*
* List healthy nodes by name:
*
* $ curl http://localhost:7777/__admin/healthy-nodes/
*
* List complete node information:
*
* $ curl http://localhost:7777/__admin/load-nodes/
*
*
* Show service stats and metrics
*
* $ curl http://localhost:8888/__stats/instance
* </code>
* </pre>
*/
@RequestMapping(value ="/todo-service", description ="Todo service")
publicclassTodoService {
privatefinalMap<String, Todo> todoMap =newTreeMap<>();
@RequestMapping(value="/todo", method=RequestMethod.POST,
description="add a todo item to the list", summary="adds todo",
returnDescription="returns true if successful")
publicbooleanadd(finalTodotodo) {
todoMap.put(todo.getId(), todo);
returntrue;
}
@RequestMapping(value="/todo", method=RequestMethod.DELETE,
description="Deletes an item by id", summary="delete a todo item")
publicvoidremove(@RequestParam(value="id", description="id of Todo item to delete")
finalStringid) {
todoMap.remove(id);
}
@RequestMapping(value="/todo", method=RequestMethod.GET,
description="List all items in the system", summary="list items",
returnDescription="return list of all items in system")
publicList<Todo>list() {
returnnewArrayList<>(todoMap.values());
}
}
QBit comes with a lib to easily (and quickly) make REST calls.
Calling todo service from Java using QBit's http client support
packagecom.mammatustech.todo;
importio.advantageous.boon.json.JsonFactory;
importio.advantageous.qbit.http.HTTP;
publicclassHttpClient {
publicstaticvoidmain(finalString... args) throwsException {
for (int index =0; index <100; index++) {
HTTP.postJSON("http://localhost:8888/v1/todo-service/todo",
JsonFactory.toJson(newTodo("name"+ index,
"desc"+ index, System.currentTimeMillis() )));
System.out.print(".");
}
}
}
Swagger generation
You can import the JSON meta data into Swagger.
$ curl http://localhost:7777/__admin/meta/
{
"swagger":"2.0",
"info": {
"title":"application title goes here",
"description":"A great service to show building a todo list",
"contact": {
"name":"Buffalo Wild Bill Lunati",
"url":"http://www.bwbl.lunati/master/of/rodeo",
"email":"lunati-not-real-email@gmail.com"
},
"version":"47.0",
"license": {
"name":"Commercial",
"url":"http://www.canttouchthis.com"
}
},
"host":"localhost:8888",
"basePath":"/v1",
"schemes": [
"http",
"https",
"wss",
"ws"
],
"consumes": [
"application/json"
],
"definitions": {
"Todo": {
"properties": {
"id": {
"type":"string"
},
"name": {
"type":"string"
},
"description": {
"type":"string"
},
"createTime": {
"type":"integer",
"format":"int64"
}
}
}
},
"produces": [
"application/json"
],
"paths": {
"/todo-service/todo": {
"get": {
"operationId":"list",
"summary":"list items",
"description":"List all items in the system",
"produces": [
"application/json"
],
"responses": {
"200": {
"description":"return list of all items in system",
"schema": {
"type":"array",
"items": {
"$ref":"#/definitions/Todo"
}
}
}
}
},
"post": {
"operationId":"add",
"summary":"adds todo",
"description":"add a todo item to the list",
"produces": [
"application/json"
],
"parameters": [
{
"name":"body",
"in":"body",
"required":true,
"schema": {
"$ref":"#/definitions/Todo"
}
}
],
"responses": {
"200": {
"description":"returns true if successful",
"schema": {
"type":"boolean"
}
}
}
},
"delete": {
"operationId":"remove",
"summary":"delete a todo item",
"description":"Deletes an item by id",
"parameters": [
{
"name":"id",
"in":"query",
"description":"id of Todo item to delete",
"type":"string"
}
],
"responses": {
"202": {
"description":"returns success",
"schema": {
"type":"string"
}
}
}
}
}
}
}
The Swagger JSON meta-data can be imported into the swagger editor.
swagger:'2.0'
info:
title:Todo Title
description:A great service to show building a todo list
contact:
name:Buffalo Wild Bill Lunati
url:'http://www.bwbl.lunati/master/of/rodeo'
email:lunati-not-real-email@gmail.com
version:'47.0'
license:
name:Commercial
url:'http://www.canttouchthis.com'
host:'localhost:8888'
basePath:/v1
schemes:
- http
- https
- wss
- ws
consumes:
- application/json
definitions:
Todo:
properties:
id:
type:string
name:
type:string
description:
type:string
createTime:
type:integer
format:int64
produces:
- application/json
paths:
/todo-service/todo:
get:
operationId:list
summary:list items
description:List all items in the system
produces:
- application/json
responses:
'200':
description:return list of all items in system
schema:
type:array
items:
$ref:'#/definitions/Todo'
post:
operationId:add
summary:adds todo
description:add a todo item to the list
produces:
- application/json
parameters:
- name:body
in:body
required:true
schema:
$ref:'#/definitions/Todo'
responses:
'200':
description:returns true if successful
schema:
type:boolean
delete:
operationId:remove
summary:delete a todo item
description:Deletes an item by id
parameters:
- name:id
in:query
description:id of Todo item to delete
type:string
responses:
'202':
description:returns success
schema:
type:string
You can see the description
s, summary
and returnDescription
from the@RequestMapping
and @RequestParam
annotations are exposed in the Swagger generation for documentation.
Using Swagger generated client to talk to TODO service
DefaultApi defaultApi =newDefaultApi();
Todo todo =newTodo();
todo.setDescription("Show demo to group");
todo.setName("Show demo");
todo.setCreateTime(123L);
defaultApi.add(todo);
List<Todo> list = defaultApi.list();
list.forEach(newConsumer<Todo>() {
@Override
publicvoidaccept(Todotodo) {
System.out.println(todo);
}
});
Curl, Stats and health
You can access the service via curl commands.
Getting a list of TODO items using REST curl call
$ curl http://localhost:8888/v1/todo-service/todo
[{"id":"name0::1441040038414","name":"name0","description":"desc0",
"createTime":1441040038414}, ...
You can inquire about the health of its nodes using the admin port.
Using admin port to check Todo services health
$ curl http://localhost:7777/__admin/load-nodes/
[{"name":"TodoService","ttlInMS":10000,"lastCheckIn":1441040291968,"status":"PASS"}]
Remember that this will list all service actors (ServiceQueue
s) andServiceServerEndpoint
s (REST and WebSocket services).
If you are looking for a yes/no answer to health for Nagios or Consul or some load balancer, then you can use.
Health status yes or no?
$ curl http://localhost:8888/__health
"ok"
Of the admin port version of this with:
Health status yes or no? on admin port
$ curl http://localhost:7777/__admin/ok
true
To get the stats (after I ran some load testing):
Getting runtime stats of the TODO microservice
$ curl http://localhost:8888/__stats/instance
Output of getting runtime stats of the TODO microservice
{
"MetricsKV": {
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.free":219638816,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.count.std":0.5,
"todo.title.millenniumfalcon.mammatustech.com.jvm.os.load.level.count":8,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.used.median":21867208,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.seconds.min":60,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.used.std":1023408.06,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.seconds.median":300,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.peak.count":32,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.used.mean":21437416,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.free.std":18688268,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.max":3817865216,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.used.mean":61136300,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.count":31,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.minutes.count":9,
"todo.title.millenniumfalcon.mammatustech.com.jvm.os.load.level.std":1.1659224,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.free.min":173839288,
"todo.title.millenniumfalcon.mammatustech.com.jvm.os.load.level":4,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.minutes.std":2.5819888,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.used.min":19162464,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.minutes":9,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.seconds.mean":300,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.total":257425408,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.started.count":35,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.count.median":32,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.count.count":2,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.started.count.max":35,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.minutes.mean":5,
"todo.title.millenniumfalcon.mammatustech.com.jvm.os.load.level.median":4,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.free.median":191758000,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.seconds.std":154.91933,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.used.max":83586120,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.seconds.count":9,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.count.max":32,
"todo.title.millenniumfalcon.mammatustech.com.jvm.os.load.level.mean":4.125,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.seconds":540,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.used":37786592,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.used.median":65667408,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.used":22026992,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.used.count":10,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.minutes.median":5,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.used.max":22026992,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.count.mean":31.5,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.count.min":31,
"TodoService":1,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.free.max":238262944,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.minutes.min":1,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.free.mean":196289104,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.used.count":10,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.started.count.median":35,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.started.count.min":34,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.used.min":18501664,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.started.count.count":2,
"todo.title.millenniumfalcon.mammatustech.com.jvm.os.load.level.max":6,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.minutes.max":9,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.daemon.count":11,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.free.count":10,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.started.count.std":0.5,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.non.heap.max":-1,
"todo.title.millenniumfalcon.mammatustech.com.jvm.os.load.level.min":2,
"todo.title.millenniumfalcon.mammatustech.com.jvm.up.time.seconds.max":540,
"todo.title.millenniumfalcon.mammatustech.com.jvm.mem.heap.used.std":18688268,
"todo.title.millenniumfalcon.mammatustech.com.jvm.thread.started.count.mean":34.5
},
"MetricsMS": {
"TodoService.callTimeSample": [
167643,
9502
],
"todo.title.millenniumfalcon.mammatustech.com.jvm.gc.collector.ps.scavengecollection.time": [
11,
11
]
},
"MetricsC": {
"todo.title.millenniumfalcon.mammatustech.com.jvm.gc.collector.ps.scavengecollection.count":2,
"TodoService.startBatchCount":175,
"TodoService.receiveCount":260
},
"version":1
}
Remember that you can disable this local collection of stats (read the batteries included tutorial for more information).
Conclusion
We covered how to expose a service by mapping the service and its methods to URIs.
Next up we will show how to create a resourceful REST scheme.