Quantcast
Channel: Sleepless Dev
Viewing all 213 articles
Browse latest View live

Adapting QBit microservice lib queues to Reactive Streams

$
0
0
At the core of QBit, microservices lib, is the queue. The QBit microservice lib centers aroundqueues and micro-batching. A queue is a stream of messages. Micro-batching is employed to improve throughput by reducing thread handoffs, and improving IO efficiency. You can implement back pressure in QBit. However, reactive streams, has a very clean interface for implementing back pressure. Increasingly APIs for new SQL databases, data grids, messaging, are using some sort of streaming API.
If you are using Apache Spark, or Kafka or Apache Storm or Cassandra, then you are likely already familiar with streaming APIs. Java 8 ships with a streaming API and Java 9 improves on the concepts with Flow. Streams are coming to you one way or another if you are a Java programmer.
There are even attempts to make a base level stream API for compatibility sakes, called Reactive Streams.
Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. This encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols.
Realizing the importance of streaming standards and the ability to integrate into a larger ecosystem, QBit now supports reactive streams and will support it more as time goes on.

QBit endeavors to make more and more things look like QBit queues which is a convenient interface for dealing with IO, interprocess and inter-thread communication with speeds up to 100M TPS to 200M TPS.
We have added two classes to adapt a QBit queue to a stream. Later there will be classes to adapte a stream to a queue. The two classes are: QueueToStreamRoundRobin and QueueToStreamUnicast. The overhead of backpressure support is around 20 to 30%. There are more QBit centric ways of implementing back pressure that will have less overhead, but in the grand scheme of thing this overhead is quite small. QueueToStreamRoundRobin and QueueToStreamUnicast have been clocked between 70M TPS and 80M TPS with the advantage of easily handling resource consumption management so that a fast data source does not overwhelm the stream destination. QueueToStreamRoundRobin allows many reactive stream destinations from the same QBit queue.
Let's see these two in action:

Simple trade class

publicclassTrade {
finalString name;
finallong amount;

privateTrade(Stringname, longamount) {
this.name = name;
this.amount = amount;
}
...
}

Adapting a Queue into a Reactive Stream

finalQueue<Trade> queue =
QueueBuilder
.queueBuilder()
.build();

/* Adapt the queue to a stream. */
finalQueueToStreamUnicast<Trade> stream =
newQueueToStreamUnicast<>(queue);

stream.subscribe(newSubscriber<Trade>() {
privateSubscription subscription;
privateint count;

@Override
publicvoidonSubscribe(Subscriptions) {
this.subscription = s;
s.request(10);
}

@Override
publicvoidonNext(Tradetrade) {

//Do something useful with the trade
count++;
if (count >9) {
count =0;
subscription.request(10);
} else {
count++;
}

}

@Override
publicvoidonError(Throwablet) {
error.set(t);
}

@Override
publicvoidonComplete() {
/* shut down. */
}
});

/* Send some sample trades. */
finalSendQueue<Trade> tradeSendQueue = queue.sendQueue();

for (int index =0; index <20; index++) {
tradeSendQueue.send(newTrade("TESLA", 100L+ index));
}
tradeSendQueue.flushSends();
For the multicast version, here is a simple unit test showing how it works.

Unit test showing how mulitcast works

packageio.advantageous.qbit.stream;

importio.advantageous.boon.core.Sys;
importio.advantageous.qbit.queue.Queue;
importio.advantageous.qbit.queue.QueueBuilder;
importio.advantageous.qbit.queue.SendQueue;
importorg.junit.Test;
importorg.reactivestreams.Subscriber;
importorg.reactivestreams.Subscription;

importjava.util.concurrent.CopyOnWriteArrayList;
importjava.util.concurrent.CountDownLatch;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.atomic.AtomicBoolean;
importjava.util.concurrent.atomic.AtomicReference;

import staticorg.junit.Assert.assertEquals;
import staticorg.junit.Assert.assertNotNull;

publicclassQueueToStreamMulticast {



privateclassTrade {
finalString name;
finallong amount;

privateTrade(Stringname, longamount) {
this.name = name;
this.amount = amount;
}
}

@Test
publicvoidtest() throwsInterruptedException {
finalQueue<Trade> queue =QueueBuilder.queueBuilder().build();

finalQueueToStreamRoundRobin<Trade> stream =newQueueToStreamRoundRobin<>(queue);

finalCopyOnWriteArrayList<Trade> trades =newCopyOnWriteArrayList<>();
finalAtomicBoolean stop =newAtomicBoolean();
finalAtomicReference<Throwable> error =newAtomicReference<>();
finalAtomicReference<Subscription> subscription =newAtomicReference<>();

finalCountDownLatch latch =newCountDownLatch(1);
finalCountDownLatch stopLatch =newCountDownLatch(1);

stream.subscribe(newSubscriber<Trade>() {
@Override
publicvoidonSubscribe(Subscriptions) {
s.request(10);
subscription.set(s);
}

@Override
publicvoidonNext(Tradetrade) {

trades.add(trade);

if (trades.size()==10) {
latch.countDown();
}
}

@Override
publicvoidonError(Throwablet) {
error.set(t);
}

@Override
publicvoidonComplete() {
stop.set(true);
stopLatch.countDown();
}
});

finalSendQueue<Trade> tradeSendQueue = queue.sendQueue();

for (int index =0; index <100; index++) {
tradeSendQueue.send(newTrade("TESLA", 100L+ index));
}
tradeSendQueue.flushSends();
Sys.sleep(100);
latch.await(10, TimeUnit.SECONDS);

assertEquals(10, trades.size());

assertEquals(false, stop.get());

assertNotNull(subscription.get());


subscription.get().cancel();

stopLatch.await(10, TimeUnit.SECONDS);


assertEquals(true, stop.get());

}


@Test
publicvoidtest2Subscribe() throwsInterruptedException {
finalQueue<Trade> queue =QueueBuilder.queueBuilder().setBatchSize(5).build();

finalQueueToStreamRoundRobin<Trade> stream =newQueueToStreamRoundRobin<>(queue);

finalCopyOnWriteArrayList<Trade> trades =newCopyOnWriteArrayList<>();
finalAtomicBoolean stop =newAtomicBoolean();
finalAtomicReference<Throwable> error =newAtomicReference<>();
finalAtomicReference<Subscription> subscription =newAtomicReference<>();

finalCountDownLatch latch =newCountDownLatch(1);
finalCountDownLatch stopLatch =newCountDownLatch(1);

stream.subscribe(newSubscriber<Trade>() {
@Override
publicvoidonSubscribe(Subscriptions) {
s.request(10);
subscription.set(s);
}

@Override
publicvoidonNext(Tradetrade) {

trades.add(trade);

if (trades.size()==20) {
latch.countDown();
}
}

@Override
publicvoidonError(Throwablet) {
error.set(t);
}

@Override
publicvoidonComplete() {
stop.set(true);
stopLatch.countDown();
}
});


stream.subscribe(newSubscriber<Trade>() {
@Override
publicvoidonSubscribe(Subscriptions) {
s.request(10);
subscription.set(s);
}

@Override
publicvoidonNext(Tradetrade) {

trades.add(trade);

if (trades.size()==20) {
latch.countDown();
}
}

@Override
publicvoidonError(Throwablet) {
error.set(t);
}

@Override
publicvoidonComplete() {
stop.set(true);
stopLatch.countDown();
}
});

finalSendQueue<Trade> tradeSendQueue = queue.sendQueue();

for (int index =0; index <40; index++) {
tradeSendQueue.send(newTrade("TESLA", 100L+ index));
}
tradeSendQueue.flushSends();
Sys.sleep(100);
latch.await(10, TimeUnit.SECONDS);

assertEquals(20, trades.size());

assertEquals(false, stop.get());

assertNotNull(subscription.get());


subscription.get().cancel();

stopLatch.await(10, TimeUnit.SECONDS);


assertEquals(true, stop.get());

}
}

QBit has support for a Queue API that works over Kafka, JMS, WebSocket and in-memory queues. You can now use reactive streams with these queues and the performance is very good.

WebSocket microservice vs REST microservice

$
0
0

WebSocket microservice vs REST microservice


With WebSocket, you are connecting to a server and sending requests what the server sends responses. You can essentially stream calls. How does this compare with REST which uses straight HTTP? WebSocket has 8x to 20x more throughput and uses less resources.
Let's demonstrate, for this test I will run everything on my MacBook pro. I have made no tweaks to the TCP/IP OS stack (but you can expect slightly higher throughput if you do).
To test the REST code we will use wrk. The wrk load tester is capable of generating significant load when run on a single multi-core CPU. The wrk load tester uses multithreaded and event notification systems such as epoll and kqueue to maximize the number of HTTP requests per second.
The REST service code:

REST service code

packageio.advantageous.qbit.example.perf.websocket;

importio.advantageous.qbit.admin.ManagedServiceBuilder;
importio.advantageous.qbit.annotation.RequestMapping;
importio.advantageous.qbit.annotation.http.GET;
importio.advantageous.qbit.annotation.http.PUT;
import staticio.advantageous.qbit.admin.ManagedServiceBuilder.managedServiceBuilder;

/**
* curl -H "Content-Type: application/json" -X PUT http://localhost:8080/trade -d '{"name":"ibm", "amount":1}'
* curl http://localhost:8080/count
*/
@RequestMapping("/")
publicclassTradeService {

privatelong count;

@PUT("/trade")
publicbooleantrade(finalTradetrade) {
trade.getName().hashCode();
trade.getAmount();
count++;
returntrue;
}

@GET("/count")
publiclongcount() {
return count;
}

publicstaticvoidmain(finalString... args) {

finalManagedServiceBuilder managedServiceBuilder = managedServiceBuilder();

managedServiceBuilder
.addEndpointService(newTradeService())
.setRootURI("/");

managedServiceBuilder.startApplication();
}
}
To test this with wrk, we need a Lua script to run the PUT operations.
wrk.method="PUT"
wrk.body='{"name":"ibm", "amount":1}'
wrk.headers["Content-Type"] ="application/json"
Throughput

100 connections 70K TPS

$ wrk -c100 -d10s -strade.lua http://localhost:8080/trade
Running 10s test @ http://localhost:8080/trade
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.45ms 199.79us 7.61ms 88.80%
Req/Sec 34.69k 0.87k 36.02k 85.15%
696962 requests in 10.10s, 49.19MB read
Requests/sec: 68993.88
Transfer/sec: 4.87MB
We can tweak the server a bit and reduce the flush rate or reduce the batch size to get higher throughput with lower connections. We can also tweak the OS so we can have more ephemeral ports available and then use a lot more connections. With those tweaks experience tells me that we can get close to 90K TPS or so on a MacBook Pro. Also we could test from two machines with one of those machines being a Linux server and we can get even more throughput. This test has the disadvantage of all being run on the same machine, but it will be the same disadvantage that the WebSocket version will have so it is somewhat fair. We could also employ HTTP pipelining to increase the throughput, but this trick is great for benchmarks but rarely works in production environments with real clients. On an average server, we can get close to 150K TPS to 200K TPS from experience (I will show this later perhaps).
Ok let's see how WebSocket version does on the same machine with the same server. QBitMicroservice lib supports REST and WebSocket.
In order to use WebSocket, we need to create an async interface so we can build a client proxy.

Async interface

packageio.advantageous.qbit.example.perf.websocket;

importio.advantageous.qbit.reactive.Callback;

publicinterfaceTradeServiceAsync {


voidtrade(Callback<Boolean>callback, finalTradetrade);
voidcount(Callback<Long>callback);
}
Here is the code to create clients and run them against the same server on the same machine.

Load testing with WebSocket client

packageio.advantageous.qbit.example.perf.websocket;

importio.advantageous.boon.core.Str;
importio.advantageous.boon.core.Sys;
importio.advantageous.qbit.client.Client;

importjava.util.ArrayList;
importjava.util.List;
importjava.util.concurrent.atomic.AtomicInteger;

import staticio.advantageous.qbit.service.ServiceProxyUtils.flushServiceProxy;

import staticio.advantageous.boon.core.IO.puts;
import staticio.advantageous.qbit.client.ClientBuilder.clientBuilder;

publicclassTradeServiceLoadTestWebSocket {


publicstaticvoidmain(finalString... args) {

/** Hold the number of clients we will run. */
finalint numClients =3;

/** Hold the number of calls each thread will make. */
finalint numCalls =50_000_000;

/** Hold the client threads to run. */
finalList<Thread> threadList =newArrayList<>(numClients);

/** Hold the counts to total. */
finalList<AtomicInteger> counts =newArrayList<>();


/** Create the client threads. */
for (int c =0; c < numClients; c++) {
finalAtomicInteger count =newAtomicInteger();
counts.add(count);
threadList.add(newThread(() -> {
runCalls(numCalls, count);
}));
}

/** Start the threads. */
threadList.forEach(Thread::start);

/** Grab the start time. */
long startTime =System.currentTimeMillis();

for (int index =0; index<1000; index++) {
Sys.sleep(1000);

long totalCount =0L;

for (int c =0; c < counts.size(); c++) {
totalCount += counts.get(c).get();
}

puts("total", Str.num(totalCount),
"\telapsed time", Str.num(System.currentTimeMillis()-startTime),
"\trate", Str.num(totalCount/(System.currentTimeMillis()-startTime)*1000));
}

}

/** Each client will run this
*
* @param numCalls number of times to make calls
* @param count holds the total count
*/
privatestaticvoidrunCalls(finalintnumCalls, finalAtomicIntegercount) {
finalClient client = clientBuilder().setAutoFlush(false).build();

finalTradeServiceAsync tradeService = client.createProxy(TradeServiceAsync.class, "tradeservice");

client.startClient();

for (int call=0; call < numCalls; call++) {
tradeService.trade(response -> {
if (response) {
count.incrementAndGet();
}
}, newTrade("IBM", 1));

/** Apply some back pressure so the server is not overwhelmed. */
if (call %10==0) {
while (call -5_000> count.get()) {
Sys.sleep(10);
}
}
}

flushServiceProxy(tradeService);
}

}
How does WebSocket do? Quite well!
total 26,668,058    elapsed time 52,186     rate 511,000
That is a total of 1,022,000 messages a second (request / response) using just three WebSocket connections. A single WebSocket connection seems to handle around 700K TPS, and then we start running into more and more IO contention, which again can be solved by having bigger pipes or by tweaking the TCP/IP stack. But in this simple test, we can see that we have 7.5X improvement over REST by using WebSocket.

Setting up my network

$
0
0
Setting up my home network again to do some testing. 
It has been a while since I setup my Linux "server" (Intel NUC) and network hub.
I meant to do it last year, but work got busy.

Spent a lot of time trying to figure out how to dual boot a laptop and setup the right partitions. It was a mere matter of googling to a certain level and the rest trial and err. Success goes to the persistent. 

I tried to setup VNC server on the two Linux servers so I could access them from my MacBook Pro (new), but no dice. The Apple VNC client could not connect to the Ubuntu shared desktop feature nor could Chicken of the VNC or JollyFastVNC. What a waste of time. It used to work in Ubuntu 12. I wish I could get those hours back. Anyway, I dug up some monitors in my supply of older equipment and got everything setup. I guess ssh will have to do and if I need more than that I can hook up a keyboard and monitor. 

I setup a network and a hub. I just hard coded the IP address on the private subnet. Each machine can also connect to the wireless network, but for load testing I want to use the wired version of the network over a 10 GB hub. I really want a 100 GB hub, but Santa did not provide. I have done this before. I should have taken notes. It would have gone a lot faster. 

I have a few older laptops that I might employ for other "servers" in my network. I plan on setting up Hadoop, Apache Spark, Graphite, Cassandra, Docker instances, and all sorts of fun things. 

I have been enjoying my new Apple Watch, MacBook Pro and iPad Pro. Of course, I need the last two for work. :)





Setting up Nomad and Consul in EC2 instead of Mesophere

$
0
0
Setting up Nomad instead of Mesophere. Notes on me setting up a Nomad set of servers for development in EC2.

Server nodes for Nomad and Consul

Three EC2 Medium (triad) machines.
Each server runs
  • consul server agent,
  • nomad server agent
  • No Docker here

Worker Nodes (aka client nodes)

Three to X client Agent Nodes (EC2 LARGE or better)
Each client agent node runs
  • consul client agent
  • nomad server agent
  • Docker daemon
These connect to home server triads (consul and nomad).

Prod cluster

Server nodes for Nomad and Consul

Five EC2 Large (triad) machines.
Each server runs
  • consul server agent,
  • nomad server agent
  • No Docker here

Worker Nodes (aka client nodes)

Three to X client Agent Nodes (as large as we need, at least one machine per AZ)
Each client agent node runs
  • consul client agent
  • nomad server agent
  • Docker daemon
These connect to home server triads (consul and nomad).

Implementation details

You have four roles
  • server1
  • server2
  • server3
  • worker-node
All worker-nodes are ephemeral. They can get blown away.
The servers: server1server2server3 form a triad cluster. Any triad member can die and be replaced. They should be started back up with the same basic ip address info.
Since we are running consulnomad, and zookeeper on the triad, there is no advantage to using consul for nomad server node discovery because consul is installed on the same triad of servers.
The server server1 is a bit special because it has the script to connect the servers into a cluster. For the most part, server1 is identical to the others.

Server 1

Server1 Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :

$script= <<SCRIPT
# Update apt and get dependencies
sudo apt-get update
sudo apt-get install -y unzip curl wget vim

# Download Nomad
echo Fetching Nomad...
cd /tmp/
curl -sSL https://releases.hashicorp.com/nomad/0.2.3/nomad_0.2.3_linux_amd64.zip -o nomad.zip

echo Installing Nomad...
unzip nomad.zip
sudo chmod +x nomad
sudo mv nomad /usr/bin/nomad
sudo mkdir -p /etc/nomad.d
sudo chmod a+w /etc/nomad.d
sudo mkdir -p /opt/nomad/data
sudo mkdir -p /var/log/nomad
sudo chmod a+w /var/log/nomad
sudo cp /vagrant/server.hcl /etc/nomad.d/

echo Fetching Consul...
curl -sSL https://releases.hashicorp.com/consul/0.6.3/consul_0.6.3_linux_amd64.zip -o consul.zip
echo Installing Consul...
unzip consul.zip
sudo chmod +x consul
sudo mv consul /usr/bin/consul
sudo mkdir -p /etc/consul.d
sudo chmod a+w /etc/consul.d
sudo mkdir -p /opt/consul/data
sudo mkdir -p /var/log/consul
sudo chmod a+w /var/log/consul
sudo cp /vagrant/consul.json /etc/consul.d/


echo Starting nomad
cd ~
sudo nohup nomad agent -config /etc/nomad.d/server.hcl &>nomad.log &


echo Starting Consul
sudo nohup consul agent -config-file /etc/consul.d/consul.json &>consul.log &

SCRIPT

Vagrant.configure(2) do |config|
config.vm.box ="base-box"
config.vm.hostname ="nomad"
config.vm.provision "shell", inline:$script, privileged:false
config.vm.network "private_network", ip:"10.21.0.10"



# Increase memory for Parallels Desktop
config.vm.provider "parallels"do |p, o|
p.memory ="1024"
end

# Increase memory for Virtualbox
config.vm.provider "virtualbox"do |vb|
vb.memory ="1024"
end

# Increase memory for VMware
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider p do |v|
v.vmx["memsize"] ="1024"
end
end

config.vm.provider :awsdo |aws, override|
aws.keypair_name ="my-app-key"
aws.region ="us-west-2"
# Ubuntu public Amazon EC2 image for Ubuntu 64 bit
aws.ami ="ami-9abea4fb"
override.ssh.username ="ubuntu"
override.ssh.private_key_path ="/opt/aws/my-app-key.pem"

