Architecture du suivi de commande

Node.js Paris - 09/10/19

@Frichti X RabbitMQ

Aymeric Dijoux

Kajan Siva

Livrer à un ami

RabbitMQ

Quoi ? & Pourquoi ?

Message broker open-source avec pleins d'avantages :

- Asynchronicité

- Découplage

- Permet une meilleure scalabilité

 

Utilise le protocole AMQP.

Concepts importants

Quatres concepts-clés :

- Messages

- Exchanges

- Bindings

- Queues

Messages

Information transmis du producer vers le consumer, à travers RabbitMQ.

 

Est constitué d'un contenu et d'une clé de routage.

Exchanges

Reçoit les messages du producer et les transmets dans les queues selon les règles définis par le type de l'exchange.

Bindings

Une binding est le lien entre une queue et un exchange.

Queues

Zone tampon qui stocke les messages attendant d'être consommés.

Types d'exchanges

Acknowledgement

Ack: accepte le message.

 

Unack : replace le message en tête de queue.

 

Reject : rejette le message.

Exchange dead letter

Fonctionnalité permettant de capturer les messages qui ne sont pas livrables.

Résumé

Quatres concepts-clés :

- Messages

- Exchanges

- Bindings

- Queues

Livrer à un ami

Étape 1 : Envoi du SMS

"exchanges": [
  {
    "name": "delivery",
    "type": "topic",
  },
  {
    "name": "delivery-dead-letter",
    "type": "topic",
  }
]
"queues": [
  {
    "name": "delivery-notifications",
    "deadLetter": "delivery-dead-letter",
  }
 ];
"bindings": [
  {
    "exchange": "delivery",
    "target": "delivery-notifications",
    "keys": [
      "event.task.created",
      "event.task.updated",
      "event.eta.updated",
      "event.tour.updated"
    ]
  }
]

Configuration - PRODUCER

rabbit.handle(
  {
    type: 'event.task.created',
    queue: 'delivery-notifications',
  },
  async (message) => {
    try {
      await notificationCreated(message);
      message.ack();
    } catch (err) {
      message.nack();
      throw err;
    }
  },
);

Traitement du message - PRODUCER

{
  "notificationCreated": {
    "title": "Suivi de commande",
    "messageToRender": "{{{firstName}}} {{{lastName}}} vient de passer une commande {{{name}}} et c'est vous qui allez la recevoir.\nElle devrait arriver {{{renderDeliveryMessage}}}."
  }
}

Setup du message - PRODUCER

const messageToSend = mustache.render(messageToRender, objectToRender);
async function start() {
  rabbit.handle('send', sendHandler.send(rabbit));

  rabbit.startSubscription('notification-send');
  rabbit.startSubscription('notification-push-os');
  rabbit.startSubscription('notification-email');
}

rabbit.configure(settings).done(() => {
  start()
    .catch((err) => {
      process.exit(1);
    });
});

Configuration rabbit -CONSUMER

if (body.phone) {
	transports = 'sms';
}

try {
  await senders[transports](body);
} catch (err) {
  throw err;
}

Envoi du SMS - CONSUMER

Étape 2 : Suivi de livraison

async function sendDeliveryNotif(message = {}) {
  
  const res = await send(message);
  
  if (res.status === 200 && res.body.errors) {
    throw new Error(`cant push notification from OneSignal ${JSON.stringify(res.body.errors)} `);
  }
  return res.body;
}

Envoi de la push notif - CONSUMER

"queues":[{
  "name": "notification-push-os",
  "deadLetter": "notification",
}]
"exchanges": [
  {
    "name": "notification",
    "type": "topic",
  },
  {
    "name": "notification-push",
    "type": "topic",
  }
]
"binding":[
  {
   "exchange": "notification-push",
   "target": "notification-push-os",
   "keys": ["#"]
  }
]

Configuration - Consumer

if (Object.getOwnPropertyNames(message.properties.headers).length 
    && queue !== 'notification-push-os' 
    && queue !== 'notification-email') {
  body.transports = 'sms';
}

const { transports } = body;

try {
  await senders[transports](body);
} catch (err) {
  throw err;
}

Dead letter SMS - CONSUMER

Étape 3 : Le retour d'expérience

"queues":[{
  "name": "notification-email",
  "deadLetter": "notification",
},
{
  "name": "notification-email-delayed",
  "deadLetter": "notification-email",
}]
"exchanges": [
  {
    "name": "notification-email-delayed",
    "type": "topic",
  },
  {
    "name": "notification-email",
    "type": "topic",
  }
]

Configuration - CONSUMER

"binding":[
  {
    "exchange": "notification-email",
    "target": "notification-email",
    "keys": [
      "#"
    ]
  },
  {
    "exchange": "notification-email-delayed",
    "target": "notification-email-delayed",
    "keys": [
      "#"
    ]
  }
]

Configuration - CONSUMER

const transport = source === 'website-desktop' ? 'email' : 'push';

const notification = await notificationService.send(
  ...message
  90 * MINUTE, // Important part
);

Envoi de l'email

const { transports } = ctx.request.body;

const exchange = config.transportAllowed[transports] || 'notification';

if (ctx.request.headers['x-delayed']) {
  await rabbit.publish(`${exchange}-delayed`, {
    type: 'send',
    body: {},
    expiresAfter: ctx.request.headers['x-delayed'] || undefined,
  });
} 

Publish - CONSUMER

async function sendMail(message) {
  try {
    return sendPost(message);
  } catch (err) {
    throw new Error(
      `cant push notification from OneSignal ${JSON.stringify(err)} `
    );
  }
}

Envoi de l'email

Etape 3

Le retour client

Conclusion

+

=

❤️

Questions ?

Frichti x Rabbitmq Node.js 09/10/19

By Kajan Siva

Frichti x Rabbitmq Node.js 09/10/19

  • 462