Overview

Design Schematic Home of the W application. R application can be found here

Description of each step

Simple explanation of the app

User sends a request (restful) to save a record, the controller then creates a command asking the writeWorker to write this record to db. After that an event is published to the Event-stream.

Rest API

Local connection

//command ... com.nshimiye.cqrs.writer.akka.WriteWorker
ActorSelection brokerWorker = AkkaFactory.getActorSystem(SystemType.REMOTE)
                            .actorSelection("/user/brokerWorker");

/user/brokerWorker is the local path of brokerWorker actor

Remote connection

Remote Akka Actors connect using either TCP or UDP. In our case, we use TCP connection. Example below shows how it is done

// query ... com.nshimiye.akka.AkkaInitializer
// Akka configuration values
private final String BROKER_URL = "akka.tcp://AKKAREMOTESystem@192.168.99.101:2555/user/brokerWorker";
...
ActorSelection messageBrokerFinder = AkkaFactory.getActorSystem(SystemType.REMOTE)
       .actorSelection(BROKER_URL);

akka.tcp://AKKAREMOTESystem@192.168.99.101:2555/user/brokerWorker is the remote path of brokerWorker actor.

Configuration

When an actor system is created, it looks for configuration information from the application.conf and/or reference.conf files. These files are assumed to be in the resource folder of the application.

If none of these are found, the default configuration is used

Below are the required configuration for creating a remote system:

// command ... application.conf
akka {
    actor {
      provider = "akka.remote.RemoteActorRefProvider"
      serialization-bindings {
        # specify java objects that need to be serialized and the serialization type to be used
      }
    }
    remote {
      enabled-transports = ["akka.remote.netty.tcp"]
      netty.tcp {
      hostname = 192.168.99.101     # external (logical) hostname, ex: my.domain.com
      port = 2555                   # external (logical) port

      bind-hostname = 0.0.0.0       # internal (bind) hostname, ex: local.address 
      bind-port = 2553              # internal (bind) port
      }
   }
}

Actors

Two different types of actor exist:

Example of actor creation:

// command ... com.nshimiye.akka.AkkaInitializer
// local actor
writeWorker = AkkaFactory.getActorSystem(SystemType.LOCAL).actorOf(WriteWorker.createWorker(), "writeWorker");
// remote actor
brokerWorker = AkkaFactory.getActorSystem(SystemType.REMOTE).actorOf(Props.create(BrokerWorker.class), "brokerWorker");
// query ... com.nshimiye.akka.AkkaInitializer
denomalizer = AkkaFactory.getActorSystem(SystemType.REMOTE).actorOf(Props.create(DenomalizerWorker.class), "denomalizer");

writeWorker can interact with brokerWorker. However, it cannot interact with denomalizer because they are not on the machines.

Event Bus

// command ... com.nshimiye.messaging.BrokerWorker
getContext().system().eventStream().subscribe(getSender(), Envelope.class);
...
getContext().system().eventStream().publish((Envelope) message);

In the above example, two things are happening:

Running

The important part here is --net=host, which tells the container to bind itself to the docker's main ipaddress instead of going through NAT.