aws.tags = {
'Name' => 'my-app-cluster-server-1'
}


# vpc-d14dacb5
aws.subnet_id ="subnet-abc123ab"
aws.security_groups ="sg-abc123ab"
aws.private_ip_address="10.21.0.10"
override.vm.hostname ="ip-21-10-0-10"
# override.ssh.host = "10.20.0.10" //NOT EXPOSED TO VPN traffic yet
# We have to use public IP address because we don't have the VPC tied to vpn traffic
aws.associate_public_ip =true

end
end

server.hcl


bind_addr = "10.21.0.10"

advertise {
# We need to specify our host's IP because we can't
# advertise 0.0.0.0 to other nodes in our cluster.
rpc = "10.21.0.10:4647"
}

# Increase log verbosity
log_level = "DEBUG"

# Setup data dir
data_dir = "/opt/nomad/data"

# Enable the server
server {
enabled = true
start_join = ["10.21.0.11", "10.21.0.10", "10.21.0.12"]
retry_join = ["10.21.0.11", "10.21.0.10", "10.21.0.12"]
retry_interval = "15s"
}

server-bootstrap.hcl

bind_addr = "10.21.0.10"

advertise {
# We need to specify our host's IP because we can't
# advertise 0.0.0.0 to other nodes in our cluster.
rpc = "10.21.0.10:4647"
}


# Increase log verbosity
log_level = "DEBUG"

# Setup data dir
data_dir = "/opt/nomad/data"

# Enable the server
server {
enabled = true

# Self-elect, should be 3 or 5 for production
bootstrap_expect = 3
}

consul.json server1

{
"data_dir":"/opt/consul/data",
"log_level":"DEBUG",
"node_name":"master1",
"server":true,

"start_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_interval":"15s"
}

consul-bootstrap.json

{
"data_dir": "/opt/consul/data",
"log_level": "DEBUG",
"node_name": "master1",
"server": true,
"bootstrap_expect" : 3
}

connect-cluster.sh

sudo pkill consul
sudo pkill nomad
sleep 5s

sudo rm -rf /opt/consul/data/*
sudo rm -rf /opt/nomad/data/*
sleep 5s

sudo nohup consul agent -config-file /vagrant/consul-bootstrap.json &>consul.log &
sudo nohup nomad agent -config /vagrant/server-bootstrap.hcl &>nomad.log &
sleep 5s

sudo nomad server-join -address=http://10.21.0.10:4646 10.21.0.11
sudo nomad server-join -address=http://10.21.0.10:4646 10.21.0.12
sudo nomad server-members -address=http://10.21.0.10:4646

Server 2 and 3 are simpler

Server 2

Vagrantfile server2

# -*- mode: ruby -*-
# vi: set ft=ruby :


$script= <<SCRIPT
# Update apt and get dependencies
sudo apt-get update
sudo apt-get install -y unzip curl wget vim

# Download Nomad
echo Fetching Nomad...
cd /tmp/
curl -sSL https://releases.hashicorp.com/nomad/0.2.3/nomad_0.2.3_linux_amd64.zip -o nomad.zip

echo Installing Nomad...
unzip nomad.zip
sudo chmod +x nomad
sudo mv nomad /usr/bin/nomad
sudo mkdir -p /etc/nomad.d
sudo chmod a+w /etc/nomad.d
sudo mkdir -p /opt/nomad/data
sudo mkdir -p /var/log/nomad
sudo chmod a+w /var/log/nomad
sudo cp /vagrant/server.hcl /etc/nomad.d/

echo Fetching Consul...
curl -sSL https://releases.hashicorp.com/consul/0.6.3/consul_0.6.3_linux_amd64.zip -o consul.zip
echo Installing Consul...
unzip consul.zip
sudo chmod +x consul
sudo mv consul /usr/bin/consul
sudo mkdir -p /etc/consul.d
sudo chmod a+w /etc/consul.d
sudo mkdir -p /opt/consul/data
sudo mkdir -p /var/log/consul
sudo chmod a+w /var/log/consul
sudo cp /vagrant/consul.json /etc/consul.d/


echo Starting nomad
cd ~
sudo nohup nomad agent -config /etc/nomad.d/server.hcl &>nomad.log &


echo Starting Consul
sudo nohup consul agent -config-file /etc/consul.d/consul.json &>consul.log &

SCRIPT

Vagrant.configure(2) do |config|
config.vm.box ="base-box"
config.vm.hostname ="nomad"
config.vm.provision "shell", inline:$script, privileged:false
config.vm.network "private_network", ip:"10.21.0.11"



# Increase memory for Parallels Desktop
config.vm.provider "parallels"do |p, o|
p.memory ="1024"
end

# Increase memory for Virtualbox
config.vm.provider "virtualbox"do |vb|
vb.memory ="1024"
end

# Increase memory for VMware
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider p do |v|
v.vmx["memsize"] ="1024"
end
end

config.vm.provider :awsdo |aws, override|
aws.keypair_name ="my-app-key"
aws.region ="us-west-2"
# Ubuntu public Amazon EC2 image for Ubuntu 64 bit
aws.ami ="ami-9abea4fb"
override.ssh.username ="ubuntu"
override.ssh.private_key_path ="/opt/aws/my-app-key.pem"

aws.tags = {
'Name' => 'my-app-cluster-server-2'
}


# vpc-d14dacb5
aws.subnet_id ="subnet-abc123ab"
aws.security_groups ="sg-abc123ab"
aws.private_ip_address="10.21.0.11"
override.vm.hostname ="ip-21-10-0-11"
#override.ssh.host = "10.20.0.10" //NOT EXPOSED TO VPN traffic yet
# We have to use public IP address because we don't have the VPC tied to vpn traffic
aws.associate_public_ip =true

end
end

consul.json server2

{
"data_dir":"/opt/consul/data",
"log_level":"DEBUG",
"node_name":"master2",
"server":true,

"start_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_interval":"15s"
}

server.hcl server2 nomad config file


bind_addr = "10.21.0.11"

advertise {
# We need to specify our host's IP because we can't
# advertise 0.0.0.0 to other nodes in our cluster.
rpc = "10.21.0.11:4647"
}

# Increase log verbosity
log_level = "DEBUG"

# Setup data dir
data_dir = "/opt/nomad/data"

# Enable the server
server {
enabled = true
start_join = ["10.21.0.11", "10.21.0.10", "10.21.0.12"]
retry_join = ["10.21.0.11", "10.21.0.10", "10.21.0.12"]
retry_interval = "15s"
}

Server 3

Vagrantfile server3

bind_addr ="10.21.0.12"

advertise {
# We need to specify our host's IP because we can't
# advertise 0.0.0.0 to other nodes in our cluster.
rpc ="10.21.0.12:4647"
}


# Increase log verbosity
log_level ="DEBUG"

# Setup data dir
data_dir ="/opt/nomad/data"

# Enable the server
server {
enabled =true
start_join = ["10.21.0.11", "10.21.0.10", "10.21.0.12"]
retry_join = ["10.21.0.11", "10.21.0.10", "10.21.0.12"]
retry_interval ="15s"

}

consul.json server3

{
"data_dir":"/opt/consul/data",
"log_level":"DEBUG",
"node_name":"master3",
"server":true,

"start_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_interval":"15s"
}

server.hcl server3 nomad config file

# -*- mode: ruby -*-
# vi: set ft=ruby :

$script = <<SCRIPT
# Update apt and get dependencies
sudo apt-get update
sudo apt-get install -y unzip curl wget vim

# Download Nomad
echo Fetching Nomad...
cd /tmp/
curl -sSL https://releases.hashicorp.com/nomad/0.2.3/nomad_0.2.3_linux_amd64.zip -o nomad.zip

echo Installing Nomad...
unzip nomad.zip
sudo chmod +x nomad
sudo mv nomad /usr/bin/nomad
sudo mkdir -p /etc/nomad.d
sudo chmod a+w /etc/nomad.d
sudo mkdir -p /opt/nomad/data
sudo mkdir -p /var/log/nomad
sudo chmod a+w /var/log/nomad
sudo cp /vagrant/server.hcl /etc/nomad.d/

echo Fetching Consul...
curl -sSL https://releases.hashicorp.com/consul/0.6.3/consul_0.6.3_linux_amd64.zip -o consul.zip
echo Installing Consul...
unzip consul.zip
sudo chmod +x consul
sudo mv consul /usr/bin/consul
sudo mkdir -p /etc/consul.d
sudo chmod a+w /etc/consul.d
sudo mkdir -p /opt/consul/data
sudo mkdir -p /var/log/consul
sudo chmod a+w /var/log/consul
sudo cp /vagrant/consul.json /etc/consul.d/


echo Starting nomad
cd ~
sudo nohup nomad agent -config /etc/nomad.d/server.hcl &>nomad.log &


echo Starting Consul
sudo nohup consul agent -config-file /etc/consul.d/consul.json &>consul.log &

SCRIPT

Vagrant.configure(2) do |config|
config.vm.box = "base-box"
config.vm.hostname = "nomad"
config.vm.provision "shell", inline: $script, privileged: false
config.vm.network "private_network", ip: "10.21.0.12"



# Increase memory for Parallels Desktop
config.vm.provider "parallels" do |p, o|
p.memory = "1024"
end

# Increase memory for Virtualbox
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end

# Increase memory for VMware
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider p do |v|
v.vmx["memsize"] = "1024"
end
end

config.vm.provider :aws do |aws, override|
aws.keypair_name = "my-app-key"
aws.region = "us-west-2"
# Ubuntu public Amazon EC2 image for Ubuntu 64 bit
aws.ami = "ami-9abea4fb"
override.ssh.username = "ubuntu"
override.ssh.private_key_path = "/opt/aws/my-app-key.pem"

aws.tags = {
'Name' => 'my-app-cluster-server-3'
}


# vpc-d14dacb5
aws.subnet_id = "subnet-abc123ab"
aws.security_groups = "sg-abc123ab"
aws.private_ip_address="10.21.0.12"
override.vm.hostname = "ip-21-10-0-12"
#override.ssh.host = "10.20.0.10" //NOT EXPOSED TO VPN traffic yet
# We have to use public IP address because we don't have the VPC tied to vpn traffic
aws.associate_public_ip = true

end
end

Worker Node

You may have noticed that the files are about all the same. The Worker Nodes are always the same. They startup and then connect consul client and nomad client to the cluster. They can handle work from the nomad servers.

consul.json for worker node

{
"data_dir":"/opt/consul/data",
"log_level":"DEBUG",
"server":false,
"start_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_join": ["10.21.0.11", "10.21.0.10", "10.21.0.12"],
"retry_interval":"15s"
}
Note that these are just the initial servers to contact. If/when we added more servers, the instances would learn about the instances via serf.

nomad.hcl for worker node

# Increase log verbosity
log_level = "DEBUG"

# Setup data dir
data_dir = "/opt/nomad/data"

# Enable the server
client {
enabled = true
servers=["10.21.0.10:4647", "10.21.0.11:4647", "10.21.0.12:4647"]
}
Note that these are just the initial servers to contact. If/when we added more servers, the instances would learn about the instances via serf.

Vagrantfile for worker node

# -*- mode: ruby -*-
# vi: set ft=ruby :

$script = <<SCRIPT
# Update apt and get dependencies
sudo apt-get update
sudo apt-get install -y unzip curl wget vim

# Download Nomad
echo Fetching Nomad...
cd /tmp/
curl -sSL https://releases.hashicorp.com/nomad/0.2.3/nomad_0.2.3_linux_amd64.zip -o nomad.zip

echo Installing Nomad...
unzip nomad.zip
sudo chmod +x nomad
sudo mv nomad /usr/bin/nomad
sudo mkdir -p /etc/nomad.d
sudo chmod a+w /etc/nomad.d
sudo mkdir -p /opt/nomad/data
sudo mkdir -p /var/log/nomad
sudo chmod a+w /var/log/nomad
sudo cp /vagrant/nomad.hcl /etc/nomad.d/

echo Fetching Consul...
curl -sSL https://releases.hashicorp.com/consul/0.6.3/consul_0.6.3_linux_amd64.zip -o consul.zip
echo Installing Consul...
unzip consul.zip
sudo chmod +x consul
sudo mv consul /usr/bin/consul
sudo mkdir -p /etc/consul.d
sudo chmod a+w /etc/consul.d
sudo mkdir -p /opt/consul/data
sudo mkdir -p /var/log/consul
sudo chmod a+w /var/log/consul
sudo cp /vagrant/consul.json /etc/consul.d/


echo Starting nomad
cd ~
sudo nohup nomad agent -config /etc/nomad.d/nomad.hcl &>nomad.log &


echo Starting Consul
export BIND_ADDRESS=`/sbin/ifconfig eth0 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'`
sudo nohup consul agent -bind $BIND_ADDRESS -config-file /etc/consul.d/consul.json &>consul.log &

SCRIPT

Vagrant.configure(2) do |config|
config.vm.box = "base-box"
config.vm.hostname = "nomad"
config.vm.provision "docker" # Just install it
config.vm.provision "shell", inline: $script, privileged: false




# Increase memory for Parallels Desktop
config.vm.provider "parallels" do |p, o|
p.memory = "1024"
end

# Increase memory for Virtualbox
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end

# Increase memory for VMware
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider p do |v|
v.vmx["memsize"] = "1024"
end
end

config.vm.provider :aws do |aws, override|
aws.keypair_name = "my-app-key"
aws.region = "us-west-2"
# Ubuntu public Amazon EC2 image for Ubuntu 64 bit
aws.ami = "ami-9abea4fb"
override.ssh.username = "ubuntu"
override.ssh.private_key_path = "/opt/aws/my-app-key.pem"

aws.tags = {
'Name' => 'my-app-cluster-worker-node'
}


# vpc-d14dacb5
aws.subnet_id = "subnet-abc123ab"
aws.security_groups = "sg-abc123ab"
aws.instance_type ="c3.8xlarge" # decide what is the best
aws.associate_public_ip = true

end
end
Notice that the startup script is nearly identical except that no IP address is hardwired.

Using Vagrant to work with EC2

The tutorial for Nomad used Vagrant so we started with Vagrant to setup out cluster.

Install AWS plugin

$ vagrant plugin install vagrant-aws

Setup a base-box for EC2

$ vagrant box add base-box https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box

Startup vagrant

Navigate to the directory corresponding to the role mentioned above.

Startup vagrant instance in EC2

$ vagrant up --provider=aws

SSH into vagrant box

Navigate to the directory corresponding to the role mentioned above.

ssh into vagrant instance in EC2

$ vagrant ssh

destroy instance

Navigate to the directory corresponding to the role mentioned above.

terminate/destroy vagrant instance in EC2

$ vagrant destroy

Nomad commands

Deploy a job

$ nomad run example.nomad 
==> Monitoring evaluation "9d05879b-d338-fa99-f2a1-9e8cdf45fc71"
Evaluation triggered by job "example"
Allocation "bccc1da9-7b0a-0a4d-17c1-345215044002" created: node "a07b38db-3be1-5a9f-d0dc-2d757991f2c4", group "cache"
Evaluation status changed: "pending" -> "complete"
==> Evaluation "9d05879b-d338-fa99-f2a1-9e8cdf45fc71" finished with status "complete"

Checking status of a job

$  nomad status example
ID = example
Name = example
Type = service
Priority = 50
Datacenters = dc1
Status = <none>

==> Evaluations
ID Priority TriggeredBy Status
9d05879b-d338-fa99-f2a1-9e8cdf45fc71 50 job-register complete

==> Allocations
ID EvalID NodeID TaskGroup Desired Status
bccc1da9-7b0a-0a4d-17c1-345215044002 9d05879b-d338-fa99-f2a1-9e8cdf45fc71 a07b38db-3be1-5a9f-d0dc-2d757991f2c4 cache run running

Show active Nomad Master Servers

$ nomad client-config -servers
10.21.0.11:4647
10.21.0.12:4647
10.21.0.10:4647

It should be easy to deploy from artifactory.
At this point, you can deploy to Nomad like you would Mesophere.

Step 1 Initially setting up gradle and create Vert.x hello world (Microservices with Vertx)

$
0
0
Initially setting up gradle and vert.x.
We will expand in this script, but it is easier to understand in its infancy.
We are using John Rengelman's Shadow Jar Plugin which is similar to the Maven which is similar to Maven Shade plugin but for Gradle and is faster than the Gradle built-in fat jar.
I am not sure how we will do the final deploy. This is a step down the road. The direction might change.
At this point the gradle build file is pretty basic.

Gradle build file

plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '1.2.2'
id 'idea'
}

group 'rickhigh'
version '1.0-SNAPSHOT'


idea {
project {
languageLevel ='1.8'
}
}
repositories {
mavenCentral()
maven {
url ='http://oss.sonatype.org/content/repositories/snapshots/'
}
mavenLocal() /* Just in case we want to use local artifacts that we build locally. */
}


sourceCompatibility ='1.8'
mainClassName ='io.vertx.core.Launcher'

dependencies {
compile "io.vertx:vertx-core:3.2.0"
testCompile group:'junit', name:'junit', version:'4.11'
}

/* used to create the fat jar files. */
shadowJar {
classifier ='fat'
manifest {
attributes 'Main-Verticle':'com.github.vertx.node.example.HelloWorldVerticle'
}
mergeServiceFiles {
include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
}
}

task wrapper(type:Wrapper) {
gradleVersion ='2.9'
}
You can see this in this branch.
Like I said, I am not sure if we will stick with gradle or use maven. It all depends. Also I am not sure we will use fatjars or dist. Or run with vertx command line.
For now, we are doing this.
Notice that the main app is io.vertx.core.Launcher. Also notice that our Verticle is calledcom.github.vertx.node.example.HelloWorldVerticle. This will change in a later branch.

HelloWorldVerticle

packagecom.github.vertx.node.example;

importio.vertx.core.AbstractVerticle;

publicclassHelloWorldVerticleextendsAbstractVerticle {

@Override
publicvoidstart() {
vertx.createHttpServer().requestHandler(req -> req.response().end("Hello World!")).listen(8080);
}
}

To build the shadow jar / fat jar do this.

Build the fat jar

$ ./gradlew shadowJar
To run the verticle do this:

Run the verticle

$ #FIND The JAR
$ find . -name "*fat.jar"
./build/libs/vertx-node-ec2-eventbus-example-1.0-SNAPSHOT-fat.jar

$ # Run the jar
$ java -jar ./build/libs/vertx-node-ec2-eventbus-example-1.0-SNAPSHOT-fat.jar

Files so far.


├── build.gradle
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradlew
├── settings.gradle
├── src
   ├── main
   │   └── java
   │   └── com
   │   └── github
   │   └── vertx
   │   └── node
   │   └── example
   │   └── HelloWorldVerticle.java
   └── test
   └── java

You can see this in this branch.
After you run this example, you can see it in action as follows:

Curl it

$ curl http://localhost:8080
Hello World!

Step 2 Change Hello World to use EventBus (Microservices with Vertx)

$
0
0
Now we want to deploy to verticles running in the same JVM. The first verticle is an HTTP verticle and it will send a Message on the Vert.x EventBus.
Now we are going to change things up a bit. We will have a main verticle called MainVerticlewhich will start up two verticles, namely, HelloWorldVerticle and WebVerticle.
You can find the source for this in this branch.
We also setup logging.
One of the biggest mistakes is not logging errors in an async program.
The HelloWorldVerticle handles events from the event bus, and responds.

