Notification, Cache, Store

Communication

Messages

Messages are sent to all microservices and clients listening on the given path.
Send:

storm.path('/samplePath/abc/helloWorldMessage')
.sendMessage({
    'text':'hello world'
});

Receive:

storm.path('/samplePath/abc/helloWorldMessage')
.onMessage(function(data){
  alert(data.text);
});

 

Requests

Requests are only received by one of the listening microservices or clients. They can be used to ask a microservice for an direct answer. Load Balancing will take care of deciding which instance gets called.
Send:

storm.path('/samplePath/auth/login')
.sendRequest({
    'userName':'user'
    'loginToken':'abc'
},function(answer){
  alert("answer: " + answer.text);
});

Receive:

storm.path('/samplePath/auth/login')
.onRequest(function(data, responder){
  alert("received login request: " + data.userName);
  responder.respond({
    text:'login allowed',
    loggedIn:true
  });
});

 

Data

The concepts of paths and path collections are tightly coupled. The later can contain multiple of the former.

Creation

Create Object:

var data = {
    'name':'My Item 1'
    'innerData':'sample'
};
storm.pathCollection("/my/items")
.createChild(data, function(object){
    alert("successfully created " + object.data().name);
});

Make sure that an object exists:

var data = new APJson();
data.set('name', 'Some Room');
storm.pathCollection('/chat/room/')
.getOrCreateChild(data, function(room, wasNewlyCreated){
    // do something with the child
});

 

Querying

Messages are sent to all microservices and clients listening on the given path.
Query Objects:

storm.pathCollection('/my/items')
.queryJson({
    'name':'My Item 1'
}).limit(1)
.executeQuery(function(resultCursor){
    alert(resultCursor.first().innerData);
    // this will actually return 'sample'!
});

 

Object Change Events

Change Data:

storm.path("/my/items/[1]/")
.setData({
  'innerData':'sample'
});

Handle Change Event:

storm.path("/my/items/[1]/")
.onDataChange(function(newValue){
  alert("new value is " + newValue.innerData);
});

 

Events about other actors listening

Another actor starts listening:

storm.path("/chat/room/[*]/")
.onListeningStarted(function(otherEndpoint, path){
  // otherEndpoint has joined the room
});

Another actor stops listening:

storm.path("/chat/room/[*]/")
.onListeningEnded(function(otherEndpoint, path, becauseOfDisconnect){
  // otherEndpoint has left the room
    if (becauseOfDisconnect)
        alert('The client or service lost connectivity');
    else
        alert('The client or service choose to lleave the room');
});

 

Introduction

It is possible to introduce objects from the microservice side to clients or other microservices.
Introduce:

client.introduce(storm.object("/my/items/[1]/"));

Handle Introduction:

storm.pathCollection("/my/items/").onIntroduce(function(object){
  alert('i received an item:' + object.path);
  // Note: the path would be '/my/items/[1]/'
});

 

Full Path Potential

The combination of objects & lists as paths is very powerful. Here are some easy examples.

Simple Combination

Combinations of lists and objects can be used to create very complex recursive communication structures that can contain data.
Path events:

storm.path("/my/items/[1]/myMessage")
.sendMessage({
  'text':'hello world'
});

Path based listening:

var path = storm.pathCollection("/my/items/[1]/myMessage");
.onMessage(function(message){
  alert(message.text);
});

 

Wildcards

Wildcards allow microservices to work with object events without requiring to listen on each given object. It is possible to just listen to all objects.
Wildcard listening:

storm.path("/my/items/[*]/myMessage")
.onMessage(function(message){
  alert(message.text);
});

Complex Wildcard listening:

storm.path("/my/items/[*]/subItems/[*]/subMessage")
.onMessage(function(message){
  alert(message.text);
});

 

Validation

It is possible for microservices to validate everything a client (or another microservice) does.

Simple Validation
Validation can accept, correct or reject anything that has been sent to a path. Any of your microservices (and even clients) can handle validation. Wildcards can be used as well. Validation can be used for objects, messages and requests.
Send & Handler:

storm.path('/samplePath/abc/helloWorldMessage')
.sendMessage({
    'text':'hello world'
});
storm.path('/samplePath/abc/helloWorldMessage')
.onMessage(function(data){
  alert(data.text);
  // this will display 'validated world'!
});

Validator:

storm.path('/samplePath/abc/helloWorldMessage')
.onValidateMessage(function(data,path,validator){
  if(data.text=='hello world'){
    validator.correct({'text':'validated world'});
  }else if(data.length > 0){
    validator.accept();
  }else{
    validator.reject();
  }
});

 

Distribution Control

It is possible to use APlay without any distribution control. But this means all microservices and clients are the same in terms of distribution.
In a lot of cases the distribution of information and even of handling things is different between client and server side or even between different microservices.

The basic distribution system can be controlled via a role based system

Setup Roles

storm.createOrUpdateRole('default', 
    {
                                           // possiblity to define default values for all roles
  });
storm.createOrUpdateRole('MyClient', 
  {
    devSetup: []                       // no setup allowed		
  });
storm.createOrUpdateRole('ModeratorClient',
  {
    devSetup: []                       // no setup allowed
    allowedAtConnection: false         // it is not possible to request this role when connecting.
  });
storm.createOrUpdateRole('MyValidationServer', 
  {
    devSetup: ['roles','distribution'] // allowed to setup roles and distribution in dev mode
    allowedIps: ['169.168.0.*',...],   // role only allowed for ips
    allowedInterfaces: [...]           // role only allowed for interfaces	
    allowRoleAssignment: true         // this role can assign roles to endpoints !
  });

Role Assignment
If the connected endpoint does not fit the requirements for the on connect role he will be disconnected. All roles that are assigned at runtime are only available to the connected endpoint until he is disconnected. Runtime role assignments override roles assigned at connection.

storm.connectAsRole("MyClient",...); // directly at connecting a microservice can select the role.
// microservices with allowRoleAssignment == true can assign roles to endpoints
endpoint.addRole("ModeratorClient"); // the auth server could for example set the Moderator role.
// or even set path sepecific roles.
endpoint.removeRole("/chat/room/*/", "ModeratorClient");
endpoint.addRole("/chat/room/[34]/","ModeratorClient");

Control Distribution by path

storm.setup('/samplePath/abc/helloWorldMessage')
.distribution({
  'default':{ 
    send: true,                    // allowed to send
    validation: false,             // needs no validation
    receive: true                  // can receive
  },
  'MyClient':{                       // overrides default config
    requiresValiation:true         // requires validation
    receivesPushNotification: true // this client will receive push notifications for this event.
  },
  'MyValidatorServer':{              // overrides default config
    canValidate:true               // is allowed to validate
  }
});

Defaults and paths
Note: paths behave differently for security and distrubition settings. You can just setup rules based on the beginning of paths and it will apply to all paths that are starting with the path. Deeper paths will override the settings of base paths.

storm.setup('/')
.distribution({
  'default':{                // by default nothing allowed
    send: false,            
    validation: false,      
    receive: false          
  }
});