0

I am developing a node.js social networking application using senecajs and need to implement a scenario where a producer can send the same message to multiple consumers. I found an article that seems to illustrate sample code for pulling this off using senecajs. The issue is that I am trying to translate this to my scenario, here is the example, from this article(https://github.com/senecajs/seneca-amqp-transport/issues/27):

I have 1 client publish event to 2 listeners.

Client:

.client({
type: 'amqp',
pin: 'incomingMessage:*',
url: process.env.AMQP_URL,
exchange: {
  name: process.env.NODE_ENV + ':events',
  type: 'fanout'
}
});

Listeners:

.listen({
type: 'amqp',
pin:  'incomingMessage:*', //maybe useless
url:  process.env.AMQP_URL,
name: process.env.NODE_ENV + ':service1',
exchange: {
  name: process.env.NODE_ENV + ':events',
  type: 'fanout'
}
});

.listen({
type: 'amqp',
pin:  'incomingMessage:*', //maybe useless?
url:  process.env.AMQP_URL,
name: process.env.NODE_ENV + ':service2',
exchange: {
  name: process.env.NODE_ENV + ':events',
  type: 'fanout'
}
});

There are a few items are confusing:

  1. For the client settings, it seems as if the name will end up being "development:events" or "production:events". Am I correct in this thinking?

  2. For the name field for the listener outside of the exchange object, what is the purpose of this field?

  3. When I call the add method, I need to pass in a name that maps to the function call that is made when the listener receives a message, would I pass the "incomingMessage:*" to the add call?

  4. Will this code actually effectively provide for fanout functionality using senecajs?

halfer
  • 19,824
  • 17
  • 99
  • 186
user1790300
  • 2,143
  • 10
  • 54
  • 123

1 Answers1

2

I have tested the sample. (credit goes for the author on GitHub)

1, Usually NODE_ENV environment variable refers to development, production, staging, etc. It can be anything (like "apple"), but the trick is that this "name" property refers to the "queue" name which has to be unique for each listener/subscriber.

2, Like I have noted above, this "name" property (outside of exchange object) is for the name of the queue. It has to be unique for each listener. Each listener will have it's own queue which is bind to the exchange.

3, It can be anything. But this is tricky as well.

For the client who will "publish" to the fanout exchange it needs to have the same pin in the options as the called action's pattern. For example if the client will call:

seneca.act('event:orderReceived', optionalPayload)

then the pin has to be:

{
    pin: 'event:orderReceived',
    ...
}

See Seneca API docs for how patterns work.

For the listeners, in this case (for RPC this is not true) they don't have to match the pin. Only the pattern has to match when you call seneca.add function (little bit strange). For example:

seneca.add('event:orderReceived', function (msg, respond) {
    // some logic
})

the pin property can be:

{
     pin: 'something:different'
}

4, For the last question the answer is true, it can provide a publish/subscribe functionality. I have tested it.

Note that if you don't pass a callback to seneca.act when "publishing" to exchange, then it does not wait for any response. For example:

seneca.act('event:orderReceived', { some: "payload" })

Also note, that if you don't need a response, you will have to call the response callback with a null for first (error) parameter anyway, in the listener service, otherwise it will timeout. See in the examples below.

Here is my version of the code:

Publisher

const seneca = require('seneca')
const si = seneca()

si
    .use('seneca-amqp-transport')

    .client({
        type: 'amqp',
        pin: 'event:orderReceived',
        url: 'amqp://localhost:5672',
        exchange: {
            name: 'order-service',
            type: 'fanout'
        }
    })
    .ready( function() {
        si.log.info('order-service application is running...')
        si.act('event:orderReceived', {})
    })

process.on('SIGTERM', () => {
    si.log.info('Got SIGTERM. Graceful shutdown start')
    si.act('role:seneca,cmd:close')
})

Subscriber 1

const seneca = require('seneca')
const si = seneca()

si
    .use('seneca-amqp-transport')

    .add('event:orderReceived', function(msg, respond) {
        si.log.info(msg)
        respond(null)
    })

    .listen({
        type: 'amqp',
        pin:  'some:pin1',
        url:  'amqp://localhost:5672',
        name: 'delivery-service-queue',
        exchange: {
            name: 'order-service',
            type: 'fanout'
        }
    })

    .ready( function() {
        si.log.info('delivery-service application is running...')
    })

process.on('SIGTERM', () => {
    si.log.info('Got SIGTERM. Graceful shutdown start')
    si.act('role:seneca,cmd:close')
})

Subscriber 2

const seneca = require('seneca')
const si = seneca()

si
    .use('seneca-amqp-transport')

    .add('event:orderReceived', function(msg, respond) {
        si.log.info(msg)
        respond(null)
    })

    .listen({
        type: 'amqp',
        pin:  'some:pin2',
        url:  'amqp://localhost:5672',
        name: 'financial-service-queue',
        exchange: {
            name: 'order-service',
            type: 'fanout'
        }
    })

    .ready( function() {
        si.log.info('financial-service application is running...')
    })

process.on('SIGTERM', () => {
    si.log.info('Got SIGTERM. Graceful shutdown start')
    si.act('role:seneca,cmd:close')
})

I am not sure that this is a proper way, but seems to work.

bersilius
  • 21
  • 5