Tutoriale / Centrala komunikatów z nagłówkami

3390fe26-b53f-3007-ed44-61c9276e2f8d

Ten rodzaj centrali wiadomości jest ulepszoną wersją centrali wiadomości bezpośrednich. Zasadniczą zmianą jest wykorzystanie nagłówków zamiast kluczy routing’u.

Każdy nagłówek ma dwie cechy:

  • nazwę,
  • wartość.

Z tego też powodu zamiast używać kluczy routing’u, które mogły być tylko łańcuchami znaków, można użyć np. liczb całkowitych lub funkcji skrótu (ang. hash) na bazie słownika.

Nagłówki

Lista nagłówków może wyglądać następująco:

Nazwa Wartość
x-match any
type error
app node.js
host 10.0.0.15

Wyróżniamy dwa rodzaje sposobów dopasowania wiadomości do określonych filtrów w centralach z nagłówkami ustawiane jako argument dla parametru x-match:

  • any – informujący centralę, że wystarczy, aby pasował przynajmniej jeden z filtrów, co można określić również przy pomocy słowa lub (ang. or);
  • all – informujący centralę, że muszą pasować wszystkie filtry, co można określić przy pomocy słowa oraz (ang. and); Ponieważ na stronie RabbitMQ w sekcji „Tutorials” zaprezentowano wszystkie rodzaje central z wyjątkiem central z nagłówkami, przyjrzymy się temu rodzajowi routing'u dokładniej na konkretnym przykładzie.

Oba rodzaje dopasowania ilustrują dwa diagramy:

  • any:
    Diagram przedstawiający filtrowanie wiadomości przez centralę komunikatów z nagłówkami z dopasowaniem any
  • all:
    Diagram przedstawiający filtrowanie wiadomości przez centralę komunikatów z nagłówkami z dopasowaniem all

Jako bazowy kod weźmy kod programu fabryka.js, który w uproszczeniu wyglądał następująco:

#!/usr/bin/env node
var amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(err, conn) {
  conn.createChannel(function(err, ch) {
    // nowe instrukcje
  });

  setTimeout(function() { conn.close(); process.exit(0) }, 500);
});

Zanim zaczniemy pisać dwa nowe programy (nadawcę i odbiorcę), potrzebujemy określić co one będą dla nas robić?

Przypuśćmy, że nadawca przesyła nam z portalu randkowego:

  • imię osoby,
  • płeć,
  • kolor włosów.

Natomiast odbiorca będzie miał ustawione „na sztywno” pożądane cechy drugiej połówki, tzn. w kodzie będzie zaszyte, jaką płeć i jaki kolor włosów swojej wybranki/wybranka preferuje.

Zacznijmy od aplikacji serwisu randkowego, który będzie wysyłał „profile” do systemu RabbitaMQ. Nazwijmy go randkoder.js

Program randkoder.js

Tuż po zaimportowaniu biblioteki amqplib definiujemy sposób korzystania z naszego nadawcy:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

var args = process.argv.slice(2);
var numberOfArguments = 3;
var numberOfAttributes = 2;

if (args.length !== numberOfArguments) {
  console.log("Korzystanie: randkoder.js [imię] [płeć: k|m] [kolor włosów: blond|rude|siwe|czarne]");
  console.log("             randkoder.js Karolina k blond");
  process.exit(1);
}

amqp.connect('amqp://localhost', function(err, conn) {});

W zmiennej numberOfAttributes ustaliliśmy, że będziemy podawać trzy cechy konkretnej osoby z portalu randkowego, czyli imię, płeć oraz kolor włosów.

Wywołanie programu z inną liczbą parametrów spowoduje wyświetlenie odpowiedniego komunikatu na konsoli.

Wartości podane do programu mogą być dowolne, jednak aby pomóc użytkownikowi w korzystaniu z programu, dostarczamy mu przykładowe dane, np. rudy kolor włosów. Nie definiujemy oddzielnego słownika, aby program był jak najprostszy.

W sekcji do uzupełnienia najpierw definiujemy nazwę centrali:

var ex = 'randkomierz';

Następnie pobieramy cechy kandydatki/kandydata:

var args = process.argv.slice(2);
var name = args[0];
var attributes = args.slice(1);

Potem definiujemy klucze dla naszych nagłówków odpowiadających przekazywanym do programu cechom:

var headersList = ['plec', 'kolor-wlosow'];

oraz wypełniamy nagłówki wartościami:

var headersValues = {};

for (var i=0; i<numberOfAttributes; i++) {
  var headerName  = headersList[i];
  var headerValue = attributes[i];

  headersValues[headerName] = headerValue;
}

Na koniec wysyłamy wiadomość do kuriera wiadomości:

ch.assertExchange(ex, 'headers', {durable: false});
ch.publish(ex, '', new Buffer(name), {headers: headersValues});

console.log(" [x] Wysłano profil '%s'", name);

Kompletny program randkoder.js wygląda następująco:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

var args = process.argv.slice(2);
var numberOfArguments = 3;
var numberOfAttributes = 2;

