2.1.1 • Published 6 years ago

rabrpc v2.1.1

Weekly downloads
5
License
MIT
Repository
github
Last release
6 years ago

RabRPC

NPM version Build status Dependency Status Coverage Status Code Climate Known Vulnerabilities

Yet another opinionated RPC library based on RabbitMQ (through rabbot)

Features

  • Promise-based interface (thanks rabbot)
  • Convention over configuration in exchange, queue, routingKeys naming

Implemented producer/consumer patterns:

  • Request / Response
  • Publish / Subscribe
  • Send / Receive

Installation

npm install --save rabrpc
# or
yarn add rabrpc

Examples

Initialization

Important! rpc.configure should be called after binding handlers via rpc.respond in consumer microservice and must be called before requesting data with rpc.request in provider microservice

rpc.configure(config, transformConfig = true)
  • config - rabrpc or rabbot config object
  • transformConfig - if config is a rabbot setting transformConfig must be false, default true
rabbot json configuration

If you need more flexibility, you can pass a valid rabbot configuration into rpc.configure The only requirement is name exchanges, queues and bindings with convention

const config = {
  connection: {
    user: 'guest',
    pass: 'guest',
    server: '127.0.0.1',
    // server: "127.0.0.1, 194.66.82.11",
    // server: ["127.0.0.1", "194.66.82.11"],
    port: 5672,
    timeout: 2000,
    vhost: '%2fmyhost'
  },
  exchanges: [
    { name: 'config-ex.1', type: 'fanout', publishTimeout: 1000 },
    {
      name: 'config-ex.2',
      type: 'topic',
      alternate: 'alternate-ex.2',
      persistent: true
    },
    { name: 'dead-letter-ex.2', type: 'fanout' }
  ],
  queues: [
    { name: 'config-q.1', limit: 100, queueLimit: 1000 },
    { name: 'config-q.2', subscribe: true, deadLetter: 'dead-letter-ex.2' }
  ],
  bindings: [
    { exchange: 'config-ex.1', target: 'config-q.1', keys: ['bob', 'fred'] },
    { exchange: 'config-ex.2', target: 'config-q.2', keys: 'test1' }
  ]
}

rpc.configure(config, false) // transform config = false

Request / Response

Responder initialization

const rpc = require('rabrpc') // singleton

const config = {
  // uri
  connection: 'amqp://guest:guest@localhost:5672/?heartbeat=10',
  // or object passed to rabbot see https://github.com/arobson/rabbot#configuration-via-json
  // connection: {user: 'guest', pass: 'guest', server: 'localhost', ...}

  // respond configuration in consumer microservice
  // this config will create exchange(s), queue(s) and binding(s)
  // request configation in provider microservice only create exchange(s)

  res: {
    // string or object or array of strings or objects
    serviceName: 'foo-service-name',
    // rabbot queue options, see https://github.com/arobson/rabbot#addqueue-queuename-options-connectionname-
    // subscribe: true is default
    messageTtl: 30000,
    limit: 10
    // ...etc
  }
}

// somewhere in your microservice initialization cycle
rpc.configure(config) // returns promise

Requester initialization

const rpc = require('rabrpc') // singleton

const config = {
  connection: '<URI string>', // see above

  // requesting resource configuration
  // this config will create only exchange(s)
  // respond configuration in consumer microservice will create queue(s) and binding(s)

  // req: 'foo-service-name' | ['foo-service-name', 'bar-service-name'] | {serviceName: 'foo-service-name'} | [{serviceName: 'foo-service-name'}, {serviceName: 'bar-service-name'}]
  req: 'foo-service-name'
}

// somewhere in your microservice initialization cycle
rpc.configure(config) // returns promise

Convention

ParmeterValueExample
exchangereq-res.serviceNamereq-res.foo-service-name
queuereq-res.serviceNamereq-res.foo-service-name
routingKeyserviceNamefoo-service-name
messageTypeversion.serviceName.actionv1.foo-service-name.someAction

Response

