a message routing system that routes messages based on their content.
The smart-router is a message routing system that routes messages based on their content. It is meant to be light-weight and HA. Internally, it uses RabbitMQ to handle the messages and socket.io as its transport protocol. It can be used to connect server-side services as well as client-side applications.
To use it:
npm install smart-router
The smart-router will listen to several endpoints or sub-endpoints as defined in its config file. One end point can be divided into sub-endpoints who will share the same route definitions, but if an endpoint has sub-endpoints, the smart-router will listen to the sub-endpoints and not the endpoint itself.
An Actor is a client of the smart-router. It has its own unique Id. It will connect to an endpoint or a sub-endpoint to publish and receive messages. They can be configured to receive messages sent directly to them or sent to their endpoint.
Messages are exchanged by the Actors through the smart-router. It will then introspect them to route them to the right actor or to the right endpoint for one actor to pick them up.
A message has a type and a body which can be repesented like that:
ids contains the ids of the actors or endpoints concerned by the message. By looking, preferably, at the metadata, the smart-router will choose which of these actors it will route the message to. The payload contains application specific data, whereas metadata will contain data used by the routing. (The smart-router still has access to the payload and can decide using it, but it is better to have a clean separation between the two.)
A Route is a function that is called when the smart-router receives a message of a specific type on a specific end point. In this function, the smart-router can look at the endpoint, the message type and the message body to define wht to do with it. Usually, it will publish it as-is to another and point or actor, but it can modify it, fork it and publish it to several endpoints. In the following route, when we receive a message of type business from the serviceA endpoint, we check if it is important. If it is, we route it to serviceC enpoint as an important message and log it by sending it to the logger as a log message. If not, we forward it as-is to serviceB.
endpoint: 'serviceA'messagetype: 'business'if messageidsserviceC && messagemetadataisImportantsmartrouterpublishmessageidsserviceC 'important' message;smartrouterpublishmessageidslogger 'log' message;elsesmartrouterpublishmessageidsserviceB 'business' message;
Queues and Exchanges are an internal notion. Actors don't see the queues and don't know about them. Internally, the route functions will publish messages to some queues and when a new actor connects, it will subscribe to one or two queues. One exchange is created per (sub)endpoint. Queues exist at the (sub)endpoint or actor level, depending on the flags used in the configuration of the endpoint.
endpoints:name: 'endpoint' queue: QUEUEFLAGendpointname: 'subendpoint' sub: 456 457 queue: QUEUEFLAGendpointname: 'actoronly' sub: 'subactor' queue: QUEUEFLAGactor // QUEUEFLAG.actor is the default valuename: 'endpointandactor' queue: QUEUEFLAGendpoint | QUEUEFLAGactor
With this configuration, the smart-router will listen to:
and will use the following queues:
actoronly/subactorwhere actorid is the unique id of the actors connectiong to the end point
endpointandactorwhere actorid is the unique id of the actors connectiong to the end point
During its transit inside the smart-router, a message will:
- be received on the endpoint
- routed using the corresponding route function
- queued on the queue selected by the routing function
- dequeued and
- sent to an actor.
Internally, the smart-router is composed of two modules:
- a socket.io server written in node.js that handles the routing of the messages
- a RabbitMQ cluster that handles the persistence and the publication of the messages. Any number of the node.js application can be deployed as long as they all connect to the same RabbitMQ cluster. A single message can be queued by one instance and dequeued by another. As long as the RabbitMQ is correctly set up to mirror the queues, there is no SPoF.
On start, the smart-router will read a configuration object. This configuration will contain:
portThe port on which the smart-router will listen.
amqpThe amqp connection options.
endpointsThe endpoints configuration. Will define endpoints' names and the socket's namespaces on which the smart-router will listen. Actors will connect on these endpoints. This object will be an array of objects containing the following properties:
subList containing endpoint's sub-endpoints. This will determine on which namespaces the smart-router will listen: If no sub are present, it will listen on
/name. If sub are set, it will listen on
queueA flag to determine the queue(s) which will be created for the endpoint. Use ('./lib').const.QUEUEFLAG to set it. If there is no flag or if
QUEUEFLAG.actoris set, smart-router will create a queue named with the actorId which has established a connection on the namespace. If the flag
QUEUEFLAG.endpointis set, the smart-router will create a generic queue named
routesArray of configuration objects which will define actions to do for each type of message received on an endpoint. Each object will contains:
endpointEndpoint's name (one of those defined in
messagetypeThe name of the event that the smart-router will listen for.
action: function(message, socket, smartrouter)A function which will be called once we receive the event
endpoint. It's here that you need to route the received message. Typically, you will do something like:
smartrouter.publish(queueId, 'messagetype', message)which will publish a message of type
messagetypeto the queue
If you develop your actors in JS, you only have to use the
Actor class as describe in the next section.
In any other language, you would need to use a socket.io client and to implement the handshake protocol:
- when a new Actor connects, the smart-router emits an empty
- the Actor must respond with a
iammessage whose payload will be its unique id. These ids have to be unique through out the whole platform.
- the smart-router responds then with a
- when receiving a message from an unknown Actor (unknown unique id), the smart-router will emit a
whoareyoumessage containing the previous message as a payload (
payload.typebeing the message type, and
payload.messagethe message body.)
- it is expected that the Actor then emits a
iamwith its id and re-emits the rejected message.
In JS, all actors need to extend the raw Actor class defined in
var Actor = require'smart-router'Actor;MyActor = Actorvar socket = thiscallSuper;socketon'myactorevent'// do some awesome stuffsocketemit'responseevent' message;;socketon'otheractorevent'// do other stuff;;
As you see, the only mandatory thing to do in an actor is to extends the
function, to get a reference on the socket by calling its parent, and to add listeners on it.
Of course listeners must match the
messagetype you have configured in
Then, you are able to instantiate your actor:
'localhost:8080' 'endpoint' 'my_actor_id';
An example of basic actor can be found in
The scenario is very simple:
- Actor1 starts by sending a 'message' which will be published to the queue
actor/2(subscribed by actor2).
- The message is routed to actor2 which reply to the queue
actor/1/my_actor_id1(subscribed by actor1)
- The message is routed to actor1 which reply to the queue
actor/2/actor_id2(subscribed by actor2)
- ... It stops after two back and forth.
The test folder contains different actors used to test the behaviour of the smart-router.
agentis the main actor. It will decide of the flow of the messages by adding some metadata.
uisimulates a UI. It can request to talk to the external
serviceis an external service to which some messages can get routed.
npm test from the command line to launch the tests.
Copyright 2012 VirtuOz, Inc.
Licensed 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
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.