if (args.length !== numberOfArguments) {
  console.log("Korzystanie: randkoder.js [imię] [płeć: k|m] [kolor włosów: blond|rude|siwe|czarne]");
  console.log("             randkoder.js Karolina k blond");
  process.exit(1);
}

amqp.connect('amqp://localhost', function(err, conn) {
  conn.createChannel(function(err, ch) {
    var ex = 'randkomierz';
    var args = process.argv.slice(2);
    var name = args[0];
    var attributes = args.slice(1);
    var headersList = ['plec', 'kolor-wlosow'];
    var headersValues = {};

    for (var i=0; i<numberOfAttributes; i++) {
      var headerName  = headersList[i];
      var headerValue = attributes[i];

      headersValues[headerName] = headerValue;
    }

    ch.assertExchange(ex, 'headers', {durable: false});
    ch.publish(ex, '', new Buffer(name), {headers: headersValues});

    console.log(" [x] Wysłano profil '%s'", name);
  });

  setTimeout(function() { conn.close(); process.exit(0) }, 500);
});

Pozostało nam już tylko użyć napisanego „randkodera”:

Terminal1 $ ./randkoder.js Miłosława k czarne
 [x] Wysłano profil 'Miłosława'

Terminal1 $ ./randkoder.js Anna k rude
 [x] Wysłano profil 'Anna'

Program swatka.js

Gdy mamy już program nadawcy, pora zabrać się za program swatka.js, który będzie odfiltrowywał nam profile, którymi nie jesteśmy zainteresowani, a wyłapywał tylko te najciekawsze w oparciu o zdefiniowane przez nas cechy.

Ponieważ program nie jest skomplikowany, możemy przejść do analizy od razu całego kodu:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(err, conn) {
  conn.createChannel(function(err, ch) {
    var ex = 'randkomierz';

    ch.assertExchange(ex, 'headers', {durable: false});

    ch.assertQueue('', {exclusive: true}, function(err, q) {
      console.log(' [*] Oczekiwanie na wiadomości w:');
      console.log('     %s', q.queue);
      console.log("     Naciśnij CTRL+C aby zakończyć.");

      ch.bindQueue(q.queue, ex, '', {
        'x-match': 'all',
        'plec': 'k',
        'kolor-wlosow': 'czarne'
      });

      ch.consume(q.queue, function(msg) {
        console.log(" [x] '%s' pasuje do Ciebie", msg.content.toString());
      }, {noAck: true});
    });
  });
});

Definiujemy tutaj centralę wiadomości z nagłówkami o tej samej nazwie co w programie nadawcy – „randkoder”, a następnie podpinamy do niej kolejkę, wskazując interesujące nas cechy.

W centralach z nagłówkami nie używamy kluczy routing’u, zatem w ich miejsce podajemy pusty łańcuch.
W powyższym przykładzie zastosowano dopasowanie „all”, dzięki czemu możemy lepiej zobaczyć, jak program działa z konkretnymi danymi.

Teraz, gdy uruchomimy program swatka.js, będziemy mogli zobaczyć, jak działa nasze filtrowanie:

$ ./swatka.js
[*] Oczekiwanie na wiadomości w
     amq.gen-z3KMAIo8WpAW72DaFdqc5w
     Naciśnij CTRL+C, aby zakończyć.
 [x] 'Miłosława' pasuje do Ciebie

Nic nie stoi na przeszkodzie, aby zmienić tę opcję na „any”:

$ ./swatka.js
 [*] Oczekiwanie na wiadomości w
     amq.gen-z1KRAIo8WpAW72DaFdqd3z
     Naciśnij CTRL+C, aby zakończyć.
 [x] 'Miłosława' pasuje do Ciebie
 [x] 'Anna' pasuje do Ciebie

Przykładowe zastosowania

Centrale komunikatów typu headers można stosować przy obsłudze:

  • przekazywania wyników pracy w wieloetapowych krokach działania aplikacji pomiędzy jej podsystemami (wzorzec blankietu routing’u – ang. routing slip pattern),
  • systemów dystrybuowania skompilowanych aplikacji (ang. builds) / systemów dostarczania oprogramowania w sposób ciągły (ang. continuous delivery) / systemów aktualizacji oprogramowania w sposób ciągły (ang. continuous deployment) / systemów ciągłej integracji dbające między innymi o jakość wytwarzanego oprogramowania poprzez uruchamianie testów automatycznych i integracyjnych (ang. continuous integration) wykonujące swoje zadania na bazie wielu parametrów (np. systemu operacyjnego, rodzaju architektury procesora, dostępności określonego pakietu).

Repozytorium z kodem źródłowym

Kod programów omawianych w tym tutorialu znajduje się pod adresem:
https://github.com/RattiQue/tutorials-pl-node.js

Tomasz Kuter

Web Developer z ponad 8-letnim, komercyjnym doświadczeniem w tworzeniu stron i aplikacji internetowych oraz paneli administracyjnych w PHP, JavaScript, HTML i CSS.
Aktualnie zainteresowany architekturą mikroserwisów, które umożliwiają budowanie skalowalnych aplikacji internetowych.