Change HelloWorldVerticle to use event bus instead of HTTP Server

packagecom.github.vertx.node.example;

importio.vertx.core.AbstractVerticle;
importio.vertx.core.eventbus.Message;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;

publicclassHelloWorldVerticleextendsAbstractVerticle {

privatefinalLogger logger =LoggerFactory.getLogger(HelloWorldVerticle.class);
@Override
publicvoidstart() {
vertx.eventBus().consumer(Services.HELLO_WORLD.toString(), message -> {
dispatchMessage(message);
});
}

privatevoiddispatchMessage(finalMessage<Object>message) {

try {
finalHelloWorldOperations operation =HelloWorldOperations.valueOf(message.body().toString());
switch (operation) {
caseSAY_HELLO_WORLD:
message.reply("HELLO WORLD");
break;
default:
logger.error("Unable to handle operation {}", operation);
message.reply("Unsupported operation");
}
}catch (finalException ex) {
logger.error("Unable to handle operation due to exception"+ message.body(), ex);
}
}

}

The WebVerticle handles all HTTP request by delegating to the HelloWorldVerticle.

Create WebVerticle to send a hello world request message to the HelloVerticle

packagecom.github.vertx.node.example;

importio.vertx.core.AbstractVerticle;
importio.vertx.core.http.HttpServerRequest;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;

publicclassWebVerticleextendsAbstractVerticle {


privatefinalLogger logger =LoggerFactory.getLogger(WebVerticle.class);

@Override
publicvoidstart() {
vertx.createHttpServer()
.requestHandler(httpRequest -> handleHttpRequest(httpRequest) )
.listen(8080);
}

privatevoidhandleHttpRequest(finalHttpServerRequesthttpRequest) {

/* Invoke using the event bus. */
vertx.eventBus().send(Services.HELLO_WORLD.toString(),
HelloWorldOperations.SAY_HELLO_WORLD.toString(), response -> {

if (response.succeeded()) {
/* Send the result from HelloWorldService to the http connection. */
httpRequest.response().end(response.result().body().toString());
} else {
logger.error("Can't send message to hello service", response.cause());
httpRequest.response().setStatusCode(500).end(response.cause().getMessage());
}
});
}
}

We define some constants via Enums to operations and such.

Enum for service names and operations

packagecom.github.vertx.node.example;

publicenumServices {

HELLO_WORLD
}

packagecom.github.vertx.node.example;

publicenumHelloWorldOperations {

SAY_HELLO_WORLD
}
The MainVerticle starts up the other two verticles, and then uses a vertx timer to see when the operation completes.
Note we added a main method so it would be easy to startup in our IDE.

Define a verticle to start the other two.

packagecom.github.vertx.node.example;

importio.vertx.core.AbstractVerticle;
importio.vertx.core.Vertx;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;

importjava.util.Arrays;
importjava.util.List;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.atomic.AtomicInteger;

publicclassMainVerticleextendsAbstractVerticle {



privatefinalLogger logger =LoggerFactory.getLogger(MainVerticle.class);

@Override
publicvoidstart() throwsInterruptedException {

/** Count of services. */
finalAtomicInteger serviceCount =newAtomicInteger();

/** List of verticles that we are starting. */
finalList<AbstractVerticle> verticles =Arrays.asList(newHelloWorldVerticle(), newWebVerticle());

verticles.stream().forEach(verticle -> vertx.deployVerticle(verticle, deployResponse -> {

if (deployResponse.failed()) {
logger.error("Unable to deploy verticle "+ verticle.getClass().getSimpleName(),
deployResponse.cause());
} else {
logger.info(verticle.getClass().getSimpleName() +" deployed");
serviceCount.incrementAndGet();
}
}));


/** Wake up in five seconds and check to see if we are deployed if not complain. */
vertx.setTimer(TimeUnit.SECONDS.toMillis(5), event -> {

if (serviceCount.get() != verticles.size()) {
logger.error("Main Verticle was unable to start child verticles");
} else {
logger.info("Start up successful");
}
});

}


publicstaticvoidmain(finalString... args) {
finalVertx vertx =Vertx.vertx();
vertx.deployVerticle(newMainVerticle());
}
}
We added logging to the gradle build file as well as a logback.xml file to the test resources.
In addition to adding logging, we changed the main verticle to MainVerticle fromHelloWorldVerticle.

Updated gradle file

plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '1.2.2'
id 'idea'
}

group 'rickhigh'
version '1.0-SNAPSHOT'


idea {
project {
languageLevel ='1.8'
}
}
repositories {
mavenCentral()
maven {
url ='http://oss.sonatype.org/content/repositories/snapshots/'
}
mavenLocal() /* Just in case we want to use local artifacts that we build locally. */
}


sourceCompatibility ='1.8'
mainClassName ='io.vertx.core.Launcher'

dependencies {
compile "io.vertx:vertx-core:3.2.0"
compile 'ch.qos.logback:logback-core:1.1.3'
compile 'ch.qos.logback:logback-classic:1.1.3'
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile group:'junit', name:'junit', version:'4.11'
}

/* used to create the fat jar files. */
shadowJar {
classifier ='fat'
manifest {
attributes 'Main-Verticle':'com.github.vertx.node.example.MainVerticle'
}
mergeServiceFiles {
include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
}
}

task wrapper(type:Wrapper) {
gradleVersion ='2.9'
}

Notice the main veritcle changed

shadowJar {
classifier ='fat'
manifest {
attributes 'Main-Verticle':'com.github.vertx.node.example.MainVerticle'
}
mergeServiceFiles {
include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
}
}

Step 3 Adding Kotlin (Microservices with Vertx)

$
0
0
We decided to add Kotlin to the mix. It is terse and modern and very compatible with the core Java libs. It builds on your knowledge of Java. The Idea guys explain how to do add Kotlin to a Gradle project, yet it still took some messing around.
You can find the source code for this in this branch.
To do this we added the Kotlin plugin to gradle.

Adding Kotlin to gradle

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.0-beta-4584'
}
}


plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '1.2.2'
id 'idea'
}


apply plugin:"kotlin"

...

dependencies {
...
compile 'org.slf4j:slf4j-api:1.7.12'
compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.0-beta-4584'
testCompile group:'junit', name:'junit', version:'4.11'
}


Most of the gradle build files stays the same. The way we build and execute stays the same as well.
We left WebVerticle in Java for now. We changed the other files and we were able to get rid of a few files as well by grouping enums with the classes that more or less own them.

HelloWorldService.kt

packagecom.github.vertx.node.example

import io.vertx.core.AbstractVerticle
import io.vertx.core.eventbus.Message
import org.slf4j.LoggerFactory

enum class HelloWorldOperations {

SAY_HELLO_WORLD
}


class HelloWorldVerticle:AbstractVerticle() {

privatevallogger= LoggerFactory.getLogger(HelloWorldVerticle::class.java)
overridefunstart() {
vertx.eventBus().consumer<Any>(Services.HELLO_WORLD.toString()) { message -> dispatchMessage(message) }
}

privatefundispatchMessage(message: Message<Any>) {

try {
valoperation= HelloWorldOperations.valueOf(message.body().toString())
when (operation) {
HelloWorldOperations.SAY_HELLO_WORLD-> message.reply("HELLO WORLD FROM KOTLIN")
else-> {
logger.error("Unable to handle operation {}", operation)
message.reply("Unsupported operation")
}
}
} catch (ex: Exception) {
logger.error("Unable to handle operation due to exception"+ message.body(), ex)
}

}

}

MainService.kt

packagecom.github.vertx.node.example

import io.vertx.core.AbstractVerticle
import io.vertx.core.Vertx
import org.slf4j.LoggerFactory
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.forEach

enum class Services {
HELLO_WORLD
}


public class MainVerticle:AbstractVerticle() {


privatevallogger= LoggerFactory.getLogger(MainVerticle::class.java)

overridefunstart() {

/** Count of services. */
valserviceCount= AtomicInteger()

/** List of verticles that we are starting. */
valverticles= Arrays.asList(HelloWorldVerticle(), WebVerticle())

verticles.forEach { verticle->
vertx.deployVerticle(verticle) { deployResponse ->

if (deployResponse.failed()) {
logger.error("Unable to deploy verticle ${verticle.javaClass.simpleName}",
deployResponse.cause())
} else {
logger.info("${verticle.javaClass.simpleName} deployed")
serviceCount.incrementAndGet()
}
}
}


/** Wake up in five seconds and check to see if we are deployed if not complain. */
vertx.setTimer(TimeUnit.SECONDS.toMillis(5)) { event ->

if (serviceCount.get() != verticles.size) {
logger.error("Main Verticle was unable to start child verticles")
} else {
logger.info("Start up successful")
}
}

}

companion object {
@JvmStatic funmain(args:Array<String>) {
valvertx= Vertx.vertx()
vertx.deployVerticle(MainVerticle())
}
}
}
The code is a bit smaller and there are less files. We think Kotlin will be a big boon when we start using data class.

Step 4 Call Vertx app from event bus from Node

$
0
0
You can find the steps for step 4 in this branch.

Install node

If you don't have node setup, then now is a good time.
$ brew install node

Output

==> Reinstalling node
==> Downloading https://homebrew.bintray.com/bottles/node-5.3.0.el_capitan.bottle.tar.gz
Already downloaded: /Library/Caches/Homebrew/node-5.3.0.el_capitan.bottle.tar.gz
==> Pouring node-5.3.0.el_capitan.bottle.tar.gz
...
==> Summary
🍺 /usr/local/Cellar/node/5.3.0: 2827 files, 37M
v5.3.0

Verify

$ node -v

$ npm -v
You should see later versions of both projects.
Create a project folder and navigate to it. We put all node files in {root_project}/node and we put all vertx files in {root_project}/vertx.

Init project

$ npm init

Install http

$ npm install http --save

package.json

{
"name":"node-2-vertx",
"version":"1.0.0",
"description":"Call Vertx",
"main":"run.js",
"scripts": {
"test":"test"
},
"author":"Rick Hightower, Geoff Chandler",
"license":"ISC",
"dependencies": {
"http":"0.0.0"
}
}
Just to see if we have everything setup.

Call vertx service via HTTP

var http =require('http');

var options = {
host:'localhost',
path:'/hello',
port:8080
};

callback=function(response) {
var str ='';

//another chunk of data has been received, so append it to `str`
response.on('data', function (chunk) {
str += chunk;
});

//the whole response has been received, so we just print it out here
response.on('end', function () {
console.log("FROM SERVER "+ str);
});
}

http.request(options, callback).end();

Next up, Let's use the event bus from this NPM module vertx3 NPM module.

Adding dependency for vertx event bus

$ npm install vertx3-eventbus-client  --save
$ npm install sockjs-client --save
This will add the dependencies to your package.json file in your node folder.
Let's review the Java code which calls the bus and let's look at the code to install the vertx bridge. First we need to install vertx-web support, and then we need the SockJS bridge support. We will also install the TCP event bus bridge so we can call this microservice from not only Node but other Vertx/Java microservices.

Gradle build script build.gradle

dependencies {
compile "io.vertx:vertx-core:3.2.0"
...
compile 'io.vertx:vertx-tcp-eventbus-bridge:3.2.0'// ** ADDED
compile 'io.vertx:vertx-web:3.2.0'//** ADDED
testCompile group:'junit', name:'junit', version:'4.11'
}
Next we need to configure the event bus bridge. We can do this in the WebVerticle as follows.

WebVerticle.java configure event bus sockJS bridge

packagecom.github.vertx.node.example;

importio.vertx.core.AbstractVerticle;
importio.vertx.core.http.HttpServerRequest;
importio.vertx.ext.web.Router;
importio.vertx.ext.web.handler.sockjs.BridgeOptions;
importio.vertx.ext.web.handler.sockjs.PermittedOptions;
importio.vertx.ext.web.handler.sockjs.SockJSHandler;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;

/**
* This gets started by the MainVerticle.
* It configures the event bus bridge and install the REST routes.
*/
publicclassWebVerticleextendsAbstractVerticle {

privatefinalLogger logger =LoggerFactory.getLogger(WebVerticle.class);

@Override
publicvoidstart() {

/* Create vertx web router. */
finalRouter router =Router.router(vertx);


/* Install our original "REST" handler at the /hello/ uri. */
router.route("/hello/*").handler(event -> handleHttpRequestToHelloWorld(event.request()));


/* Allow Hello World service to be exposed to Node.js. */
finalBridgeOptions options =newBridgeOptions()
.addInboundPermitted(
newPermittedOptions().setAddress(Services.HELLO_WORLD.toString()));

/* Configure bridge at this HTTP/WebSocket URI. */
router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(options));

/* Install router into vertx. */
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(8080);
}

