|
| 1 | +# @github1/meshage |
| 2 | + |
| 3 | +A simple service mesh. Messages sent within the service mesh can be consistently partitioned across members of the cluster. |
| 4 | + |
| 5 | +[](https://travis-ci.org/github1/meshage) |
| 6 | +[](https://www.npmjs.com/package/meshage) |
| 7 | +[](https://www.npmjs.com/package/meshage) |
| 8 | + |
| 9 | +## Install |
| 10 | + |
| 11 | +```shell |
| 12 | +npm install meshage --save |
| 13 | +``` |
| 14 | + |
| 15 | +## Usage |
| 16 | + |
| 17 | +Initialize a node: |
| 18 | + |
| 19 | +```javascript |
| 20 | +const meshage = require('meshage'); |
| 21 | +meshage |
| 22 | + .init( |
| 23 | + // Initialize the cluster to join (Grapevine or Consul) |
| 24 | + new meshage.GrapevineCluster( |
| 25 | + process.env.CLUSTER_PORT, |
| 26 | + (process.env.SEEDS || '').split(',')), |
| 27 | + process.env.HTTP_PORT |
| 28 | + ) |
| 29 | + .register('echo', message => { |
| 30 | + // Register a message handler on the 'echo' stream |
| 31 | + return { echoed: message }; |
| 32 | + }) |
| 33 | + .start(); |
| 34 | +``` |
| 35 | + |
| 36 | +Start one or more instances: |
| 37 | + |
| 38 | +```shell |
| 39 | +CLUSTER_PORT=9742 SERVICE_PORT=8080 node index.js |
| 40 | +CLUSTER_PORT=9743 SEEDS=127.0.0.1:9742 HTTP_PORT=8081 node index.js |
| 41 | +``` |
| 42 | + |
| 43 | +Each node exposes an HTTP endpoint which accepts messages for registered streams. When a request is received by any instance registered to the cluster, a consistent hashing algorithm is used to determine which node should handle the request. If the node which receives the initial HTTP request is the designated handler it will respond directly, otherwise the receiving node will route the request to the designated node within the cluster. |
| 44 | + |
| 45 | +*Request:* |
| 46 | + |
| 47 | +```shell |
| 48 | +curl -sX POST http://localhost:8080/api/echo/$RANDOM \ |
| 49 | + -H 'Content-Type: application/json' \ |
| 50 | + -d '{"hello":"world"}' |
| 51 | +``` |
| 52 | + |
| 53 | +*Response:* |
| 54 | + |
| 55 | +```json |
| 56 | +{ |
| 57 | + "echoed": { |
| 58 | + "hello": "world" |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +## HTTP API |
| 64 | + |
| 65 | +### Send |
| 66 | + |
| 67 | +Sends a message to be handled consistently by a registered handler for the specified stream. |
| 68 | + |
| 69 | +**URL** : `/api/:stream/:partitionKey` |
| 70 | + |
| 71 | +**URL Parameters** : |
| 72 | +- `stream` - the logical name for the message handler. |
| 73 | +- `partitionKey` - the identifier for the `entity` receiving the message. |
| 74 | + |
| 75 | +**Example** : |
| 76 | + |
| 77 | +*Request:* |
| 78 | + |
| 79 | +```shell |
| 80 | +curl -sX POST http://localhost:8080/api/echo/$RANDOM \ |
| 81 | + -H 'Content-Type: application/json' \ |
| 82 | + -d '{"hello":"world"}' |
| 83 | +``` |
| 84 | + |
| 85 | +*Response:* |
| 86 | + |
| 87 | +```json |
| 88 | +{ |
| 89 | + "echoed": { |
| 90 | + "hello": "world" |
| 91 | + } |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +### Broadcast |
| 96 | + |
| 97 | +Sends a message to all registered handlers for the specified stream. |
| 98 | + |
| 99 | +**URL** : `/api/broadcast/:stream/:partitionKey` |
| 100 | + |
| 101 | +**URL Parameters** : |
| 102 | +- `stream` - the logical name for the message handler. |
| 103 | +- `partitionKey` - the identifier for the `entity` receiving the message. |
| 104 | + |
| 105 | +**Example** : |
| 106 | + |
| 107 | +*Request:* |
| 108 | + |
| 109 | +```shell |
| 110 | +curl -sX POST http://localhost:8080/api/broadcast/echo/$RANDOM \ |
| 111 | + -H 'Content-Type: application/json' \ |
| 112 | + -d '{"hello":"world"}' |
| 113 | +``` |
| 114 | + |
| 115 | +*Response:* |
| 116 | + |
| 117 | +```json |
| 118 | +[ |
| 119 | + { |
| 120 | + "echoed": { |
| 121 | + "hello": "world" |
| 122 | + } |
| 123 | + }, |
| 124 | + { |
| 125 | + "echoed": { |
| 126 | + "hello": "world" |
| 127 | + } |
| 128 | + } |
| 129 | +] |
| 130 | +``` |
| 131 | + |
| 132 | +## JS API |
| 133 | + |
| 134 | +### Init |
| 135 | +Configure the cluster to join. |
| 136 | + |
| 137 | +**init(cluster : Cluster) : MessageRouter** |
| 138 | +- `cluster` - an instance of `Cluster` which is responsible for advertising and discovering message handlers. |
| 139 | +- `address` - (optional) an *host:port* pair (string) or simply a numeric port (number) to listen for HTTP requests on |
| 140 | + |
| 141 | +```javascript |
| 142 | +const node = meshage.init(cluster, 8080); |
| 143 | +``` |
| 144 | + |
| 145 | +#### *Cluster Implementations:* |
| 146 | + |
| 147 | +#### GrapevineCluster |
| 148 | + |
| 149 | +Leverages an implementation of the Gossip protocol to discover nodes and services. |
| 150 | + |
| 151 | +**GrapevineCluster(address : (string | number), seeds : (string | number)[])** |
| 152 | +- `address` - accepts a *host:port* pair (string) or simply a numeric port (number). If only a port is provided, the host defaults to `127.0.0.1`. |
| 153 | +- `seeds` - accepts an array of `address` values (following the same behavior as the *address* argument). |
| 154 | + |
| 155 | +```javascript |
| 156 | +// The initial node in the cluster will not have seeds |
| 157 | +new meshage.GrapevineCluster(9473); |
| 158 | +// Subsequent nodes in the cluster need to specify at least one existing node as a seed |
| 159 | +new meshage.GrapevineCluster(9474, [9473]); |
| 160 | +``` |
| 161 | + |
| 162 | +#### ConsulCluster |
| 163 | + |
| 164 | +Connects to a consul agent/cluster for service registration. |
| 165 | + |
| 166 | +**ConsulCluster(address : (string | number), seeds : (string | number)[])** |
| 167 | +- `address` - an *host:port* pair (string) or simply a numeric port (number). *The cluster address should point to the associated consul agents HTTP API (typically port 8500)*. |
| 168 | +- `seeds` - an array of `address` values (following the same behavior as the *address* argument). *The seed address should be point to a consul agents serf_lan port (typically port 8301)*. |
| 169 | + |
| 170 | +```javascript |
| 171 | +new meshage.ConsulCluster('127.0.0.1:8500'); |
| 172 | +``` |
| 173 | + |
| 174 | +#### Custom Implementations |
| 175 | + |
| 176 | +Custom cluster types may be provided by implementing the `core/cluster/Cluster` interface. |
| 177 | + |
| 178 | +## Register |
| 179 | + |
| 180 | +Registers a message handler on the node. |
| 181 | + |
| 182 | +**register(stream : string, handler : (message : Message) => any) : MessageRouter** |
| 183 | +- `stream` - the stream name to accept messages for. |
| 184 | +- `handler` - the message handler function. |
| 185 | + |
| 186 | +```javascript |
| 187 | +node.register('someStream', message => { |
| 188 | + return {}; |
| 189 | +}); |
| 190 | +``` |
| 191 | + |
| 192 | +## Start |
| 193 | + |
| 194 | +Joins the cluster and begins advertising the nodes message handlers. |
| 195 | + |
| 196 | +**start(callback : (router : ConnectedMessageRouter) => void)** |
| 197 | +- `callback` - (optional) accepts a callback function which is provided a router instance. The router instance can be used to send or broadcast messages to nodes in the cluster. |
| 198 | + |
| 199 | +```javascript |
| 200 | +node.start(router => { |
| 201 | + router |
| 202 | + .send({ stream: 'echo', partitionKey: 'c6c5e7f3-6228-41ce-a7ea-23ac24a08a32', data: 'hello' }) |
| 203 | + .then(res => { |
| 204 | + console.log(res); |
| 205 | + }); |
| 206 | +}); |
| 207 | +``` |
| 208 | + |
| 209 | +The `router` instance passed to the `start` callback exposes two methods: |
| 210 | + |
| 211 | +### Send |
| 212 | + |
| 213 | +Sends a message to be handled consistently by a registered handler for the specified stream. Depending on how the message is routed, it could be handled by the node itself. |
| 214 | + |
| 215 | +**send(message : Message) : Promise<{}>** |
| 216 | +- `message` - the message to send |
| 217 | + |
| 218 | +### Broadcast |
| 219 | + |
| 220 | +Sends a message to all registered handlers for the specified stream. |
| 221 | + |
| 222 | +**broadcast(message : Message) : Promise<{}>** |
| 223 | +- `message` - the message to send |
| 224 | + |
| 225 | +## Address Formats |
| 226 | + |
| 227 | +Address may be supplied in the following formats: |
| 228 | + |
| 229 | +### Host and port string |
| 230 | + |
| 231 | +The host and port separated by a colon. |
| 232 | + |
| 233 | +_Example_ |
| 234 | + |
| 235 | +`localhost:8080` |
| 236 | + |
| 237 | +### Port number |
| 238 | + |
| 239 | +Just the port (as a number or string). If no explicit hostname is provided, `os.hostname()` is used to determine the host. |
| 240 | + |
| 241 | +_Example_ |
| 242 | + |
| 243 | +`8080` |
| 244 | + |
| 245 | +### Finding open ports |
| 246 | + |
| 247 | +By suffixing the address with the keyword `find`, the library will attempt to find an open port to listen on. |
| 248 | + |
| 249 | +_Example_ |
| 250 | + |
| 251 | +- `localhost:find` - use localhost, but find an open port |
| 252 | +- `localhost:8080/find` - use localhost and port 8080 if available, otherwise find an open port |
| 253 | +- `8080/find` - use port 8080 if available, otherwise find an open port |
| 254 | +- `find` - find any open port |
| 255 | + |
| 256 | +## License |
| 257 | +[MIT](LICENSE.md) |
0 commit comments