Messaging
Hydra supports inter-service communication in the following ways:
- Discovery and direct use of the server's networking info (IP and Port).
- Through the use of the
makeAPIRequest
method. - Using inter-service messaging.
- Using service message queues.
Which approach you use depends on your application's requirements and the amount of extra work you're willing to do. Using Hydra's messaging methods abstracts the network layer functionality you might otherwise need to contend with. So it offers an easier and more reliable way of interacting with remote services.
Discovery and direct use of the service's networking info is straightforward:
let apiRoute = '/v1/email/send';
hydra.findService('emailer')
.then((service) => {
let url = `http://${service.ip}:${service.port}/${apiRoute}`;
let options = {
headers: {
'content-type': 'application/json',
'Accept': 'application/json; charset=UTF-8'
},
method: 'post'
};
options.body = emailObject;
fetch(url, options)
:
:
Note: using the above approach should be preceded by a check to see whether the service is available using the
getServicePresence
method. After all, we want to make sure the service is both registered, and currently available.
This is where using Hydra'smakeAPIRequest
method ends up being easier and less error prone. ThemakeAPIRequest
method accepts an object which contains the service's name along with other useful, but optional, information. The method automatically handles checking for service availability and can even push the message (request) to the service's message queue if the service is temporally unavailable. This is optional behavior and presumes that this is acceptable to the sender and that the remote service is capable of handling the request as a queued message.
let message = hydra.createUMFMessage({
to: 'emailer:/v1/email/send',
from: 'website:backend',
body: {
to: '[email protected]',
from: '[email protected]',
emailBody: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium'
fallbackToQueue: true
}
});
hydra.makeAPIRequest(message)
then()
:
Inter-service messaging
Using Hydra you can send messages between services and even route messages among a series of services. This is one of the features that theHydra-Routeroffers.
Built-in message channels
Every hydra service automatically listens to two built-in channels, where messages sent from other services arrive.
One channel listens to any message sent to a type of service, another channel listens for messages directed to a specific service instance. So a message sent to afile-processing
service would be received by all instances of that service. While a message sent to5585f53bd1171db38eafd79bf16e02f4@file-processing
would only be handled by the service instance with an ID of5585f53bd1171db38eafd79bf16e02f4
.
To send a message to a service you can use thesendMessage
call.
let message = hydra.createUMFMessage({
to: 'test-service:/',
from: 'blue-service:/',
body: {
fileData: '{base64}'
}
});
hydra.sendMessage(message);
The first parameter is the name of the service you want to send a message to, and the second parameter is a UMF formatted object containing a message.
When sendMessage is used, the message is sent to an available, randomly selected, service instance. If you need to specify a specific instance you can simply address a service using its unique service ID. This is shown in theto
message field below.
let message = hydra.createUMFMessage({
to: 'cef54f47984626c9efbf070c50bfad1b@test-service:/',
from: 'blue-service:/',
body: {
fileData: '{base64}'
}
});
hydra.sendMessage(message);
You can obtain a service's unique ID via thegetInstanceID()
orgetServicePresence()
methods.
If you need too, you can use thesendBroadcastMessage
method to send a message to ALL the available instances of a service.
Warning: Although, you can use
sendMessage
to send and respond to messages - it's recommended that you usesendReplyMessage
when replying. The reason for this is that sendReplyMessage uses the source message to properly fill out UMF fields required for robust messaging. This includes things like using the source mid, for, to, from UMF fields to formulate a reply message.
Your service can receive messages by adding a listener to your loaded hydra instance. The example below demonstrates how to also formulate a response if necessary.
hydra.registerService();
hydra.on('message', function(message) {
// message will be a UMF formatted object
console.log(`Received object message: ${msg.mid}: ${JSON.stringify(msg)}`);
// to send a reply message here or elsewhere in your service use the `sendReplyMessage` call.
hydra.sendReplyMessage(message, hydra.createUMFMessage({
body: {
// response items
}
}));
});
UMF messaging
In the prior example, we used a UMF style message, which is created by the HydracreateUMFMessage
method. UMF is an acronym for Universal Message Format and is a light-weight messaging protocol designed for routable and queue-able messaging.
UMF allows you to optionally specify that a message should be sent to one service which in turn should send the message and/or additional results to another service. In this way, processes can be chained across services.
Let's demystify UMF a bit by looking at what thecreateUMFMessage
actually does.
First, the method accepts a message object. In that object three fields are required:
{
"to":'serviceName',
"from": 'sending-entity-name',
"body": {}
}
The createUMFMessage method takes that object and returns a new one with additional fields:
{
"mid": "02d7e85b-5609-4179-b3af-fee60efc8ef0",
"timestamp": "2016-03-28T15:40:05.820Z",
"version": "UMF/1.2",
"priority": "normal",
"type": "msg",
"to": "filewatcher",
"from": "hydramcp",
"body": {
"actions": [
"restart",
"processBatch"
]
}
}
The additional fields are defined by the UMF specification and aid Hydra, and other distributed systems in the handling of messages.
ThecreateUMFMessage
helper method helps ensure that we're starting with a properly formatted UMF compatible message which we can further extend.
For example, here we can change thepriority
andtype
of the message before passing it along to themakeAPIRequest
method.
message.priority = 'high';
message.type = 'service:control';
It's important to note that we could have added thepriority
andtype
fields to our original message that was passed tocreateUMFMessage
. The method will use your supplied fields to overwrite the ones it creates by default. So it's important to not overwritemid
ortimestamp
in careless ways.
Note: For a detailed look at the UMF spec, visit:Universal Messaging Format