/** This REST endpoint if for hello.
* It invokes the hello world service via the event bus.
* @param httpRequest HTTP request from vertx.
*/
privatevoidhandleHttpRequestToHelloWorld(finalHttpServerRequesthttpRequest) {

/* Invoke using the event bus. */
vertx.eventBus().send(Services.HELLO_WORLD.toString(),
HelloWorldOperations.SAY_HELLO_WORLD.toString(), response -> {

/* If the response was successful, this means we were able to execute the operation on
the HelloWorld service.
Pass the results to the http request's response.
*/
if (response.succeeded()) {
/* Send the result to the http connection. */
logger.debug("Successfully invoked HelloWorld service {}", response.result().body());
httpRequest.response().end(response.result().body().toString());
} else {
logger.error("Can't send message to hello world service", response.cause());
//noinspection ThrowableResultOfMethodCallIgnored
httpRequest.response().setStatusCode(500).end(response.cause().getMessage());
}
});
}
}
Notice that we had to add the event topic new PermittedOptions().setAddress(Services.HELLO_WORLD.toString() to expose it to the outside world (in this case the Node.js world).
HelloWorldService.kt and MainService.kt stay the same. (Kotlin files). We will include them at the end with some extra comments. Now we can change our node.js example to use the event bus bridge.

Change node to use event bus to call hello world service instead of REST

var EventBus =require('vertx3-eventbus-client');
var eventBus =newEventBus("http://localhost:8080/eventbus/");

/** Don't call until the event bus is open. */
functiononopenEventBus() {

//Call using event bus.
eventBus.send("HELLO_WORLD",
"SAY_HELLO_WORLD", function(response, json) {
console.log(json.body);
});
}

/** Get notified of errors. */
functiononerrorEventBus(error) {
console.log("Problem calling event bus "+ error)
}


eventBus.onopen= onopenEventBus;
eventBus.onerror= onerrorEventBus;
For completeness here is the Kotlin files up to this point:

MainService.kt

packagecom.github.vertx.node.example

importio.vertx.core.AbstractVerticle
importio.vertx.core.Vertx
importorg.slf4j.LoggerFactory
importjava.util.*
importjava.util.concurrent.TimeUnit
importjava.util.concurrent.atomic.AtomicInteger
importkotlin.collections.forEach

enumclass Services {
HELLO_WORLD
}

publicclassMainVerticle : AbstractVerticle() {

private val logger =LoggerFactory.getLogger(MainVerticle::class.java)

override fun start() {

/** Count of services. */
val serviceCount = AtomicInteger()

/** List of verticles that we are starting. */
val verticles =Arrays.asList(HelloWorldVerticle(), WebVerticle())


verticles.forEach { verticle ->
vertx.deployVerticle(verticle) { deployResponse ->

if (deployResponse.failed()) {
logger.error("Unable to deploy verticle ${verticle.javaClass.simpleName}",
deployResponse.cause())
} else {
logger.info("${verticle.javaClass.simpleName} deployed")
serviceCount.incrementAndGet()
}
}
}

/** Wake up in five seconds and check to see if we are deployed if not complain. */
vertx.setTimer(TimeUnit.SECONDS.toMillis(5)) { event ->

if (serviceCount.get() != verticles.size) {
logger.error("Main Verticle was unable to start child verticles")
} else {
logger.info("Start up successful")
}
}

}

}

fun main(args:Array<String>) {
val vertx =Vertx.vertx()
vertx.deployVerticle(MainVerticle())
}

HelloWorldService.kt

packagecom.github.vertx.node.example

importio.vertx.core.AbstractVerticle
importio.vertx.core.eventbus.Message
importorg.slf4j.LoggerFactory

enumclass HelloWorldOperations {

SAY_HELLO_WORLD
}


/** Hello World Verticle gets started by Main Verticle.
* Listens to the event bus.
*/
classHelloWorldVerticle : AbstractVerticle() {

private val logger =LoggerFactory.getLogger(HelloWorldVerticle::class.java)

override fun start() {
vertx.eventBus().consumer<Any>(Services.HELLO_WORLD.toString()) { message -> dispatchMessage(message) }
}

/**
* Handles message from the event bus.
*/
private fun dispatchMessage(message:Message<Any>) {

try {
val operation =HelloWorldOperations.valueOf(message.body().toString())

/** Switch statement that handles various operations. */
when (operation) {
HelloWorldOperations.SAY_HELLO_WORLD-> message.reply("HELLO WORLD FROM KOTLIN")
else-> {
logger.error("Unable to handle operation {}", operation)
message.reply("Unsupported operation")
}
}
} catch (ex:Exception) {
logger.error("Unable to handle operation due to exception"+ message.body(), ex)
}
}

}

Step 5 Using Vertx Service Proxies

$
0
0
When you compose a vert.x application, you may want to divvy your applications into services. Using the event bus to do this, forces us to write tons of boilerplate code. In Vertx 3, you can instead use service proxies, which allow you to create code that looks more like a service and less like tons of callback registry with the event bus. This is the main reason they created Vertx service proxies so that you could easily create services that were invokable via the event bus that can be exposed to other JVM languages, browsers or node.js. You can read more about that in the vert.x docs on service proxies.
With Verx Service proxies, you define a Java interface containing methods following the async pattern using Vertx Handler interface, and the common types which are listed on the home page of our wiki and in the vertx docs.
Vertx will use the event bus to invoke our service and get the response back. Vertx will also code generate clients in whatever language you want. All you have to do is include the corresponding artifact vertx-lang, and Vertx will generated the client stubs for that language.
Supported languages:
  • vertx-lang-ceylon Ceylon (supported by vertx team)
  • vertx-lang-ruby Ruby (supported by vertx team)
  • vertx-lang-js JavaScript (supported by vertx team)
  • vertx-lang-groovy Groovy (supported by vertx team)
  • vertx-lang-java Java (supported by vertx team)
  • vertx-lang-jruby JRuby (supported by vertx team)
  • Kotlin support
  • More languages are listed Vertx awesome

Adding service proxy

Add new dependencies to build.gradle

dependencies {
compile "io.vertx:vertx-core:3.2.0"
compile 'ch.qos.logback:logback-core:1.1.3'
compile 'ch.qos.logback:logback-classic:1.1.3'
compile 'org.slf4j:slf4j-api:1.7.12'
compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.0-beta-4584'
compile 'io.vertx:vertx-tcp-eventbus-bridge:3.2.0'
compile 'io.vertx:vertx-web:3.2.0'
compile 'io.vertx:vertx-service-proxy:3.2.0'//NEW Vertx Proxy code
compile 'io.vertx:vertx-lang-js:3.2.0'//NEW FOR JS code gen
compile 'io.vertx:vertx-codegen:3.2.0'//NEW Vertx code gen
testCompile group:'junit', name:'junit', version:'4.11'
}
Next we need to add the vertx proxy generation support to gradle.

Add new sourceset for generated code to build.gradle

sourceSets {
generated{
java.srcDir "${projectDir}/src/generated/java"
}
}

Create new gradle task to generate Java proxy code to build.gradle

task generateProxies(type:JavaCompile, group:'build', 
description:'Generates the Vertx proxies') {
source = sourceSets.main.java // input source set
classpath = configurations.compile //+ configurations.vertx // add processor module to classpath
// specify javac arguments
options.compilerArgs = [
"-proc:only",
"-processor", "io.vertx.codegen.CodeGenProcessor", // vertx processor here
"-AoutputDirectory=${projectDir}/src/main"
]
// specify output of generated code
destinationDir = file("${projectDir}/src/generated/java")
}

Update the compileJava built-in task to call generateProxies task in build.gradle

compileJava{
dependsOn(generateProxies)
source += sourceSets.generated.java
// specify javac arguments
options.compilerArgs = [
"-Acodetrans.output=${projectDir}/src/main"
]
}
In the compileJava, we added compilerArgs which will kick off the JavaScript code generation. The generateProxies creates the Java source files that we need for the service proxy, and the compileJava task generates the JavaScript that we need for the browser andnode.js. (I think.)

HelloWorldServiceInterface.java The interface to our service.

packagecom.github.vertx.node.example;


importio.vertx.codegen.annotations.ProxyClose;
importio.vertx.codegen.annotations.ProxyGen;
importio.vertx.codegen.annotations.VertxGen;

importio.vertx.core.AsyncResult;
importio.vertx.core.Handler;
importio.vertx.core.Vertx;
importio.vertx.serviceproxy.ProxyHelper;

@ProxyGen
@VertxGen
publicinterfaceHelloWorldServiceInterface {



/**
* Create a HelloWorldServiceInterface implementation.
* @param vertx vertx
* @return HelloWorldServiceInterface
*/
staticHelloWorldServiceInterfacecreate(finalVertxvertx) {
returnnewHelloWorldServiceImpl();
}


staticHelloWorldServiceInterfacecreateProxy(finalVertxvertx,
finalStringaddress) {
returnProxyHelper.createProxy(HelloWorldServiceInterface.class, vertx, address);

}

// Actual service operations here...
voidhello(finalStringmessage,
finalHandler<AsyncResult<String>>resultHandler);


@ProxyClose
voidclose();
}
Notice the @ProxyGen and @VertxGen, these are used to generated client code and server binding code for this interface. The @ProxyClose is used to denote that a call to this method will close the remote connection to the event bus.
For the code generation to work you need a package-info.java as follows:

package-info.java

@ModuleGen(groupPackage ="com.github.vertx.node.example", name ="hello-module")
packagecom.github.vertx.node.example;
importio.vertx.codegen.annotations.ModuleGen;
The @ModuleGen annotation specifies group package and the name of the artifact.
Notice that we have to specify the module name and the group.
The implementation of the HelloWorldService is as follows:

HelloWorldServiceInterface

packagecom.github.vertx.node.example;

importio.vertx.core.AsyncResult;
importio.vertx.core.Future;
importio.vertx.core.Handler;


publicclassHelloWorldServiceImplimplementsHelloWorldServiceInterface {

@Override
publicvoidhello(finalStringmessage, finalHandler<AsyncResult<String>>resultHandler) {

resultHandler.handle(Future.succeededFuture("Hello World! "+ message));

}

@Override
publicvoidclose() {

}
}
At this point the service is really simple. Refer to the vert.x docs on service proxies to see all of the supported types.

HelloWorldServiceImpl.java implementation of our service

packagecom.github.vertx.node.example;

importio.vertx.core.AsyncResult;
importio.vertx.core.Future;
importio.vertx.core.Handler;


publicclassHelloWorldServiceImplimplementsHelloWorldServiceInterface {

@Override
publicvoidhello(finalStringmessage, finalHandler<AsyncResult<String>>resultHandler) {

resultHandler.handle(Future.succeededFuture("Hello World! "+ message));

}

@Override
publicvoidclose() {

}
}
The class HelloWorldServiceImpl does not have a lot of boiler plate code that you would usually expect from using the event bus. The boiler plate code is still there but it is in the generated classes, which we will show at the end.

Register and use the HellowWorldService

Next we want to register and use the HelloWorldService as follows.

Define an address - WebVerticle.java

...
publicclassWebVerticleextendsAbstractVerticle {

publicstaticfinalStringHELLO_WORLD_SERVICE="hello.world";
privateHelloWorldServiceInterface helloWorldServiceInterface;

Create and register the implementation - WebVerticle.java

...
publicclassWebVerticleextendsAbstractVerticle {
...

@Override
publicvoidstart() {

/* Create the hello world service. */
finalHelloWorldServiceImpl helloWorldService =newHelloWorldServiceImpl();

/* Register the proxy implementation. */
ProxyHelper.registerService(HelloWorldServiceInterface.class, vertx,
helloWorldService, HELLO_WORLD_SERVICE);
The call to ProxyHelper.registerService register this service implementation under the address HELLO_WORLD_SERVICE, i.e., "hello.world".

Create the proxy interface - WebVerticle.java

...
publicclassWebVerticleextendsAbstractVerticle {
...

@Override
publicvoidstart() {
...
/* Create the proxy interface to HelloWorldService. */
helloWorldServiceInterface =ProxyHelper.createProxy(HelloWorldServiceInterface.class, vertx,
HELLO_WORLD_SERVICE);
The above creates a proxy to the service. When you call methods on this proxy, it will put messages on the event bus that correspond to method calls on the HelloWorldService.

Register the new handler - WebVerticle.java

...
publicclassWebVerticleextendsAbstractVerticle {
...

@Override
publicvoidstart() {
...

/* Register a new handler that uses the proxy. */
router.route("/hello-world/*").handler(event ->
handleCallToHelloWorldProxy(event.request()));

Open up the event bus to the event bus bridge using the address - WebVerticle.java

...
publicclassWebVerticleextendsAbstractVerticle {
...

@Override
publicvoidstart() {
...

/* Allow Hello World service to be exposed to Node.js.
* Also add Add the new Hello World proxy. */
finalBridgeOptions options =newBridgeOptions()
...
.addInboundPermitted(newPermittedOptions().setAddress(HELLO_WORLD_SERVICE))
.addOutboundPermitted(newPermittedOptions().setAddress(HELLO_WORLD_SERVICE));
The above allows inbound and outbound communication with the HelloWorldService although at this point, we are just implementing inbound.

Call the service proxy interface from the new handler - WebVerticle.java

...
publicclassWebVerticleextendsAbstractVerticle {
...

/**
* Handle call to hello world service proxy from REST
* @param httpRequest httpRequest
*/
privatevoidhandleCallToHelloWorldProxy(finalHttpServerRequesthttpRequest) {

/** Call the service proxy interface for Hello World. */
helloWorldServiceInterface.hello(httpRequest.getParam("msg"), response -> {
if (response.succeeded()) {
logger.debug("Successfully invoked HelloWorldService Proxy to service {}",
response.result());
httpRequest.response().end(response.result());
} else {

logger.error("Can't send message to hello world service",
response.cause());
//noinspection ThrowableResultOfMethodCallIgnored
httpRequest.response().setStatusCode(500).end(
response.cause().getMessage());
}
});
}
Here is the whole class.

WebVerticle.java

packagecom.github.vertx.node.example;

importio.vertx.core.AbstractVerticle;
importio.vertx.core.AsyncResult;
importio.vertx.core.Handler;
importio.vertx.core.http.HttpServerRequest;
importio.vertx.ext.web.Router;
importio.vertx.ext.web.handler.sockjs.BridgeOptions;
importio.vertx.ext.web.handler.sockjs.PermittedOptions;
importio.vertx.ext.web.handler.sockjs.SockJSHandler;
importio.vertx.serviceproxy.ProxyHelper;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;

/**
* This gets started by the MainVerticle.
* It configures the event bus bridge and install the REST routes.
*/
publicclassWebVerticleextendsAbstractVerticle {

publicstaticfinalStringHELLO_WORLD_SERVICE="hello.world";
privatefinalLogger logger =LoggerFactory.getLogger(WebVerticle.class);


privateHelloWorldServiceInterface helloWorldServiceInterface;

@Override
publicvoidstart() {


/* Create the hello world service. */
finalHelloWorldServiceImpl helloWorldService =newHelloWorldServiceImpl();

/* Register the proxy implementation. */
ProxyHelper.registerService(HelloWorldServiceInterface.class, vertx,
helloWorldService, HELLO_WORLD_SERVICE);

/* Create vertx web router. */
finalRouter router =Router.router(vertx);

/* Create the proxy interface to HelloWorldService. */
helloWorldServiceInterface =ProxyHelper.createProxy(HelloWorldServiceInterface.class, vertx,
HELLO_WORLD_SERVICE);


/* Install our original "REST" handler at the /hello/ uri. */
router.route("/hello/*").handler(event -> handleHttpRequestToHelloWorld(event.request()));

/* Register a new handler that uses the proxy. */
router.route("/hello-world/*").handler(event -> handleCallToHelloWorldProxy(event.request()));



/* Allow Hello World service to be exposed to Node.js.
* Also add Add the new Hello World proxy. */
finalBridgeOptions options =newBridgeOptions()
.addInboundPermitted(
newPermittedOptions().setAddress(Services.HELLO_WORLD.toString()))
.addInboundPermitted(newPermittedOptions().setAddress(HELLO_WORLD_SERVICE))
.addOutboundPermitted(newPermittedOptions().setAddress(HELLO_WORLD_SERVICE));

/* Configure bridge at this HTTP/WebSocket URI. */
router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(options));

/* Install router into vertx. */
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(8080);
}

/**
* Handle call to hello world service proxy from REST
* @param httpRequest httpRequest
*/
privatevoidhandleCallToHelloWorldProxy(finalHttpServerRequesthttpRequest) {

/** Call the service proxy interface for Hello World. */
helloWorldServiceInterface.hello(httpRequest.getParam("msg"), response -> {
if (response.succeeded()) {
logger.debug("Successfully invoked HelloWorldService Proxy to service {}", response.result());
httpRequest.response().end(response.result());
} else {

logger.error("Can't send message to hello world service", response.cause());
//noinspection ThrowableResultOfMethodCallIgnored
httpRequest.response().setStatusCode(500).end(response.cause().getMessage());
}
});
}

/** This REST endpoint if for hello.
* It invokes the hello world service via the event bus.
* @param httpRequest HTTP request from vertx.
*/
privatevoidhandleHttpRequestToHelloWorld(finalHttpServerRequesthttpRequest) {

/* Invoke using the event bus. */
vertx.eventBus().send(Services.HELLO_WORLD.toString(),
HelloWorldOperations.SAY_HELLO_WORLD.toString(), response -> {

/* If the response was successful, this means we were able to execute the operation on
the HelloWorld service.
Pass the results to the http request's response.
*/
if (response.succeeded()) {
/* Send the result to the http connection. */
logger.debug("Successfully invoked HelloWorld service {}", response.result().body());
httpRequest.response().end(response.result().body().toString());
} else {
logger.error("Can't send message to hello world service", response.cause());
//noinspection ThrowableResultOfMethodCallIgnored
httpRequest.response().setStatusCode(500).end(response.cause().getMessage());
}
});
}
}

Code generation for Java

The generateProxies task that we defined gets used to generate the Java client side and Java server side bindings for our service. The code gets put into ${projectDir}/src/generated/javawhich we also add to our classpath so that it gets included with our fat jar.

HelloWorldServiceInterfaceVertxEBProxy.java - GENERATED CODE

/*
* Copyright 2014 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

packagecom.github.vertx.node.example;

importcom.github.vertx.node.example.HelloWorldServiceInterface;
importio.vertx.core.eventbus.DeliveryOptions;
importio.vertx.core.Vertx;
importio.vertx.core.Future;
importio.vertx.core.json.JsonObject;
importio.vertx.core.json.JsonArray;
importjava.util.ArrayList;
importjava.util.HashSet;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.stream.Collectors;
importjava.util.function.Function;
importio.vertx.serviceproxy.ProxyHelper;
importio.vertx.core.Vertx;
importio.vertx.core.AsyncResult;
importio.vertx.core.Handler;
importcom.github.vertx.node.example.HelloWorldServiceInterface;

/*
Generated Proxy code - DO NOT EDIT
@author Roger the Robot
*/
publicclassHelloWorldServiceInterfaceVertxEBProxyimplementsHelloWorldServiceInterface {

privateVertx _vertx;
privateString _address;
privateDeliveryOptions _options;
privateboolean closed;

publicHelloWorldServiceInterfaceVertxEBProxy(Vertxvertx, Stringaddress) {
this(vertx, address, null);
}

publicHelloWorldServiceInterfaceVertxEBProxy(Vertxvertx, Stringaddress, DeliveryOptionsoptions) {
this._vertx = vertx;
this._address = address;
this._options = options;
}

publicvoidhello(Stringmessage, Handler<AsyncResult<String>>resultHandler) {
if (closed) {
resultHandler.handle(Future.failedFuture(newIllegalStateException("Proxy is closed")));
return;
}
JsonObject _json =newJsonObject();
_json.put("message", message);
DeliveryOptions _deliveryOptions = (_options !=null) ?newDeliveryOptions(_options) :newDeliveryOptions();
_deliveryOptions.addHeader("action", "hello");
_vertx.eventBus().<String>send(_address, _json, _deliveryOptions, res -> {
if (res.failed()) {
resultHandler.handle(Future.failedFuture(res.cause()));
} else {
resultHandler.handle(Future.succeededFuture(res.result().body()));
}
});
}

publicvoidclose() {
if (closed) {
thrownewIllegalStateException("Proxy is closed");
}
closed =true;
JsonObject _json =newJsonObject();
DeliveryOptions _deliveryOptions = (_options !=null) ?newDeliveryOptions(_options) :newDeliveryOptions();
_deliveryOptions.addHeader("action", "close");
_vertx.eventBus().send(_address, _json, _deliveryOptions);
}


privateList<Character>convertToListChar(JsonArrayarr) {
List<Character> list =newArrayList<>();
for (Object obj: arr) {
Integer jobj = (Integer)obj;
list.add((char)(int)jobj);
}
return list;
}

privateSet<Character>convertToSetChar(JsonArrayarr) {
Set<Character> set =newHashSet<>();
for (Object obj: arr) {
Integer jobj = (Integer)obj;
set.add((char)(int)jobj);
}
return set;
}

private<T>Map<String, T>convertMap(Mapmap) {
if (map.isEmpty()) {
return (Map<String, T>) map;
}

Object elem = map.values().stream().findFirst().get();
if (!(elem instanceofMap) &&!(elem instanceofList)) {
return (Map<String, T>) map;
} else {
Function<Object, T> converter;
if (elem instanceofList) {
converter = object -> (T) newJsonArray((List) object);
} else {
converter = object -> (T) newJsonObject((Map) object);
}
return ((Map<String, T>) map).entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, converter::apply));
}
}
private<T>List<T>convertList(Listlist) {
if (list.isEmpty()) {
return (List<T>) list;
}

Object elem = list.get(0);
if (!(elem instanceofMap) &&!(elem instanceofList)) {
return (List<T>) list;
} else {
Function<Object, T> converter;
if (elem instanceofList) {
converter = object -> (T) newJsonArray((List) object);
} else {
converter = object -> (T) newJsonObject((Map) object);
}
return (List<T>) list.stream().map(converter).collect(Collectors.toList());
}
}
private<T>Set<T>convertSet(Listlist) {
returnnewHashSet<T>(convertList(list));
}
}

HelloWorldServiceInterfaceVertxProxyHandler.java - GENERATED CODE

/*
* Copyright 2014 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

packagecom.github.vertx.node.example;

importcom.github.vertx.node.example.HelloWorldServiceInterface;
importio.vertx.core.Vertx;
importio.vertx.core.Handler;
importio.vertx.core.AsyncResult;
importio.vertx.core.eventbus.EventBus;
importio.vertx.core.eventbus.Message;
importio.vertx.core.eventbus.MessageConsumer;
importio.vertx.core.eventbus.DeliveryOptions;
importio.vertx.core.eventbus.ReplyException;
importio.vertx.core.json.JsonObject;
importio.vertx.core.json.JsonArray;
importjava.util.Collection;
importjava.util.ArrayList;
importjava.util.HashSet;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importjava.util.UUID;
importjava.util.stream.Collectors;
importio.vertx.serviceproxy.ProxyHelper;
importio.vertx.serviceproxy.ProxyHandler;
importio.vertx.core.Vertx;
importio.vertx.core.AsyncResult;
importio.vertx.core.Handler;
importcom.github.vertx.node.example.HelloWorldServiceInterface;

/*
Generated Proxy code - DO NOT EDIT
@author Roger the Robot
*/
publicclassHelloWorldServiceInterfaceVertxProxyHandlerextendsProxyHandler {

publicstaticfinallongDEFAULT_CONNECTION_TIMEOUT=5*60; // 5 minutes

privatefinalVertx vertx;
privatefinalHelloWorldServiceInterface service;
privatefinallong timerID;
privatelong lastAccessed;
privatefinallong timeoutSeconds;

publicHelloWorldServiceInterfaceVertxProxyHandler(Vertxvertx, HelloWorldServiceInterfaceservice) {
this(vertx, service, DEFAULT_CONNECTION_TIMEOUT);
}

publicHelloWorldServiceInterfaceVertxProxyHandler(Vertxvertx, HelloWorldServiceInterfaceservice, longtimeoutInSecond) {
this(vertx, service, true, timeoutInSecond);
}

publicHelloWorldServiceInterfaceVertxProxyHandler(Vertxvertx, HelloWorldServiceInterfaceservice, booleantopLevel, longtimeoutSeconds) {
this.vertx = vertx;
this.service = service;
this.timeoutSeconds = timeoutSeconds;
if (timeoutSeconds !=-1&&!topLevel) {
long period = timeoutSeconds *1000/2;
if (period >10000) {
period =10000;
}
this.timerID = vertx.setPeriodic(period, this::checkTimedOut);
} else {
this.timerID =-1;
}
accessed();
}

publicMessageConsumer<JsonObject>registerHandler(Stringaddress) {
MessageConsumer<JsonObject> consumer = vertx.eventBus().<JsonObject>consumer(address).handler(this);
this.setConsumer(consumer);
return consumer;
}

privatevoidcheckTimedOut(longid) {
long now =System.nanoTime();
if (now - lastAccessed > timeoutSeconds *1000000000) {
service.close();
close();
}
}

@Override
publicvoidclose() {
if (timerID !=-1) {
vertx.cancelTimer(timerID);
}
super.close();
}

privatevoidaccessed() {
this.lastAccessed =System.nanoTime();
}

publicvoidhandle(Message<JsonObject>msg) {
try {
JsonObject json = msg.body();
String action = msg.headers().get("action");
if (action ==null) {
thrownewIllegalStateException("action not specified");
}
accessed();
switch (action) {


case"hello": {
service.hello((java.lang.String)json.getValue("message"), createHandler(msg));
break;
}
case"close": {
service.close();
close();
break;
}
default: {
thrownewIllegalStateException("Invalid action: "+ action);
}
}
} catch (Throwable t) {
msg.fail(-1, t.getMessage());
throw t;
}
}

private<T>Handler<AsyncResult<T>>createHandler(Messagemsg) {
return res -> {
if (res.failed()) {
msg.fail(-1, res.cause().getMessage());
} else {
if (res.result() !=null&& res.result().getClass().isEnum()) { msg.reply(((Enum) res.result()).name()); } else { msg.reply(res.result()); } }
};
}

private<T>Handler<AsyncResult<List<T>>>createListHandler(Messagemsg) {
return res -> {
if (res.failed()) {
msg.fail(-1, res.cause().getMessage());
} else {
msg.reply(newJsonArray(res.result()));
}
};
}

private<T>Handler<AsyncResult<Set<T>>>createSetHandler(Messagemsg) {
return res -> {
if (res.failed()) {
msg.fail(-1, res.cause().getMessage());
} else {
msg.reply(newJsonArray(newArrayList<>(res.result())));
}
};
}

privateHandler<AsyncResult<List<Character>>>createListCharHandler(Messagemsg) {
return res -> {
if (res.failed()) {
msg.fail(-1, res.cause().getMessage());
} else {
JsonArray arr =newJsonArray();
for (Character chr: res.result()) {
arr.add((int) chr);
}
msg.reply(arr);
}
};
}

privateHandler<AsyncResult<Set<Character>>>createSetCharHandler(Messagemsg) {
return res -> {
if (res.failed()) {
msg.fail(-1, res.cause().getMessage());
} else {
JsonArray arr =newJsonArray();
for (Character chr: res.result()) {
arr.add((int) chr);
}
msg.reply(arr);
}
};
}

private<T>Map<String, T>convertMap(Mapmap) {
return (Map<String, T>)map;
}

private<T>List<T>convertList(Listlist) {
return (List<T>)list;
}

private<T>Set<T>convertSet(Listlist) {
returnnewHashSet<T>((List<T>)list);
}
}

Code generation for JavaScript

The compileJava generates the JavaScript for this example when we specify "-Acodetrans.output=${projectDir}/src/main".

hello_world_service_interface.js - GENERATED CODE

/*
* Copyright 2014 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

/** @module hello-module-js/hello_world_service_interface */
var utils =require('vertx-js/util/utils');
var Vertx =require('vertx-js/vertx');

var io =Packages.io;
var JsonObject =io.vertx.core.json.JsonObject;
var JHelloWorldServiceInterface =com.github.vertx.node.example.HelloWorldServiceInterface;

/**
@class
*/
varHelloWorldServiceInterface=function(j_val) {

var j_helloWorldServiceInterface = j_val;
var that =this;

/**

@public
@param message {string}
@param resultHandler {function}
*/
this.hello=function(message, resultHandler) {
var __args = arguments;
if (__args.length===2&&typeof __args[0] ==='string'&&typeof __args[1] ==='function') {
j_helloWorldServiceInterface["hello(java.lang.String,io.vertx.core.Handler)"](message, function(ar) {
if (ar.succeeded()) {
resultHandler(ar.result(), null);
} else {
resultHandler(null, ar.cause());
}
});
} elsethrownewTypeError('function invoked with invalid arguments');
};

/**

@public

*/
this.close=function() {
var __args = arguments;
if (__args.length===0) {
j_helloWorldServiceInterface["close()"]();
} elsethrownewTypeError('function invoked with invalid arguments');
};

// A reference to the underlying Java delegate
// NOTE! This is an internal API and must not be used in user code.
// If you rely on this property your code is likely to break if we change it / remove it without warning.
this._jdel= j_helloWorldServiceInterface;
};

/**
Create a HelloWorldServiceInterface implementation.

@memberof module:hello-module-js/hello_world_service_interface
@param vertx {Vertx} vertx
@return {HelloWorldServiceInterface} HelloWorldServiceInterface
*/
HelloWorldServiceInterface.create=function(vertx) {
var __args = arguments;
if (__args.length===1&&typeof __args[0] ==='object'&& __args[0]._jdel) {
returnutils.convReturnVertxGen(JHelloWorldServiceInterface["create(io.vertx.core.Vertx)"](vertx._jdel), HelloWorldServiceInterface);
} elsethrownewTypeError('function invoked with invalid arguments');
};

/**

@memberof module:hello-module-js/hello_world_service_interface
@param vertx {Vertx}
@param address {string}
@return {HelloWorldServiceInterface}
*/
HelloWorldServiceInterface.createProxy=function(vertx, address) {
var __args = arguments;
if (__args.length===2&&typeof __args[0] ==='object'&& __args[0]._jdel&&typeof __args[1] ==='string') {
returnutils.convReturnVertxGen(JHelloWorldServiceInterface["createProxy(io.vertx.core.Vertx,java.lang.String)"](vertx._jdel, address), HelloWorldServiceInterface);
} elsethrownewTypeError('function invoked with invalid arguments');
};

// We export the Constructor function
module.exports= HelloWorldServiceInterface;

hello_world_service_interface.js - GENERATED CODE

/*
* Copyright 2014 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

/** @module hello-module-js/hello_world_service_interface */
!function (factory) {
if (typeof require ==='function'&&typeofmodule!=='undefined') {
factory();
} elseif (typeof define ==='function'&&define.amd) {
// AMD loader
define('hello-module-js/hello_world_service_interface-proxy', [], factory);
} else {
// plain old include
HelloWorldServiceInterface =factory();
}
}(function () {

/**
@class
*/
varHelloWorldServiceInterface=function(eb, address) {

var j_eb = eb;
var j_address = address;
var closed =false;
var that =this;
varconvCharCollection=function(coll) {
var ret = [];
for (var i =0;i <coll.length;i++) {
ret.push(String.fromCharCode(coll[i]));
}
return ret;
};

/**

@public
@param message {string}
@param resultHandler {function}
*/
this.hello=function(message, resultHandler) {
var __args = arguments;
if (__args.length===2&&typeof __args[0] ==='string'&&typeof __args[1] ==='function') {
if (closed) {
thrownewError('Proxy is closed');
}
j_eb.send(j_address, {"message":__args[0]}, {"action":"hello"}, function(err, result) { __args[1](err, result &&result.body); });
return;
} elsethrownewTypeError('function invoked with invalid arguments');
};

/**

@public

*/
this.close=function() {
var __args = arguments;
if (__args.length===0) {
if (closed) {
thrownewError('Proxy is closed');
}
j_eb.send(j_address, {}, {"action":"close"});
closed =true;
return;
} elsethrownewTypeError('function invoked with invalid arguments');
};

};

/**
Create a HelloWorldServiceInterface implementation.

@memberof module:hello-module-js/hello_world_service_interface
@param vertx {Vertx} vertx
@return {HelloWorldServiceInterface} HelloWorldServiceInterface
*/
HelloWorldServiceInterface.create=function(vertx) {
var __args = arguments;
if (__args.length===1&&typeof __args[0] ==='object'&& __args[0]._jdel) {
if (closed) {
thrownewError('Proxy is closed');
}
j_eb.send(j_address, {"vertx":__args[0]}, {"action":"create"});
return;
} elsethrownewTypeError('function invoked with invalid arguments');
};

/**

@memberof module:hello-module-js/hello_world_service_interface
@param vertx {Vertx}
@param address {string}
@return {HelloWorldServiceInterface}
*/
HelloWorldServiceInterface.createProxy=function(vertx, address) {
var __args = arguments;
if (__args.length===2&&typeof __args[0] ==='object'&& __args[0]._jdel&&typeof __args[1] ==='string') {
if (closed) {
thrownewError('Proxy is closed');
}
j_eb.send(j_address, {"vertx":__args[0], "address":__args[1]}, {"action":"createProxy"});
return;
} elsethrownewTypeError('function invoked with invalid arguments');
};

if (typeofexports!=='undefined') {
if (typeofmodule!=='undefined'&&module.exports) {
exports=module.exports= HelloWorldServiceInterface;
} else {
exports.HelloWorldServiceInterface= HelloWorldServiceInterface;
}
} else {
return HelloWorldServiceInterface;
}
});

Running this

To get the Java service proxy to run, run the application.

Run MainVerticle

$ gradle clean shadowJar
$ find . -name "*.jar"
$ java -jar ./build/libs/vertx-1.0-SNAPSHOT-fat.jar
Now hit the REST end point that calls the HelloWorld service proxy through the HTTP end point.

Hit the REST end point which calls the HelloWorldService proxy

$ curl http://localhost:8080/hello-world/foo?msg=Rick
Hello World! Rick

Step 6 Using ZooKeeper as the cluster manager

$
0
0
At some point we decided we are going to deploy to EC2/Mesos, and we will connect the Vertx nodes using ZooKeeper.
We just want to validate that this works. We will do that by starting two instances of our app and sending a broadcast message on the eventBus and making sure both nodes got the message.
You can find the source for this step here.

Installing Zookeeper

Install Zookeeper

$ brew install zookeeper

Run ZooKeeper

$ zkServer start
To do this, we will add an extra REST end point to our example app.

WebVerticle.java

...
publicclassWebVerticleextendsAbstractVerticle {

/* Register a new handler to send broadcast message. */
router.route("/broadcast/*").handler(event -> broadcastHello(event.request()));

...

private void broadcastHello(HttpServerRequest request) {
vertx.eventBus().publish(Services.HELLO_WORLD.toString(),
HelloWorldOperations.BROADCAST_HELLO_TO_ALL_NODES.toString());

request.response().end("SENT BROADCAST");
}
The eventBus.publish will publish a message to all connected nodes.
Then we handle the event bus message in the first example that we created

HelloWorldService.kt Kotlin

...
private fun dispatchMessage(message:Message<Any>) {

try {
val operation =HelloWorldOperations.valueOf(message.body().toString())

/** Switch statement that handles various operations. */
when (operation) {
HelloWorldOperations.SAY_HELLO_WORLD-> message.reply("HELLO WORLD FROM KOTLIN")

HelloWorldOperations.BROADCAST_HELLO_TO_ALL_NODES-> println("GOT HELLO BROADCAST!!!!!!!!!!!!!")
else-> {
logger.error("Unable to handle operation {}", operation)
message.reply("Unsupported operation")
}
}
...
Pretty simple. We will start up two instances and then send a

Send a broadcast message

 $ curl http://localhost:8080/broadcast/
We should see a broadcast debug message in both.

Output from both nodes

GOT HELLO BROADCAST!!!!!!!!!!!!!
Note the second node that we start will complain about not being able to bind to port 8080. This is ok. We are communicating with the clustered nodes with the vertx event bus.
Before we can run this, we need to install vertx's zookeeper support which is not yet distributed with vertx.

Installing vertx zookeeper Cluster Manager

You will need to check out vertx zookeeper support and build it.
Clone the vertx zookeeper repo and then build it with maven as follows.
$ git clone https://github.com/vert-x3/vertx-zookeeper.git
$ cd vertx-zookeeper
$ mvn clean install -Dmaven.test.skip=true

Modify gradle build to use zookeeper

We will need to add this as a dependency of our project and create a script that can run the shadow jar file with the cluster options.

gradle.build adding dependency

dependencies {
compile "io.vertx:vertx-core:3.2.0"
...
compile 'io.vertx:vertx-zookeeper:3.2.0-SNAPSHOT'//ADDED THIS
testCompile group:'junit', name:'junit', version:'4.11'
}
Then we will need to write up a script that knows how to run vertx shadow jar for this project with the Zookeeper cluster manager installed.

gradle.build run shadow jar file with ZookeeperClusterManager installed

task runShadowJar(dependsOn: shadowJar,  description:'Runs the system executing the shadow jar.')  {
def arguments = ["$buildDir/libs/$project.name-$version-fat.jar", "-cluster"]
def jvmArguments = ["-Dvertx.cluster.managerClass=io.vertx.spi.cluster.impl.zookeeper.ZookeeperClusterManager"]

javaexec {
main ="-jar"
jvmArgs jvmArguments
args arguments
}
}
We also added a main method to MainService that configures the ZookeeperClusterManager so you can run this from your IDE.

Running from IDE

fun main(args:Array<String>) {


val logger =LoggerFactory.getLogger(MainVerticle::class.java)
val zkConfig = Properties();
zkConfig.setProperty("hosts.zookeeper", "127.0.0.1");
zkConfig.setProperty("path.root", "io.vertx");
zkConfig.setProperty("retry.initialSleepTime", "1000");
zkConfig.setProperty("retry.intervalTimes", "3");

val clusterManager = ZookeeperClusterManager(zkConfig);
val options = VertxOptions().setClusterManager(clusterManager);

Vertx.clusteredVertx(options) { response ->
if (response.succeeded()) {
val vertx = response.result();
vertx.deployVerticle(MainVerticle())
logger.info("Main is deployed")
} else {
logger.error("Issue deploying main", response.cause())
}
}


}
Later we will setup -Dvertx.zookeeper.conf=/somepath/zookeeper.properties so we can specify how we want to contact our Zookeeper cluster.

Run the example

Run this once

$ gradle runShadowJar 

Run this again in a new terminal

$ gradle runShadowJar 

Run the endpoint that does the broadcast

$ curl http://localhost:8080/broadcast/

Output from both vertx instances

GOT HELLO BROADCAST!!!!!!!!!!!!!

Step 7 Adding docker support to gradle and deploying our image to Mesos Marathon

$
0
0
This step was done after adding zookeeper clustering, but it depends on version of the branch that predates the zookeeper cluster manager. We do this because the zookeeper support is not in the maven public repo yet, and we want the master branch build to work.
The code for this is in this branch.

Adding Docker Support Begin with the end in mind.

We want to be able to launch this service via Docker as follows:

Build and install docker instance

 $ gradle clean shadowJar buildDocker

Run docker instance

 $ docker run advantageous/vertx:1.0

Test the instance with curl

$ echo$DOCKER_HOST
tcp://192.168.99.100:2376

$ curl http://192.168.99.100:5000/hello-world/?msg="rick"
Hello World! rick

Setup docker on your dev box (assuming OSX, adjust accordingly)

There is an installation guide to using docker on OSX. You will need to install the Docker Toolbox, and run through some of the tutorial (even if you used boot2docker before as a lot has changed). Once you feel comfortable with building and deploying images for Docker come back here.

Add docker support to gradle

We will need to add the gradle docker plugin.
buildscript {
repositories {
mavenCentral()
jcenter() //ADDED THIS
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.0-beta-4584'
classpath 'se.transmode.gradle:gradle-docker:1.2'//ADDED THIS
}
}

Use the docker plugin

Apply plugin

apply plugin:'docker'

Use plugin

group 'advantageous'//changed this
version '1.0'//and this

docker {
baseImage "vertx/vertx3-exec"
maintainer 'Rick Hightower "richardhightowerATgmailDOTcom"'



}

Create build task to build and install docker

task buildDocker(type:Docker) {

tagVersion =System.getenv("BUILD_NUMBER") ?: project.version
push =Boolean.getBoolean("docker.push")
applicationName = project.applicationName

addFile {
from "${project.shadowJar.outputs.files.singleFile}"
into "/opt/hello/"
}
exposePort 8080
entryPoint = ["sh", "-c"]
defaultCommand = ["java -jar /opt/hello/${project.name}-${project.version}-fat.jar"]

}
The above will both create the Dockerfile and install docker in your local docker instance. It assumes docker is setup correctly on your build box.

Contents of generated Dockerfile

cat ./build/docker/Dockerfile 
FROM vertx/vertx3-exec
MAINTAINER Rick Hightower "richardhightowerATgmailDOTcom"
ADD add_1.tar /
EXPOSE 8080
ENTRYPOINT ["sh", "-c"]
CMD ["java -jar /opt/hello/vertx-1.0-fat.jar"]

Deploying to Mesos / Marathon

We setup a Mesos master and two Mesos Slaves with Marathon and Zookeeper all up in our private EC2 VPC using this guide. We also setup an DockerHub public organization calledadvantageous.
This should work:

Forcing push to DockerHub

gradle -Ddocker.push=true clean shadowJar buildDocker
But instead it takes FOREVER.

Forcing push to DockerHub

gradle  clean shadowJar buildDocker
docker push advantageous/vertx-node-eventbus-example
Note I changed the name of the Docker container from vertx to vertx-node-eventbus-example.

Deploying to Mesos / Marathon Create deploy file

 cat VertxNodeDocker.json 
{
"container": {
"type": "DOCKER",
"docker": {
"image": "advantageous/vertx-node-eventbus-example:1.0"
}
},
"id": "myimage",
"instances": 1,
"cpus": 0.5,
"mem": 512,
"uris": [],
"cmd": "while sleep 10; do date -u +%T; done"
}

Use curl to deploy Docker image you stored in DockerHub to Marathon/Mesos

curl -X POST -H "Content-Type: application/json" http://10.0.0.148:8080/v2/apps -d@VertxNodeDocker.json
For this we setup three Mesos instances (1 master and two slaves) in EC2 in a VPC (isolated cloud resources).
The two slaves (called Agents in Mesos 1.0) point to the one master (for QA, prod you would want at least three master mesos instances)

Slave boxes pointing to master mesos instances.

$ cat /etc/mesos/zk
zk://10.0.0.148:2181/mesos
There are plenty of guides to show how to install Meso and Docker on Linux. Follow those guides and setup your own Mesos master.

Debugging

Logs for Mesos are found in /var/log/mesos and /var/log/zookeeper.
To see a list of deployments in mesos.

List of deployments in Mesos

$ curl http://10.0.0.148:8080/v2/deployments/ | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 317 0 317 0 0 115k 0 --:--:-- --:--:-- --:--:-- 154k
[
{
"totalSteps": 2,
"currentStep": 2,
"currentActions": [
{
"app": "/myimage",
"action": "ScaleApplication"
}
],
"steps": [
[
{
"app": "/myimage",
"action": "StartApplication"
}
],
[
{
"app": "/myimage",
"action": "ScaleApplication"
}
]
],
"affectedApps": [
"/myimage"
],
"version": "2016-01-23T00:12:39.099Z",
"id": "176ae76a-3f3a-4c6f-a9b6-b539214a4ee9"
}
]
To delete an image, you hit the mesos REST endpoint as follows:

Delete a deployment

$ curl -X DELETE http://10.0.0.148:8080/v2/deployments/176ae76a-3f3a-4c6f-a9b6-b539214a4ee9 | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 92 0 92 0 0 6028 0 --:--:-- --:--:-- --:--:-- 6133
{
"deploymentId": "dd370d17-3b9e-4682-a107-3b3bd018728c",
"version": "2016-01-25T19:45:16.584Z"
}

Not working

At this point, you deployed the vertx application but it is not able to run because port 8080 is already taken by Marathon. In order to get the vertx application to work, we will need to map its port 8080 with an outside port for the Marathon agent/slave box.
We can do this by following mesos marathon docker guide and crafting a deploy script.
"portMappings": [
{ "containerPort": 8080, "hostPort": 0,
"servicePort": 9000, "protocol": "tcp" }
]
This goes into the docker area of the deploy VertxNodeDocker.json that we have been using.
{
"container": {
"type": "DOCKER",
"docker": {
"image": "advantageous/vertx-node-eventbus-example:1.0",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 8080, "hostPort": 0,
"servicePort": 9000, "protocol": "tcp" }
]
}
},
"id": "vertx-node-eventbus-example",
"instances": 1,
"cpus": 0.5,
"mem": 512,
"uris": []
}
Undeploy the older version. Then deploy this image.
You can ssh to slave boxes and run docker ps to see where it ended up.

Hooks up fine

ubuntu@ip-10-0-0-188:~$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
04e5b69a3bf9 advantageous/vertx-node-eventbus-example:1.0 "sh -c 'java -jar /op" 7 minutes ago Up 7 minutes 0.0.0.0:31627->8080/tcp mesos-00481535-158b-4812-aff3-bf02d4176ef8-S0.84d6bd59-c5cc-4c56-b2d6-e30af1ea11aa

ubuntu@ip-10-0-0-188:~$ curl http://10.0.0.188:31627/hello/
HELLO WORLD FROM KOTLIN
We looked up the ports and then used that information to make a call into the service.
To actually get this information from Marathon, we do this.

Getting service discovery information from Marathon

$ curl http://10.0.0.148:8080/v2/tasks/ | jq .
{
"tasks": [
{
"servicePorts": [
9000
],
"appId": "/vertx-node-eventbus-example",
"id": "vertx-node-eventbus-example.21e8f58e-c3aa-11e5-aace-02422bec481e",
"host": "ip-10-0-0-188.us-west-2.compute.internal",
"ipAddresses": [
{
"protocol": "IPv4",
"ipAddress": "172.17.0.2"
}
],
"ports": [
31627
],
"startedAt": "2016-01-25T21:25:19.459Z",
"stagedAt": "2016-01-25T21:25:18.576Z",
"version": "2016-01-25T20:47:56.140Z",
"slaveId": "00481535-158b-4812-aff3-bf02d4176ef8-S0"
}
]
}
Note that the port in ports is the correct docker port for our HelloWorld sample. We can curl using this host and port.

Working with host

$ curl http://ip-10-0-0-188.us-west-2.compute.internal:31627/hello/
HELLO WORLD FROM KOTLIN

Common commands I use to redeploy the example to mesos

I edit the version of the docker deploy after I make changes in the build.gradle file.

Build microserivce/docker image on my dev box and soon Jenkins

./gradlew clean shadowJar buildDocker
docker push advantageous/vertx-node-eventbus-example
After this, you will see the image on docker hub.

Deploy from any member of Amazon VPC

Edit deploy file and add version that I want to deploy

sudo nano VertxNodeDocker.json

Deploy file to deploy new version of docker image to Mesos

{
"id":"vertx-node-eventbus-example",
"instances":1,
"cpus":0.5,
"mem":512,
"uris": [],
"container": {
"type":"DOCKER",
"docker": {
"image":"advantageous/vertx-node-eventbus-example:1.0.1.3",
"network":"BRIDGE",
"portMappings": [
{ "containerPort":8080, "hostPort":0,
"servicePort":9000, "protocol":"tcp" }
]
}
}
}
I use curl to remove the old copy of the app install for this microservice from mesos.

Remove old docker container from mesos

curl -X DELETE http://10.0.0.148:8080/v2/apps/vertx-node-eventbus-example
Then I redeploy the service to mesos.

Redeploy docker image

curl -X POST -H "Content-Type: application/json" http://10.0.0.148:8080/v2/apps -d@VertxNodeDocker.json | jq .
Then I test the microservice but first I need to know where it was deployed and what port it is using.

Using mesos service discovery

curl http://10.0.0.148:8080/v2/tasks/ | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 439 0 439 0 0 92811 0 --:--:-- --:--:-- --:--:-- 107k
{
"tasks": [
{
"servicePorts": [
9000
],
"appId": "/vertx-node-eventbus-example",
"id": "vertx-node-eventbus-example.cf1d43a1-c6e5-11e5-bee9-02424e94d413",
"host": "ip-10-0-0-188.us-west-2.compute.internal",
"ipAddresses": [
{
"protocol": "IPv4",
"ipAddress": "172.17.0.2"
}
],
"ports": [
31516
],
"startedAt": "2016-01-30T00:10:10.010Z",
"stagedAt": "2016-01-30T00:10:02.932Z",
"version": "2016-01-30T00:10:02.901Z",
"slaveId": "71ad679f-18ef-4941-a912-69c6cd5adbd4-S1"
}
]
}
With the above host and port, we can now test our deploy.

Testing our deploy

 curl http://10.0.0.188:31516/hello/
HELLO WORLD FROM KOTLIN

Step 8 Setup ELK and use it from Java

$
0
0
If we are going to have a distributed application, we need a way to see the logs. Searchable logging is essential to using Docker and Mesos effectively, and is also essential for microserivce architecture. It is the monitoring that makes distributed computing not suck as much.
Let's setup ELK. ELK is logstash, Kibana, and ElasticSearch.
Following the instructions for the ELK setup, you need to monitor the log files of the Mesos master server (or servers for your production environment).
Follow those instructions to setup an ELK server.
Once you follow the ELK install instructions then add this for our Java microservices.

Config for logback to send messages.

cat /etc/logstash/conf.d/02-tcp-input.conf 
input {
tcp {
port => 4560
codec => json_lines
}
}
Then restart logstash.
sudo service logstash restart

Java setup.

Now we will use the LogStashEncoder for Logback using these instructions.
In the Java world, we need to add the jar file that plugins into Logback and sends the log files to LogStash.

build.gradle


dependencies {
compile "io.vertx:vertx-core:3.2.0"
compile 'ch.qos.logback:logback-core:1.1.3'
compile 'ch.qos.logback:logback-classic:1.1.3'
compile 'net.logstash.logback:logstash-logback-encoder:4.6' // <------- NEW
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile group: 'junit', name: 'junit', version: '4.11'
}
Note we added net.logstash.logback:logstash-logback-encoder:4.6 to the dependencies.
Next we configure the logback.xml file to the mix.

logback.xml

<configuration>

<appendername="stash"class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>10.0.0.62:4560</destination>

<!-- encoder is required -->
<encoderclass="net.logstash.logback.encoder.LogstashEncoder" />
<keepAliveDuration>5 minutes</keepAliveDuration>
</appender>

<appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<rootlevel="DEBUG">
<appender-refref="STDOUT"/>
<appender-refref="stash" />
</root>
</configuration>
Notice we added the stash appender to use the LogstashEncoder. Now use logging as you normally would.

Using logging

packagecom.github.vertx.node.example;

importio.vertx.core.AsyncResult;
importio.vertx.core.Future;
importio.vertx.core.Handler;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;


publicclassHelloWorldServiceImplimplementsHelloWorldServiceInterface {
privatefinalLogger logger =LoggerFactory.getLogger(HelloWorldServiceImpl.class);


@Override
publicvoidhello(finalStringmessage, finalHandler<AsyncResult<String>>resultHandler) {


logger.info("HelloWorldServiceImpl hello was called {}", message);
resultHandler.handle(Future.succeededFuture("Hello World! "+ message));

}

@Override
publicvoidclose() {

}
}
The source code for this step is in this branch.

Common commands I use to redeploy the example to mesos

I edit the version of the docker deploy after I make changes in the build.gradle file.

Build microserivce/docker image on my dev box and soon Jenkins

./gradlew clean shadowJar buildDocker
docker push advantageous/vertx-node-eventbus-example
After this, you will see the image on docker hub.

Deploy from any member of Amazon VPC

Edit deploy file and add version that I want to deploy

sudo nano VertxNodeDocker.json

Deploy file to deploy new version of docker image to Mesos

{
"id":"vertx-node-eventbus-example",
"instances":1,
"cpus":0.5,
"mem":512,
"uris": [],
"container": {
"type":"DOCKER",
"docker": {
"image":"advantageous/vertx-node-eventbus-example:1.0.1.3",
"network":"BRIDGE",
"portMappings": [
{ "containerPort":8080, "hostPort":0,
"servicePort":9000, "protocol":"tcp" }
]
}
}
}
I use curl to remove the old copy of the app install for this microservice from mesos.

Remove old docker container from mesos

curl -X DELETE http://10.0.0.148:8080/v2/apps/vertx-node-eventbus-example
Then I redeploy the service to mesos.

Redeploy docker image

curl -X POST -H "Content-Type: application/json" http://10.0.0.148:8080/v2/apps -d@VertxNodeDocker.json | jq .
Then I test the microservice but first I need to know where it was deployed and what port it is using.

Using mesos service discovery

curl http://10.0.0.148:8080/v2/tasks/ | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 439 0 439 0 0 92811 0 --:--:-- --:--:-- --:--:-- 107k
{
"tasks": [
{
"servicePorts": [
9000
],
"appId": "/vertx-node-eventbus-example",
"id": "vertx-node-eventbus-example.cf1d43a1-c6e5-11e5-bee9-02424e94d413",
"host": "ip-10-0-0-188.us-west-2.compute.internal",
"ipAddresses": [
{
"protocol": "IPv4",
"ipAddress": "172.17.0.2"
}
],
"ports": [
31516
],
"startedAt": "2016-01-30T00:10:10.010Z",
"stagedAt": "2016-01-30T00:10:02.932Z",
"version": "2016-01-30T00:10:02.901Z",
"slaveId": "71ad679f-18ef-4941-a912-69c6cd5adbd4-S1"
}
]
}
With the above host and port, we can now test our deploy.

Testing our deploy

 curl http://10.0.0.188:31516/hello/
HELLO WORLD FROM KOTLIN

New release. QBit Microservices Lib now supports SockJS and Kafka integration (You can invoke QBit service methods over Kafka/SockJS)

$
0
0

There is a new release of QBit microservices lib. This has the bug fixes and features that the some of our clients requested, and some new bridge tech. (included is JS and Java example of using new bridge tech).
New version just released.
compile 'io.advantageous.qbit:qbit-admin:0.9.3-M2'
compile 'io.advantageous.qbit:qbit-vertx:0.9.3-M2'
One more thing release 0.9.3-M4 or 0.9.3 (the one after this one, which will be out in less than a week), will have a nice new feature for integration with things like Kafka and SockJS. The new release has a Vertx/EventBus connector feature (code is written and tested). This will allow any app that can uses the Vertx EventBus to invoke QBit service methods over the event bus. There are connectors that connect Kafka to the Vertx EventBus, and other connectors. This effectively allows QBit service methods to be invoked from Kafka and SockJS. (Example is included.) :)
More importantly we improved the service queue dispatch and callback mechanism to make writing bridges to other forms of IO and messaging a lot easier, which give more options when streaming in method calls from reactive streams, which gives even more micorservices cred to QBit. This allows easier integration with other languages. 
By supporting this new approach, you can now invoke QBit service methods from Node / Browser using the Vertx EventBus bridge as well. Included is an JavaScript/NPM that I worked on with some other folks and some Java code to show how to do this.
You just need to send a header with the method name (using method) (part of the event bus call). The EventBus address is the QBit object address. The body of the message is a list of params to the method (in JSON). We support generic arguments (collections of Employees).

JavaScript example that talks to QBit over Vertx Event bus

It should be easy to adapt this to any programming langauge that can use the TCP Bridge Vertx EventBus bridge or the SockJS Vertx EventBus Bridge

Java service we are going to call from JavaScript/ES6

@RequestMapping("/es/1.0")
publicclassEmployeeService {

@Bridge("/employee/")
publicbooleanaddEmployee(finalEmployeeemployee) {
System.out.println(employee);
returntrue;
}

@Bridge("/employee/err/")
publicbooleanaddEmployeeError(finalEmployeeemployee) {
thrownewIllegalStateException("Employee can't be added");
}

@Bridge
publicvoidgetEmployee(finalCallback<Employee>callback, finalStringid) {
callback.returnThis(newEmployee(id, "Bob", "Jingles", 1962, 999999999));
}


@GET("/employee/")
publicvoidgetEmployeeWithParam(finalCallback<Employee>callback,
@RequestParam(value="id", required=true) finalStringid) {
callback.returnThis(newEmployee(id, "Bob", "Jingles", 1962, 999999999));
}

@Bridge
publicEmployeesingleton(finalEmployeeemployee) {
return employee;
}

@Bridge
publicbooleantwoArg(finalEmployeeemployee, booleanflag) {

return employee.getId().equals("rick") && flag;
}

@Bridge
publicList<Employee>list(finalList<Employee>employees) {
return employees;
}

serviceClient.js - NPM client lib to talk to QBit over Vertx/EventBus (next version of QBit, which will be out in a few days)

"use strict";
importloggerfrom"winston";

exportdefaultclassServiceClient {

constructor(asyncEventbusProvider, serviceAddress) {
logger.verbose('constructing a service client for '+ serviceAddress);
this._asyncEventbusProvider= asyncEventbusProvider;
this._serviceAddress= serviceAddress;
}

get connected() {
returnthis._connected;
}

connect() {
constclient=this;
returnnewPromise((resolve, reject) => {
if (this._connected) {
logger.verbose('already connected.');
resolve();
} else {
try {
client._asyncEventbusProvider((error, eventBus) => {
if (error) {
reject(error);
return;
}
client._eventbus= eventBus;
logger.info('Connecting to vertx');
client._eventbus.onopen= () => {
logger.verbose('established connection to eventbus');
client._connected=true;
resolve();
};
client._eventbus.onclose= () => {
logger.warn('Connection closed');
client._connected=false;
reject('connection to the eventbus has been closed');
};
client._eventbus.onerror= (error) => {
logger.error('There was an error: ', error);
reject(error);
}
});
} catch (e) {
logger.error('Exception encountered connecting to eventbus', e);
reject(e);
}
}
});
}

//TODO: it would be cool to use a proxy here... but sadly it's not supported in our version of node. someday.
invoke(method, params) {
if (this._connected) {
returnthis._doInvoke(method, params);
} else {
returnnewPromise((resolve, reject) => {
this.connect().then(() => {
this._doInvoke(method, params).then(resolve).catch(reject);
}).catch(reject);
});
}
}

_doInvoke(method, params) {
constclient=this;
returnnewPromise((resolve, reject) => {
try {
client._eventbus.send(this._serviceAddress, JSON.stringify(params),
{'method':method},
(error, result) => {
logger.debug('Vertx error result', error, result);
if (error) {
logger.error('error message from vertx or socketJS ', error);
reject(error.body);
} elseif (result.failureCode) {
logger.error('error from app ', result);
reject(result.message);
} else {
constbody=JSON.parse(result.body);
logger.debug('Success ', body);
resolve(body);
}
});
} catch (e) {
logger.error('exception invoking method '+ method, e);
reject(e);
}
});
}

}

index.js for NPM

importServiceClientfrom"./serviceClient";

exportdefaultServiceClient

mockEventBus.js for unit testing NPM

importloggerfrom"winston";
import {testEmployee, employees} from"./serviceClientSpec";

classEventBus {

constructor(url) {
constself=this;
logger.debug('initializing mock eventbus for '+ url);
setTimeout(() => {
self.onopen&&self.onopen();
}, 5);
}

send(address, message, headers, callback) {

constmethod= headers['method'];
logger.debug(`calling ${method} on ${address}`);
if ('addEmployee'=== method) {
callback(null, {body:true});
} elseif ('getEmployee'=== method) {
callback(null, {body:JSON.stringify(testEmployee)});
}elseif ('twoArg'=== method) {
callback(null, {body:true});
}elseif ('list'=== method) {
callback(null, {body:JSON.stringify(employees)});
} else {
callback({body:'[Unable to find handler]'});
}
}
}

module.exports= EventBus;

serviceClientSpec.js client spec for testing NPM QBit client, shows you how to use the client lib

"use strict";
importServiceClientfrom"lib/index";
importloggerfrom"winston";
importshouldfrom"should";

logger.level='debug';

exportconsttestEmployee= {
"id":"5",
"firstName":"Bob",
"lastName":"Jingles",
"birthYear":1962,
"socialSecurityNumber":999999999
};

exportconsttestEmployee2= {
"id":"rick",
"firstName":"Rick",
"lastName":"Jingles",
"birthYear":1962,
"socialSecurityNumber":999999999
};

exportconstemployees= [testEmployee2, testEmployee];

describe('ServiceClient', () => {

let EventBus;

before(() => {
EventBus =require(process.env.INTEGRATION_TESTS?"vertx3-eventbus-client":"./mockEventBus");
});

constprovider= (callback) => {
callback(null, newEventBus('http://localhost:8080/eventbus/'));
};

it('should be able to connect', (done) => {
constserviceClient=newServiceClient(provider, '/es/1.0');
serviceClient.connect().then(done);
}
);

it('should invoke a service call when already connected', (done) => {
constserviceClient=newServiceClient(provider, '/es/1.0');
serviceClient.connect().then(() => {
serviceClient.invoke('addEmployee', [testEmployee]).then(
(result) => {
result.should.be.equal(true);
logger.info("result", result);
done();
});
});
}
);

it('should invoke a service call when not already connected', (done) => {
constserviceClient=newServiceClient(provider, '/es/1.0');
serviceClient.invoke('addEmployee', [testEmployee]).then(
(result) => {
result.should.be.equal(true);
logger.info("result", result);
done();
});
}
);

it('should handle list of employees', (done) => {
constserviceClient=newServiceClient(provider, '/es/1.0');
serviceClient.invoke('list', [employees]).then(
(result) => {
logger.info("result", result);
done();
});
}
);

it('should handle two args', (done) => {
constserviceClient=newServiceClient(provider, '/es/1.0');
serviceClient.invoke('twoArg', [testEmployee2, true]).then(
(result) => {
logger.info("result", result);
done();
});
}
);

it('should invoke a service call that returns JSON', (done) => {
constserviceClient=newServiceClient(provider, '/es/1.0');
serviceClient.invoke('getEmployee', ["5"]).then(
(result) => {
should.exist(result);
result.id.should.be.equal('5');
logger.info("result", result);
done();
});
}
);

it('should fail on an unknown method', (done) => {
constserviceClient=newServiceClient(provider, '/es/1.0');
serviceClient.invoke('bogus').then().catch(
(error) => {
logger.info("error ", error);
should.exist(error);
error.should.be.equal('[Unable to find handler]');
done();
});
}
);

});

package.json

{
"name":"qbit-service-client",
"version":"1.1.3",
"description":"Service Client for backend services using Vertx Eventbus.",
"main":"distribution/index.js",
"publishConfig": {
"registry":"REDACTED"
},
"scripts": {
"test":"NODE_PATH=./ mocha --require babel-core/register --require babel-polyfill --recursive --reporter spec",
"build":"babel lib --out-dir distribution"
},
"dependencies": {
"winston":"^2.2.0"
},
"devDependencies": {
"babel-cli":"^6.6.5",
"babel-core":"^6.7.2",
"babel-plugin-transform-regenerator":"^6.6.5",
"babel-polyfill":"^6.7.2",
"babel-preset-es2015":"^6.6.0",
"mocha":"^2.4.5",
"should":"^8.2.2",
"vertx3-eventbus-client":"^3.2.1"
},
"license":"MIT"
}

Java

Employee

packageio.advantageous.qbit.example.vertx.eventbus.bridge;

publicclassEmployee {


privatefinalString id;
privatefinalString firstName;
privatefinalString lastName;
privatefinalint birthYear;
privatefinallong socialSecurityNumber;

publicEmployee(Stringid, StringfirstName, StringlastName, intbirthYear, longsocialSecurityNumber) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.birthYear = birthYear;
this.socialSecurityNumber = socialSecurityNumber;
}


publicStringgetFirstName() {
return firstName;
}

publicStringgetLastName() {
return lastName;
}

publicintgetBirthYear() {
return birthYear;
}

publiclonggetSocialSecurityNumber() {
return socialSecurityNumber;
}

publicStringgetId() {
return id;
}

@Override
publicStringtoString() {
return"Employee{"+
"firstName='"+ firstName +'\''+
", lastName='"+ lastName +'\''+
", birthYear="+ birthYear +
", socialSecurityNumber="+ socialSecurityNumber +
'}';
}
}

Employee Service

packageio.advantageous.qbit.example.vertx.eventbus.bridge;

importio.advantageous.qbit.annotation.RequestMapping;
importio.advantageous.qbit.annotation.RequestParam;
importio.advantageous.qbit.annotation.http.Bridge;
importio.advantageous.qbit.annotation.http.GET;
importio.advantageous.qbit.reactive.Callback;

importjava.util.List;

@RequestMapping("/es/1.0")
publicclassEmployeeService {

@Bridge("/employee/")
publicbooleanaddEmployee(finalEmployeeemployee) {
System.out.println(employee);
returntrue;
}

@Bridge("/employee/err/")
publicbooleanaddEmployeeError(finalEmployeeemployee) {
thrownewIllegalStateException("Employee can't be added");
}

@Bridge
publicvoidgetEmployee(finalCallback<Employee>callback, finalStringid) {
callback.returnThis(newEmployee(id, "Bob", "Jingles", 1962, 999999999));
}


@GET("/employee/")
publicvoidgetEmployeeWithParam(finalCallback<Employee>callback,
@RequestParam(value="id", required=true) finalStringid) {
callback.returnThis(newEmployee(id, "Bob", "Jingles", 1962, 999999999));
}

@Bridge
publicEmployeesingleton(finalEmployeeemployee) {
return employee;
}

@Bridge
publicbooleantwoArg(finalEmployeeemployee, booleanflag) {

return employee.getId().equals("rick") && flag;
}

@Bridge
publicList<Employee>list(finalList<Employee>employees) {
return employees;
}

}

EventBusBridgeExample

packageio.advantageous.qbit.example.vertx.eventbus.bridge;

importio.advantageous.qbit.admin.ManagedServiceBuilder;
importio.advantageous.qbit.http.server.HttpServer;
importio.advantageous.qbit.server.ServiceEndpointServer;
importio.advantageous.qbit.vertx.eventbus.bridge.VertxEventBusBridgeBuilder;
importio.advantageous.qbit.vertx.http.VertxHttpServerBuilder;
importio.vertx.core.AbstractVerticle;
importio.vertx.core.Vertx;
importio.vertx.ext.web.Router;
importio.vertx.ext.web.handler.sockjs.BridgeOptions;
importio.vertx.ext.web.handler.sockjs.PermittedOptions;
importio.vertx.ext.web.handler.sockjs.SockJSHandler;

/**
* Send JSON POST.
* <code>
* curl -X POST -H "Content-Type: application/json" \
* http://localhost:8080/es/1.0/employee/ \
* -d '{"id":"5","firstName":"Bob","lastName":"Jingles","birthYear":1962,"socialSecurityNumber":999999999}'
* </code>
*
* Get JSON
*
* <code>
* curl http://localhost:8080/es/1.0/employee/?id=5
* </code>
*/
publicclassEventBusBridgeExampleextendsAbstractVerticle {

@Override
publicvoidstart() throwsException {


finalString address ="/es/1.0";
finalEmployeeService employeeService =newEmployeeService();
finalVertxEventBusBridgeBuilder vertxEventBusBridgeBuilder =VertxEventBusBridgeBuilder
.vertxEventBusBridgeBuilder()
.setVertx(vertx);
finalManagedServiceBuilder managedServiceBuilder =ManagedServiceBuilder.managedServiceBuilder();
finalRouter router =Router.router(vertx);


managedServiceBuilder.setRootURI("/");
vertxEventBusBridgeBuilder.addBridgeAddress(address, EmployeeService.class);
/* Route everything under address to QBit http server. */
router.route().path(address +"/*");
/* Configure bridge at this HTTP/WebSocket URI. */
router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(
newBridgeOptions()
.addInboundPermitted(newPermittedOptions().setAddress(address))
.addOutboundPermitted(newPermittedOptions().setAddress(address))
));

finalio.vertx.core.http.HttpServer vertxHttpServer = vertx.createHttpServer();
/*
* Use the VertxHttpServerBuilder which is a special builder for Vertx/Qbit integration.
*/
finalHttpServer httpServer =VertxHttpServerBuilder.vertxHttpServerBuilder()
.setRouter(router)
.setHttpServer(vertxHttpServer)
.setVertx(vertx)
.build();


finalServiceEndpointServer endpointServer = managedServiceBuilder.getEndpointServerBuilder()
.setHttpServer(httpServer)
.addService(employeeService)
.build();
vertxEventBusBridgeBuilder.setServiceBundle(endpointServer.serviceBundle()).build();
endpointServer.startServer();
vertxHttpServer.requestHandler(router::accept).listen(8080);


}


publicstaticvoidmain(String... args) throwsException {
finalVertx vertx =Vertx.vertx();
vertx.deployVerticle(newEventBusBridgeExample());
}
}

Introducing Reakt : Reactive interfaces for Java Promises, Streams, Callbacks, and Async results

$
0
0
Introducing Reakt Reactive interfaces for Java Promises, Streams, Callbacks, and Async results. To be used by QBit 2 and conekt.


Reakt is reactive interfaces for Java:
  • Promises,
  • Streams,
  • Callbacks,
  • Async results
The emphasis is on defining interfaces that enable lambda expressions, and fluent APIs for asynchronous programming for Java.

Note: This mostly just provides the interfaces not the implementations. There are some starter implementations but the idea is that anyone can implement this. It is all about interfaces. There will be adapters for Vertx, RxJava, Reactive Streams, Guava Async Futures, etc. 

Fluent Promise API

Promise<Employee> promise = promise()
.then(e -> saveEmployee(e))
.catchError(error ->
logger.error("Unable to lookup employee", error));

employeeService.lookupEmployee(33, promise);
Or you can handle it in one line.

Fluent Promise API example 2

  employeeService.lookupEmployee(33, 
promise().then(e -> saveEmployee(e))
.catchError(error -> logger.error(
"Unable to lookup ", error))
);
Promises are both a callback and a Result; however, you can work with Callbacks directly.

Using Result and callback directly

        employeeService.lookupEmployee(33, result -> {
result.then(e -> saveEmployee(e))
.catchError(error -> {
logger.error("Unable to lookup", error);
});
});
In both of these examples, lookupService would look like:

Using Result and callback directly

publicvoid lookup(long employeeId, Callback<Employee> callback){...}
QBit version 2 is going to use ReaktCommunikate, a slimmed down fork of Vert.x, will also use Reakt.
(See QBit micorservies lib for more details. See our wiki for more details on Reakt.)

Ref is a similar concept to Optional in Java JDK and Option in Scala.
We added a new concept because this one is expected to come through callbacks and is used in places where Optional does not make sense. Also Reakt wants to use interfaces for all core concepts so others can provide their own implementations. In addition we wanted consumers forifPresent and ifEmpty.
Ref contains an value object which may or may not be set. This is like {@code Optional} but could be the value from an async operation. If a value is present, isPresent() will return trueand get() will return the value. Ref is heavily modeled after optional (java.util.Optional).

Sample Usage

finalRef<Employee> empty =Ref.empty();
...//OR

finalRef<Employee> empty =Ref.ofNullable(null);
...


//isEmpty isPresent
assertTrue( empty.isEmpty() );
assertFalse( empty.isPresent() );

//ifEmpty ifPresent
empty.ifEmpty(() -> logger.info("Empty"));
empty.ifPresent(employee -> logger.info("Employee", employee));

//empty get throws a NoSuchElementException for empty ref
try {
empty.get();
fail();
} catch (NoSuchElementException nsee) {
}

Sample Usage non-empty

finalRef<Employee> rick =Ref.ofNullable(newEmployee("Rick"));
...//OR
finalRef<Employee> rick =Ref.of(newEmployee("Rick"));

...


//ifEmpty ifPresent
rick.ifEmpty(() -> logger.info("Empty"));
rick.ifPresent(employee -> logger.info("Employee", employee));


//Convert employee into a sheep using map
finalSheep sheep = rick.map(employee ->newSheep(employee.id)).get();
assertEquals("Rick", sheep.id);


//Use Filter
assertTrue(
rick.filter(employee -> employee.id.equals("Rick")
).isPresent()); //Rick is Rick
assertFalse(
rick.filter(employee -> employee.id.equals("Bob")
).isPresent()); //Rick is not Bob

Result is the result of an async operation. This was modeled after Vert.x AsyncResult and after the types of results one would deal with in JavaScript.

Example usage using then and catchError

        employeeService.lookupEmployee(33, result -> {
result.then(e -> saveEmployee(e))
.catchError(error -> {
logger.error("Unable to lookup", error);
});
});

Example usage using Ref from thenRef and catchError

        employeeService.lookupEmployee(33, result -> {
result.thenRef(ref -> ref.ifPresent(e -> saveEmployee(e)))
.catchError(error -> {
logger.error("Unable to lookup", error);
});
});

Callback is a generic event handler which can be thought of as a callback handler. This is like an async future or callback. This was modeled after QBit's callback and Guava's Callback, and JavaScripts callbacks. A Result represents the result or error from an async operation and is passed to onResult.

Promise is like a non-blocking Future (java.util.concurrent.Future). With a promise you can get notified of changes instead of having to call get.
A promise is both a Callback (io.advantageous.reakt.Callback), and a Result(io.advantageous.reakt.Result). A promise is a sort of deferred value.
There are three types of promises, a blocking promise, a callback promise and a replay promise.
blocking promise is for legacy integration and for testing. A callback promise will get called back (its thenthenRef and catchError handlers), but usually on a foreign thread. A replay promise gets called back on the the caller's thread not the callee. The replay promise usually works with aReactor (a concept from QBit).

Stream is a generic event handler for N results, i.e., a stream of results. This is a like a type of Callback for streaming results. While Callback can be considered for scalar results, a Stream is more appropriate for non-scalar results, i.e., Stream.onNext will get called many times.
This is a very small API and you can find examples via unit tests at Reakt. Please check it out and give feedback.




Czar Maker is a nice set of Java reactive interfaces for Leader Election

$
0
0

Czar Maker

Czar Maker is a nice set of interfaces for Leader Election.
There is one Czar Maker Consul implementation of this interface that uses Consul. You could use the interface to implement leader election with zookeeper or etcd. Consul and etcd use the RAFT algorithm to present a reliable kv storage (Zookeeper uses a similar technique as Consul and etcd).
Czar uses Reakt, a Java reactive, streaming API, with callbacks and promises that is Java 8 and Lambda friendly
Czar also uses QBit microservices as its HTTP/IO lib.

Getting Started

This library is just interfaces, to use Czar on your project you will need the Czar Maker Consul implementation.

maven

<dependency>
<groupId>io.advantageous.czarmaker</groupId>
<artifactId>czar-maker</artifactId>
<version>0.1.0.RELEASE</version>
</dependency>

gradle

compile 'io.advantageous.czarmaker:czar-maker:0.1.0.RELEASE'

Example usage

publicclassMyService {

privatefinalString host;
privatefinalint port;
privatefinalLeaderElector leaderElector;
privateAtomicBoolean amILeader =newAtomicBoolean();
privateAtomicReference<Endpoint> leaderEndpoint =newAtomicReference<>();

publicvoidinit() {
Promise<Endpoint> getLeaderPromise =Promises.<Endpoint>promise();

/* Call elect new leader. */
getLeaderPromise.thenExpect(expected ->
expected
.ifEmpty(this::nominateSelf)
.ifPresent(endpoint -> leaderEndpoint.set(endpoint)))
.catchError(Throwable::printStackTrace);
leaderElector.getLeader(getLeaderPromise);

/* Register for stream of leadership changes. */
registerForLeadershipNotices();

}


privatevoidregisterForLeadershipNotices() {
leaderElector.leadershipChangeNotice(result ->
result
.thenExpect(this::checkIfThisServiceIsLeader)
.catchError(Throwable::printStackTrace)
);
}

//Handles leadership change stream.
privatevoidcheckIfThisServiceIsLeader(Expected<Endpoint>expectedEndpoint) {
expectedEndpoint.ifEmpty(() -> nominateSelf()) //If empty then nominate this service
.ifPresent(endpoint -> {
amILeader.set(endpoint.getHost().equals(host) && endpoint.getPort()==port);
leaderEndpoint.set(endpoint);
});
}

//Attempt to Nominate self if there is no leader
privatevoidnominateSelf() {
finalPromise<Boolean> selfElectPromise =Promises.<Boolean>promise();
selfElectPromise.then((elected) -> {
amILeader.set(elected);
}).catchError(Throwable::printStackTrace);

leaderElector.selfElect(newEndpoint(host, port), selfElectPromise);

}

publicbooleanisLeader() {
return amILeader.get();
}

publicEndpointgetLeaderEndpoint() {
return leaderEndpoint.get();
}

Related projects


Reakt reactive Java lib to Guava Bridge

$
0
0

Reakt to Guava Bridge

Guava gets used by many libraries for its async support. Many NoSQL drivers use Guava, e.g., Cassandra.
Guava is JDK 1.6 backwards compatible.
Reakt provides composable promises that support lambda expressions, and a fluent API.
This bridge allows you to use Reakt's promises, reactive streams and callbacks to have a more modern Java experience with libs like Cassandra and other libs that use Guava.

Cassandra Reakt example

register(session.executeAsync("SELECT release_version FROM system.local"), 
promise().thenExpect(expected ->
gui.setMessage("Cassandra version is "+
expected.get().one().getString("release_version"))
).catchError(error ->
gui.setMessage("Error while reading Cassandra version: "
+ error.getMessage())
)
);
You can also use replay promises, all promises, any promises, and other features of Reakt to simplify async, reactive Java development.
Reakt gets used by QBit, and Conekt.

Related projects

Czar Maker Consul - Reactive Java Leadership Election lib

$
0
0

Czar Maker Consul

Czar Maker is a Java lib for leadership election. Czar Maker Consul uses Consul to do leadership election.

Getting Started

Maven

<dependency>
<groupId>io.advantageous.czarmaker</groupId>
<artifactId>czar-maker-consul</artifactId>
<version>0.1.0.RELEASE</version>
</dependency>

Gradle

compile 'io.advantageous.czarmaker:czar-maker-consul:0.1.0.RELEASE'

Sample usage

importio.advantageous.consul.Consul;
importio.advantageous.czarmaker.Endpoint;
importio.advantageous.czarmaker.consul.*;
importio.advantageous.qbit.util.TestTimer;
importio.advantageous.reakt.promise.Promise;
importio.advantageous.reakt.promise.Promises;
importio.advantageous.reakt.reactor.Reactor;
importio.advantageous.reakt.reactor.TimeSource;

...

privatefinallong sessionTTL =10;
privatefinallong newLeaderCheckInterval =5;
privateConsulLeadershipElector leadershipElector;
privateReactor reactor;
privateTestTimer testTimer;
privateConsul consul;

...
consul =Consul.consul();
testTimer =newTestTimer();
testTimer.setTime();
reactor =Reactor.reactor(Duration.ofSeconds(30), newTestTimeSource(testTimer));
finalString serviceName ="foo";

ConsulLeadershipProvider provider =newConsulLeadershipProvider(serviceName, consul, TimeUnit.SECONDS, sessionTTL);

leadershipElector =newConsulLeadershipElector(provider, serviceName, reactor, TimeUnit.SECONDS,
sessionTTL, newLeaderCheckInterval);


/** Get the current leader. */
Promise<Endpoint> promise =Promises.<Endpoint>blockingPromise();
leadershipElector.getLeader(promise);

assertTrue(promise.expect().isEmpty());


/** Elect this endpoint as the current leader. */
Promise<Boolean> selfElectPromise =Promises.<Boolean>blockingPromise();
leadershipElector.selfElect(newEndpoint("foo.com", 9091), selfElectPromise);

assertTrue("We are now the leader", selfElectPromise.get());


/** Get the current leader again. */
Promise<Endpoint> getLeaderPromise =Promises.<Endpoint>blockingPromise();
leadershipElector.getLeader(getLeaderPromise);

/** See if it present. */
assertTrue(getLeaderPromise.expect().isPresent());

/** See if it has the host foo.com. */
assertEquals("foo.com", getLeaderPromise.get().getHost());

/** See if the port is 9091. */
assertEquals(9091, getLeaderPromise.get().getPort());

testTimer.seconds(100);

leadershipElector.process();

/** Elect a new leader. */
leadershipElector.selfElect(newEndpoint("foo2.com", 9092), selfElectPromise);

Reakt 2.0 released - Java 8 interfaces for Streams, Callbacks and Promises

$
0
0
Reakt is a set of reactive interfaces for Java that are Java 8 Lambda function friendly.
Reakt is reactive interfaces for Java which includes:
The emphasis is on defining interfaces that enable lambda expressions, and fluent APIs for asynchronous programming for Java.
Note: This mostly just provides the interfaces not the implementations. There are some starter implementations but the idea is that anyone can implement this. It is all about interfaces. There will be adapters for Vertx, RxJava, Reactive Streams, etc. There is support for Guava Async (used by Cassandra) and the QBit microservices lib. Czar Maker uses Reakt for its reactive leadership election.

Have a question?

Getting started

Using from maven

<dependency>
<groupId>io.advantageous</groupId>
<artifactId>reakt</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>

Using from gradle

compile 'io.advantageous:reakt:2.0.0.RELEASE'

Fluent Promise API

Promise<Employee> promise = promise()
.then(e -> saveEmployee(e))
.catchError(error ->
logger.error("Unable to lookup employee", error));

employeeService.lookupEmployee(33, promise);
Or you can handle it in one line.

Fluent Promise API example 2

  employeeService.lookupEmployee(33, 
promise().then(e -> saveEmployee(e))
.catchError(error -> logger.error(
"Unable to lookup ", error))
);
Promises are both a callback and a Result; however, you can work with Callbacks directly.

Using Result and callback directly

        employeeService.lookupEmployee(33, result -> {
result.then(e -> saveEmployee(e))
.catchError(error -> {
logger.error("Unable to lookup", error);
});
});
In both of these examples, lookupEmployee would look like:

Using Result and callback directly

publicvoid lookupEmployee(long employeeId, Callback<Employee> callback){...}
You can use Promises to transform into other promises.

Transforming into another type of promise using thenMap

Promise<Employee> employeePromise =Promises.<Employee>blockingPromise();

Promise<Sheep> sheepPromise = employeePromise
.thenMap(employee1 ->newSheep(employee1.getId()));
The thenMap will return a new type of Promise.
You can find more examples in the reakt wiki.
We also support working with streams.

Promise concepts

This has been adapted from this article on ES6 promises. A promise can be:
  • fulfilled The callback/action relating to the promise succeeded
  • rejected The callback/action relating to the promise failed
  • pending The callback/action has not been fulfilled or rejected yet
  • completed The callback/action has been fulfilled/resolved or rejected
Java is not single threaded, meaning that two bits of code can run at the same time, so the design of this promise and streaming library takes that into account.
There are three types of promises:
  • Callback promises
  • Blocking promises (for testing and legacy integration)
  • Replay promises (allow promises to be handled on the same thread as caller)
Replay promises are the most like their JS cousins. Replay promises are usually managed by the Reakt Reactor and supports environments like Vert.x and QBit. See the wiki for more details on Replay promises.
It is common to make async calls to store data in a NoSQL store or to call a remote REST interface or deal with a distributed cache or queue. Also Java is strongly typed so the library that mimics JS promises is going to look a bit different. We tried to use similar terminology where it makes sense.
Events and Streams are great for things that can happen multiple times on the same object — keyup, touchstart, or event a user action stream from Kafka, etc.
With those events you don't really care about what happened before when you attached the listener.
But often times when dealing with services and data repositories, you want to handle a response with a specific next action, and a different action if there was an error or timeout from the responses. You essentially want to call and handle a response asynchronously and that is what promises allow.
This is not our first time to bat with Promises. QBit has had Promises for a few years now. We just called them CallbackBuilders instead. We wanted to use more standard terminology and wanted to use the same terminology and modeling on projects that do not use QBit like Conekt, Vert.x, RxJava, and reactive streams.
At their most basic level, promises are like event listeners except:
A promise can only succeed or fail once. A promise cannot succeed or fail twice, neither can it switch from success to failure. Once it enters its completed state, then it is done.

Bridges

Reakt Guava Bridge which allows libs that use Guava async support to now have a modern Java feel.

Cassandra Reakt example

register(session.executeAsync("SELECT release_version FROM system.local"), 
promise().thenExpect(expected ->
gui.setMessage("Cassandra version is "+
expected.get().one().getString("release_version"))
).catchError(error ->
gui.setMessage("Error while reading Cassandra version: "
+ error.getMessage())
)
);
QBit 1 ships with a bridge and QBit 2will use Reakt as its primary reactive callback mechanism.
Conekt, a slimmed down fork of Vert.x, will also use Reakt.
See QBit microservices lib for more details.
See our wiki for more details on Reakt.

Further reading

The Java microservice lib. QBit is a reactive programming lib for building microservices - JSON, HTTP, WebSocket, and REST. QBit uses reactive programming to build elastic REST, and WebSockets based cloud friendly, web services. SOA evolved for mobile and cloud. ServiceDiscovery, Health, reactive StatService, events, Java idiomatic reactive programming for Microservices.

Invokable Promise making reactive Java programming more fluid

$
0
0
First cut of invokable promises, 

Please provide feedback. 



Reakt, Java reactive programming lib, supports invokable promises so service methods could return a Promise.

Invokable Promise

        employeeService.lookupEmployee("123")
.then((employee)-> {...}).catchError(...).invoke();
NOTE: QBit Microservices Lib version 1.4 (coming soon) will support generation of client stubs (local and remote) that return Reakt Promises. Having the client proxy method return the promise instead of taking a callback. With QBit you will be able to use Callback's on the service side and Promises on the client proxy side. Callbacks are natural for the service, and Promises are natural for the client.
But you do not have to use QBit to use Reakt's invokable promises.
Let's walk through an example of using a Reakt invokable promises.
Let's say we are developing a service discovery service with this interface.

ServiceDiscovery interface (Example service that does lookup)

interfaceServiceDiscovery {
Promise<URI>lookupService(URIuri);

default voidshutdown() {}
default voidstart() {}
}
Note that ServiceDiscovery.lookupService is just an example likeEmployeeService.lookupEmployee was just an example.
Notice the lookupService returns a Promise (Promise<URI> lookupService(URI uri)). To call this, we can use an invokable promise. The service side will return an invokable promise, and the client of the service will use that Promise to register its thenthenExpectthenMap, and/orcatchError handlers.
Here is the service side of the invokable promise example.

Service Side of invokable promise

classServiceDiscoveryImplimplementsServiceDiscovery {

@Override
publicPromise<URI>lookupService(URIuri) {
return invokablePromise(promise -> {
if (uri ==null) {
promise.reject("URI was null");
} else {
...
// DO SOME ASYNC OPERATION WHEN IT RETURNS CALL RESOLVE.
promise.resolve(successResult);
}
});
}
}
Notice to create the invokablePromise we use Promises.invokablePromise which takes aPromise Consumer (static <T> Promise<T> invokablePromise(Consumer<Promise<T>> promiseConsumer)).
The lookupService example returns a Promise and it does not execute its body until theinvoke (Promise.invoke) on the client side of the equation is called.
Let's look at the client side.

Client side of using lookupService.

        serviceDiscovery.lookupService(empURI)
.then(this::handleSuccess)
.catchError(this::handleError)
.invoke();
The syntax this::handleSuccess is a method reference which can be used as Java 8 lambda expressions. We use method references to make the example shorter.
Here is a complete example with two versions of our example service.

Complete example from unit tests

packageio.advantageous.reakt.promise;

importorg.junit.After;
importorg.junit.Before;
importorg.junit.Test;

importjava.net.URI;
importjava.util.Queue;
importjava.util.concurrent.*;
importjava.util.concurrent.atomic.AtomicBoolean;
importjava.util.concurrent.atomic.AtomicReference;

import staticio.advantageous.reakt.promise.Promises.*;
import staticorg.junit.Assert.*;

publicclassInvokablePromise {


finalURI successResult =URI.create("http://localhost:8080/employeeService/");
ServiceDiscovery serviceDiscovery;
ServiceDiscovery asyncServiceDiscovery;
URI empURI;
CountDownLatch latch;
AtomicReference<URI> returnValue;
AtomicReference<Throwable> errorRef;

@Before
publicvoidbefore() {
latch =newCountDownLatch(1);
returnValue =newAtomicReference<>();
errorRef =newAtomicReference<>();
serviceDiscovery =newServiceDiscoveryImpl();
asyncServiceDiscovery =newServiceDiscoveryAsyncImpl();
asyncServiceDiscovery.start();
empURI =URI.create("marathon://default/employeeService?env=staging");
}

@After
publicvoidafter() {
asyncServiceDiscovery.shutdown();
}

publicvoidawait() {
try {
latch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
thrownewIllegalStateException(e);
}
}

@Test
publicvoidtestServiceWithReturnPromiseSuccess() {
serviceDiscovery.lookupService(empURI).then(this::handleSuccess)
.catchError(this::handleError).invoke();
await();
assertNotNull("We have a return", returnValue.get());
assertNull("There were no errors", errorRef.get());
assertEquals("The result is the expected result", successResult, returnValue.get());
}


@Test
publicvoidtestServiceWithReturnPromiseFail() {


serviceDiscovery.lookupService(null).then(this::handleSuccess)
.catchError(this::handleError).invoke();

await();
assertNull("We do not have a return", returnValue.get());
assertNotNull("There were errors", errorRef.get());
}


@Test
publicvoidtestAsyncServiceWithReturnPromiseSuccess() {
asyncServiceDiscovery.lookupService(empURI).then(this::handleSuccess)
.catchError(this::handleError).invoke();
await();
assertNotNull("We have a return from async", returnValue.get());
assertNull("There were no errors form async", errorRef.get());
assertEquals("The result is the expected result form async", successResult, returnValue.get());
}


@Test
publicvoidtestAsyncServiceWithReturnPromiseFail() {


asyncServiceDiscovery.lookupService(null).then(this::handleSuccess)
.catchError(this::handleError).invoke();

await();
assertNull("We do not have a return from async", returnValue.get());
assertNotNull("There were errors from async", errorRef.get());
}

@Test (expected =IllegalStateException.class)
publicvoidtestServiceWithReturnPromiseSuccessInvokeTwice() {
finalPromise<URI> promise = serviceDiscovery.lookupService(empURI).then(this::handleSuccess)
.catchError(this::handleError);
promise.invoke();
promise.invoke();
}

@Test
publicvoidtestIsInvokable() {
finalPromise<URI> promise = serviceDiscovery.lookupService(empURI).then(this::handleSuccess)
.catchError(this::handleError);

assertTrue("Is this an invokable promise", promise.isInvokable());
}


privatevoidhandleError(Throwableerror) {
errorRef.set(error);
latch.countDown();
}

privatevoidhandleSuccess(URIuri) {
returnValue.set(uri);
latch.countDown();
}


interfaceServiceDiscovery {
Promise<URI>lookupService(URIuri);

default voidshutdown() {
}

default voidstart() {
}
}

classServiceDiscoveryImplimplementsServiceDiscovery {

@Override
publicPromise<URI>lookupService(URIuri) {
return invokablePromise(promise -> {

if (uri ==null) {
promise.reject("URI was null");
} else {
promise.resolve(successResult);
}
});
}
}


classServiceDiscoveryAsyncImplimplementsServiceDiscovery {

finalExecutorService executorService;

finalQueue<Runnable> runnables;

finalAtomicBoolean stop;

publicServiceDiscoveryAsyncImpl() {
executorService =Executors.newSingleThreadExecutor();
runnables =newLinkedTransferQueue<>();
stop =newAtomicBoolean();
}

@Override
publicPromise<URI>lookupService(URIuri) {
return invokablePromise(promise -> {
runnables.offer(() -> {
if (uri ==null) {
promise.reject("URI was null");
} else {
promise.resolve(URI.create("http://localhost:8080/employeeService/"));
}
});
});
}

@Override
publicvoidshutdown() {
stop.set(true);
executorService.shutdown();
}

@Override
publicvoidstart() {
executorService.submit((Runnable) () -> {

try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
if (stop.get())break;
Runnable runnable = runnables.poll();
while (runnable !=null) {
runnable.run();
runnable = runnables.poll();
}
}

});
}
}
}

Getting started with QBit Microservices Stats KPI Metrics Monitoring

$
0
0

Getting started with QBit Microservices Stats KPI Metrics Monitoring



KPI Microservices Monitoring

There has been a lot written on the subject of Microservices Monitoring. Monitoring is a bit of an overloaded term. There is service health monitoring, which can be done with tools like Mesosphere/Marathon, Nomad, Consul, etc. There is also KPI monitoring, which is done with tools like Grafana, Graphite, InfluxDB, StatsD, etc. Then there is log monitoring and search with tools like the ELK stack (elastic-search, LogStash and Kibana) and Splunk, where you can easily trace logs down to the requests or client ID in a request header. And, then there is system monitoring (JVM, slow query logs, network traffic, etc.), with tools like SystemD, and more. You will want all of this when you are doing Microservices Development.
The more insight you have into your system, the easier it will be to support and debug. Microservices imply async distributed development. Doing async distributed development without monitoring is like running with scissors.
To summarize Microservices Monitoring is:
  • KPI Monitoring (e.g., StatsD, Grafana, Graphite, InfluxDB, etc.)
  • Health Monitoring (e.g., Consul, Nomad, Mesosphere/Marathon, Heroku, etc.)
  • Log monitoring (e.g., ELK stack, Splunk, etc.)
QBit has support for ELK/Splunk by providing support for MDC. QBit has support for systems that can monitor health like Mesosphere/Marathon, Heroku, Consul, Nomad, etc. by having an internal health system that QBit service actors all check-in with that then gets rolled up to other systems like Mesosphere/Marathon, Heroku, Consul, Nomad, etc.
In this tutorial we are going to just cover KPI monitoring for microservices which is sometimes called Metrics Monitoring or Stats Monitoring. KPI stands for Key Performance Indicators. These are the things you really care about to see if your system is up and running, and how hard it is getting hit, and how it is performing.
At the heart of the QBit KPI system is the Metrics collector. QBit uses the Metrik interface for tracking Microservice KPIs.

Metrik Interface for tracking KPIs

publicinterfaceMetricsCollector {

default voidincrement(finalStringname) {
recordCount(name, 1);
}

default voidrecordCount(Stringname, longcount) {
}

default voidrecordLevel(Stringname, longlevel) {
}

default voidrecordTiming(Stringname, longduration) {
}

}
We are recording counts per time period, current level or gauge at this instance in time and timings which is how long did something take.

Demonstrating using QBit metrics

This guide assumes you have read through the main overview of QBit and have gone through the first tutorials, but you should be able to follow along if you have not, you just will be able to follow along better if you read the docs (at least skimmed) and went through the first set of tutorials.
Let's show it. First we need to build. Use Gradle as follows:

gradle.build

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'

/* Used for reactive Java programming. */
compile 'io.advantageous.reakt:reakt:2.5.0.RELEASE'

/* Used to simplify QBit config and for integration with Consul, StatsD, Heroku, etc. */
compile group: 'io.advantageous.qbit', name: 'qbit-admin', version: '1.3.0.RELEASE'

/* Vertx is used as the network driver for QBit. */
compile group: 'io.advantageous.qbit', name: 'qbit-vertx', version: '1.3.0.RELEASE'

}
Read the comments in all of the code listings.
In this example we will use StatsD but QBit is not limited to StatsD for Microservice KPI monitoring. In fact QBit can do amazing things with its StatsService like clustered rate limiting based on OAuth header info, but that is beyond this tutorial.
StatsD is a protocol that you can send KPI messages to over UDP. Digital Ocean has a really nicedescription of StatsD.
The easiest way to setup StatsD is to use Docker. Docker is a great tool for development, and using Docker with Nomad, CoreOS, Mesosphere/Marathon, etc. is a great way to deploy Docker containers, but at a minimum you should be using the Docker tools for development.
Set up the StatsD server stack by using this public docker container.
QBit ships with StatsD support in the qbit-admin lib (jar). It has done this for a long time.
We will connect to StatsD with this URI.

URI to connect to with StatsD

finalURI statsdURI =URI.create("udp://192.168.99.100:8125");
Depending on how you have Docker setup, your URI might look a bit different. If you are running Docker tools with a Mac, then that should be your URI. (On Linux the above IO is likely to be localhost not 192.168.99.100, go through the docker tool tutorials if you are lost at this point. It will be worth your time. I promise. I promise.invoke promise.)
If you have not already followed the instructions at statsD, grafana, influxdb docker container docs, do so now.

Running Docker

docker run -d \
--name docker-statsd-influxdb-grafana \
-p 3003:9000 \
-p 3004:8083 \
-p 8086:8086 \
-p 22022:22 \
-p 8125:8125/udp \
samuelebistoletti/docker-statsd-influxdb-grafana
The above yields

Servers

Host        Port        Service

3003 9000 grafana to see the results
8086 8086 influxdb to store the results
3004 8083 influxdb-admin to query the results
8125 8125 statsd server that listens to statsD UPD messages
22022 22 sshd
If you want to see the metrics and see if this is working, go through the influxDB tutorial and look around at the measurements with the influx-admin. Influx is a time series database. Grafana allows you to see pretty graphs and charts of the microservice KPIs that we are collecting. You will want to learn grafana as well.
We use the host and port of the URI to connect to the StatsD daemon that is running on the docker container.

Setting up StatsD by using QBit managedServiceBuilder

        managedServiceBuilder.getStatsDReplicatorBuilder().setHost(host).setPort(port);
managedServiceBuilder.setEnableStatsD(true);
We covered using and setting up the managedServiceBuilder in the first tutorials, and the complete code listing is below.

Use the managedServiceBuilder to create the StatsCollector/MetricsCollector

StatsCollector statsCollector = managedServiceBuilder.createStatsCollector();

/* Start the service. */
managedServiceBuilder.addEndpointService(newTodoService(reactor, statsCollector))
The QBit StatsCollector interface extends the Metrik MetricsCollector interface (from QBit 1.5 onwards).

Using the StatsCollector.

Then we just need to use it.

Using the StatsCollector to collect KPIs about our service

For kicks, we track the KPI todoservice.i.am.alive every three seconds.

Tracking KPI i.am.am.alive

@RequestMapping("/todo-service")
publicclassTodoService {
...
/** Send stat count i.am.alive every three seconds. */
this.reactor.addRepeatingTask(Duration.ofSeconds(3),
() -> statsCollector.increment("todoservice.i.am.alive"));

Tracking calls to add method

    @RequestMapping(value ="/todo", method =RequestMethod.POST)
publicvoid add(finalCallback<Boolean> callback, finalTodo todo) {

/** Send KPI add.called every time the add method gets called. */
statsCollector.increment("todoservice.add.called");
todoMap.put(todo.getId(), todo);
callback.accept(true);
}

Tracking calls to remove method

    @RequestMapping(value ="/todo", method =RequestMethod.DELETE)
publicvoid remove(finalCallback<Boolean> callback, final @RequestParam("id") String id) {

/** Send KPI add.removed every time the remove method gets called. */
statsCollector.increment("todoservice.remove.called");
Todo remove = todoMap.remove(id);
callback.accept(remove!=null);

}

Managing callbacks and repeating tasks

    @QueueCallback({EMPTY, IDLE, LIMIT})
publicvoid process() {
reactor.process();
}

Complete example

Todo.java

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;
}
}

TodoService.java to show tracking KPIs.

packagecom.mammatustech.todo;

importio.advantageous.qbit.annotation.*;
importio.advantageous.qbit.reactive.Callback;
importio.advantageous.qbit.service.stats.StatsCollector;
importio.advantageous.reakt.reactor.Reactor;

importjava.time.Duration;
importjava.util.*;

import staticio.advantageous.qbit.annotation.QueueCallbackType.*;


@RequestMapping("/todo-service")
publicclassTodoService {


privatefinalMap<String, Todo> todoMap =newTreeMap<>();

/** Used to manage callbacks and such. */
privatefinalReactor reactor;

/** Stats Collector for KPIs. */
privatefinalStatsCollector statsCollector;

publicTodoService(Reactorreactor, StatsCollectorstatsCollector) {
this.reactor = reactor;
this.statsCollector = statsCollector;

/** Send stat count i.am.alive every three seconds. */
this.reactor.addRepeatingTask(Duration.ofSeconds(3),
() -> statsCollector.increment("todoservice.i.am.alive"));

this.reactor.addRepeatingTask(Duration.ofSeconds(1), statsCollector::clientProxyFlush);
}


@RequestMapping(value="/todo", method=RequestMethod.POST)
publicvoidadd(finalCallback<Boolean>callback, finalTodotodo) {

/** Send KPI add.called every time the add method gets called. */
statsCollector.increment("todoservice.add.called");
todoMap.put(todo.getId(), todo);
callback.accept(true);
}



@RequestMapping(value="/todo", method=RequestMethod.DELETE)
publicvoidremove(finalCallback<Boolean>callback, final@RequestParam("id") Stringid) {

/** Send KPI add.removed every time the remove method gets called. */
statsCollector.increment("todoservice.remove.called");
Todo remove = todoMap.remove(id);
callback.accept(remove!=null);

}



@RequestMapping(value="/todo", method=RequestMethod.GET)
publicvoidlist(finalCallback<ArrayList<Todo>>callback) {

/** Send KPI add.list every time the list method gets called. */
statsCollector.increment("todoservice.list.called");

callback.accept(newArrayList<>(todoMap.values()));
}


@QueueCallback({EMPTY, IDLE, LIMIT})
publicvoidprocess() {
reactor.process();
}


}

TodoServiceMain.java showing how to configure StatsD QBit for MicroService KPI tracking

packagecom.mammatustech.todo;


importio.advantageous.qbit.admin.ManagedServiceBuilder;
importio.advantageous.qbit.service.stats.StatsCollector;
importio.advantageous.reakt.reactor.Reactor;

importjava.net.URI;
importjava.time.Duration;
importjava.util.Objects;

publicclassTodoServiceMain {


publicstaticvoidmain(finalString... args) throwsException {

//To test locally use https://hub.docker.com/r/samuelebistoletti/docker-statsd-influxdb-grafana/
finalURI statsdURI =URI.create("udp://192.168.99.100:8125");

//For timer
finalReactor reactor =Reactor.reactor();


/* 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

enableStatsD(managedServiceBuilder, statsdURI);

StatsCollector statsCollector = managedServiceBuilder.createStatsCollector();



/* Start the service. */
managedServiceBuilder.addEndpointService(newTodoService(reactor, statsCollector)) //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");

}

/**
* Enable Stats D.
*
* @param host statsD host
* @param port statsD port
*/
publicstaticvoidenableStatsD(ManagedServiceBuildermanagedServiceBuilder, Stringhost, intport) {
if (port <1) thrownewIllegalStateException("StatsD port must be set");
Objects.requireNonNull(host, "StatsD Host cannot be null");
if (host.isEmpty()) thrownewIllegalStateException("StatsD Host name must not be empty");
managedServiceBuilder.getStatsDReplicatorBuilder().setHost(host).setPort(port);
managedServiceBuilder.setEnableStatsD(true);
}

/**
* Enable Stats D.
*
* @param uri for statsd
*/
publicstaticvoidenableStatsD(ManagedServiceBuildermanagedServiceBuilder, URIuri) {
if (!uri.getScheme().equals("udp")) thrownewIllegalStateException("Scheme must be udp");
enableStatsD(managedServiceBuilder, uri.getHost(), uri.getPort());
}
}



Read more


Still not enough, then try:








Viewing all 213 articles
Browse latest View live