rpc.respond(messageType, handler, raw = false)
  • messageType - full path for service action, e.g. 'v1.images.resize' or 'v1.users.role.findAll' where second part (images, users) is a serviceName specified in config (in rabbot using as type of message)
  • handler - function, which takes payload or message, responseActions and messageType
    • payload or message - message if raw is true otherwise message.body
    • responseActions - object with 3 functions success, fail, error
    • messageType - type of rabbot message (usefull when listening for types, which contain * or #)
  • raw - if true then first argument for handler will be rabbot message instead message.body by default
Example
const rpc = require('rabrpc')

// before initialization
rpc.respond('v1.foo-service-name.someAction', (payload, actions, messageType) =>
  actions.success(payload * 2)
)

// handler can aslo just return promise, or `.then`able or value and result will be replied with success status
// exception or rejected promise will cause replying error (be sure throw `Error` with message)

rpc.respond('v1.foo-service-name.anotherAction', payload =>
  SomeDB.query({
    /* ... */
  }).then(rows => ({ count: rows.count, data: rows }))
)
rpc.respond('v1.foo-service-name.thirdAction', payload => payload * 2)

rpc.respond(
  'v1.foo-service-name.someResource.*',
  (payload, actions, messageType) => {
    const [version, serviceName, resource, actionName] = messageType.split('.')
    switch (actionName) {
      case 'find':
        return Resource.findAll(paylaod)
      case 'create':
        return Resource.create(payload)
      case 'destroy':
        return Resource.destroy(payload)
      default:
        throw new Error(`Action '${actionName}' is not supported!`)
    }
  }
)

// in your service initialization cycle
rpc.configure(config)
Request
rpc.request(messageType, payload, options, raw = false)
  • messageType - see rpc.respond messageType argument
  • payload - payload data, which will passed into respond handler (see supported payload)
  • options - rabbot request options (will be merged with defaults: {replyTimeout: 10000})
  • raw - resolve rabbot reply message instead of message.body

returns Promise, which resolved with body (or message if raw is true)

  • body or message
Example
const rpc = require('rabrpc')

// request allowed only after initialization
rpc
  .configure(config)
  .then(() => rpc.request('v1.foo-service-name.someAction', 42))
  .then(body => {
    console.log('response:', body.data) // body = {status: 'succes', data: 84}
  })

Publish / Subscribe

Subscriber initialization

const rpc = require('rabrpc') // singleton

const config = {
  connection: 'amqp://guest:guest@localhost:5672/?heartbeat=10',
  sub: {
    // string or object or array of strings or objects
    serviceName: 'foo-service-name',
    limit: 10
    // ...etc
  }
}

// somewhere in your microservice initialization cycle
rpc.configure(config) // returns promise

Publisher initialization

const rpc = require('rabrpc') // singleton

const config = {
  connection: '<URI string>', // see above
  // pub: 'foo-service-name' | ['foo-service-name', 'bar-service-name'] | {serviceName: 'foo-service-name'} | [{serviceName: 'foo-service-name'}, {serviceName: 'bar-service-name'}]
  pub: 'foo-service-name'
}

// somewhere in your microservice initialization cycle
rpc.configure(config) // returns promise

Convention

ParmeterValueExample
exchangepub-sub.serviceNamepub-sub.foo-service-name
queuepub-sub.serviceName.uuid4pub-sub.foo-service-name.110ec58a-a0f2-4ac4-8393-c866d813b8d1
routingKeyserviceNamefoo-service-name
messageTypeversion.serviceName.actionv1.foo-service-name.someAction
Subscribe
rpc.subscribe(messageType, handler, raw = false)
  • messageType - full path for service action, e.g. 'v1.images.archive' or 'v1.statistics.synchronize' where second part (images, statistics) is a serviceName specified in config (in rabbot using as type of message)
  • handler - function, which takes payload or message, actions and messageType
    • payload or message - message if raw is true otherwise message.body
    • messageType - type of rabbot message (usefull when listening for types, which contain * or #)
  • raw - if true then first argument for handler will be rabbot message instead message.body by default
Example
const rpc = require('rabrpc')

// before initialization
rpc.subscribe(
  'v1.foo-service-name.someAction',
  (payload, actions, messageType) => {}
)

// always auto ack

// in your service initialization cycle
rpc.configure(config)
Publish
rpc.publish(messageType, payload, options)
  • messageType - see rpc.respond messageType argument
  • payload - payload data, which will passed into respond handler (see supported payload)
  • options - rabbot publish options (will be merged with defaults: {replyTimeout: 10000})

returns rabbot publish Promise (see Rabbot Publish)

Example
const rpc = require('rabrpc')

// publish allowed only after initialization
rpc
  .configure(config)
  .then(() => rpc.publish('v1.foo-service-name.someAction', 42))
  .then(() => {
    console.log('Message published')
  })

Send / Receive

Receiver initialization

const rpc = require('rabrpc') // singleton

const config = {
  connection: 'amqp://guest:guest@localhost:5672/?heartbeat=10',
  recv: {
    // string or object or array of strings or objects
    serviceName: 'foo-service-name',
    messageTtl: 30000,
    limit: 10
    // ...etc
  }
}

// somewhere in your microservice initialization cycle
rpc.configure(config) // returns promise

Sender initialization

const rpc = require('rabrpc') // singleton

const config = {
  connection: '<URI string>', // see above
  // send: 'foo-service-name' | ['foo-service-name', 'bar-service-name'] | {serviceName: 'foo-service-name'} | [{serviceName: 'foo-service-name'}, {serviceName: 'bar-service-name'}]
  send: 'foo-service-name'
}

// somewhere in your microservice initialization cycle
rpc.configure(config) // returns promise

Convention

ParmeterValueExample
exchangesend-recv.serviceNamesend-recv.foo-service-name
queuesend-recv.serviceNamesend-recv.foo-service-name
routingKeyserviceNamefoo-service-name
messageTypeversion.serviceName.actionv1.foo-service-name.someAction
Receive
rpc.receive(messageType, handler, raw = false)
  • messageType - full path for service action, e.g. 'v1.images.archive' or 'v1.statistics.synchronize' where second part (images, statistics) is a serviceName specified in config (in rabbot using as type of message)
  • handler - function, which takes payload, actions and messageType
    • payload or message - message if raw is true otherwise message.body
    • actions - object with 3 functions ack, nack, reject (see Rabbot Message API)
    • messageType - type of rabbot message (usefull when listening for types, which contain * or #)
  • raw - if true then first argument for handler will be rabbot message instead message.body by default
Example
const rpc = require('rabrpc')

// before initialization
rpc.receive('v1.foo-service-name.someAction', (payload, actions, messageType) =>
  actions.ack()
)

// handler can aslo just return promise, or `.then`able or value and message will be ack'ed on promise resolution
// exception or rejected promise will cause nack'ing message

rpc.receive('v1.foo-service-name.anotherAction', payload =>
  SomeDB.query({
    /* ... */
  })
) // auto ack

rpc.receive(
  'v1.foo-service-name.someResource.*',
  (payload, actions, messageType) => {
    // you can manually ack message if you don't need default behaviuor
    actions.ack() // DO NOT RETURNS PROMISE
    const [version, serviceName, resource, actionName] = messageType.split('.')
    switch (actionName) {
      case 'find':
        return Resource.findAll(paylaod)
      case 'create':
        return Resource.create(payload)
      case 'destroy':
        return Resource.destroy(payload)
      default:
        throw new Error(`Action '${actionName}' is not supported!`) // will not produce nack call
    }
  }
)

// in your service initialization cycle
rpc.configure(config)
Send
rpc.send(messageType, payload, options)
  • messageType - see rpc.respond messageType argument
  • payload - payload data, which will passed into respond handler (see supported payload)
  • options - rabbot publish options (will be merged with defaults: {replyTimeout: 10000})

returns rabbot publish Promise (see Rabbot Publish)

Example
const rpc = require('rabrpc')

// send allowed only after initialization
rpc
  .configure(config)
  .then(() => rpc.send('v1.foo-service-name.someAction', 42))
  .then(() => {
    console.log('Message sended')
  })

Graceful shutdown

rpc.stopSubscription()

remove all handlers, unsubscribe from queues

Supported payload

  • string
  • number
  • null
  • JSON serializable Object
  • Buffer
2.1.1

6 years ago

2.1.0

6 years ago

2.0.1

6 years ago

2.0.0

6 years ago

1.3.1

6 years ago

1.3.0

6 years ago

1.2.0

7 years ago

1.1.1

7 years ago

1.1.0

7 years ago

1.0.0

7 years ago

0.1.14

7 years ago

0.1.13

7 years ago

0.1.12

7 years ago

0.1.11

7 years ago

0.1.10

7 years ago

0.1.9

7 years ago

0.1.8

7 years ago

0.1.7

7 years ago

0.1.5

7 years ago

0.1.4

7 years ago

0.1.3

8 years ago

0.1.2

8 years ago

0.1.0

8 years ago

0.0.12

8 years ago

0.0.11

8 years ago

0.0.10

8 years ago

0.0.9

8 years ago

0.0.8

8 years ago

0.0.7

8 years ago

0.0.6

8 years ago

0.0.5

8 years ago

0.0.4

8 years ago

0.0.3

8 years ago

0.0.2

8 years ago

0.0.1

8 years ago