NATS est un système permettant de gérer une distribution de messages et notamment une file d'attente côté serveur pour vos applications.
C'est un système très facile à utiliser et à mettre en place.
Dans ce cas pratique, nous verrons comment l'installer et l'utiliser avec NodeJS dans le contexte d'une file d'attente et d'un worker qui traite les messages qu'il reçoit au fur et à mesure.
Prérequis
Pour suivre l'installation vous devez connaître les bases de l'éditeur de texte "nano" et avoir installé docker sur votre machine.
Pour la partie Node JS, il est nécessaire d'installer le runtime Node JS et éventuellement Git.
Installation
Création du docker-compose
Connectez-vous sur votre serveur via SSH, créez un dossier pour NATS et positionnez-vous dessus.
mkdir nats
cd nats
Utilisez nano pour créer un fichier "docker-compose.yml".
nano docker-compose.yml
Insérez ceci dans le fichier :
services:
nats:
image: nats:latest
container_name: nats_server
ports:
- "4222:4222"
- "8222:8222"
environment:
- NATS_SERVER_NAME=nats-server
- NATS_MAX_CONNECTIONS=1024
- NATS_USER=nats-user
- NATS_PASSWORD=<votre-mot-de-passe>
volumes:
- ./nats.conf:/etc/nats.conf
- nats_data:/data
command: ["-js", "--config", "/etc/nats.conf"]
restart: unless-stopped
nats_cli:
image: natsio/nats-box:latest
container_name: nats_cli
depends_on:
- nats
environment:
- NATS_URL=nats://nats-user:<votre-mot-de-passe>@nats:4222
entrypoint: /bin/sh
stdin_open: true
tty: true
volumes:
nats_data:
driver: local
Créer le fichier de configuration NATS
Utilisez nano pour créer un fichier "nats.conf".
nano nats.conf
Insérez ceci dans le fichier :
authorization {
users = [
{user: "nats-user", password: "<votre-mot-de-passe>"}
]
}
Lancer l’app
docker compose up -d
Utilisation CLI
Pour utiliser shell à l’intérieur du conteneur CLI :
docker exec -it nats_cli /bin/sh
S’abonner à un sujet NATS
nats sub test
Publier sur un sujet
nats pub test "message"
Publier depuis un serveur distant
nats --user="nats-user" --password="<votre-mot-de-passe>" --server=nats://<ip-de-votre-serveur>:4222 pub test "essai"
Utilisation avec NodeJS
Installer dotenv et NATS
npm i dotenv nats
Créer un fichier .env
Ce fichier évite de coder les valeurs sensibles dans le dur, il définit des "Variables d'environnement".
NATS_URL="nats://<ip-de-votre-serveur>:4222"
NATS_USER="nats-user"
NATS_PASSWORD="<votre-mot-de-passe>"
Créer nats.js pour se simplifier la vie :
Ce bout de code crée une classe NATS pour faciliter la mise en place d'un système de file d'attente avec worker(s).
import { connect } from 'nats';
import dotenv from 'dotenv';
dotenv.config();
const { NATS_URL, NATS_USER, NATS_PASSWORD } = process.env;
export class NATS {
constructor() {
this.connection = null;
}
async connect() {
try {
this.connection = await connect({
servers: NATS_URL,
user: NATS_USER,
pass: NATS_PASSWORD
});
} catch (e) {
console.error('Erreur connection NATS : ', e);
throw Error('Erreur NATS, veuillez réessayer plus tard...');
}
}
async publish(subject, data) {
if (!this.connection) {
await this.connect();
}
try {
await this.connection.publish(subject, JSON.stringify(data));
} catch (e) {
console.error('Erreur publication NATS : ', e);
throw Error('Erreur NATS, veuillez réessayer plus tard...');
}
}
async subscribe(subject, callback, options = {}) {
if (!this.connection) {
await this.connect();
}
try {
const subscription = this.connection.subscribe(subject, options);
for await (const message of subscription) {
const data = JSON.parse(message.data);
await callback(data);
}
} catch (e) {
console.error('Erreur subscription NATS : ', e);
throw Error('Erreur NATS, veuillez réessayer plus tard...');
}
}
async close() {
if (this.connection) {
await this.connection.close();
}
}
}
export async function simplePublish(subject, data) {
const nats = new NATS();
await nats.connect();
await nats.publish(subject, data);
await nats.close();
}
Exemple de code émetteur
La méthode la plus simple pour publier sur un sujet (en l'occurence "test").
import { simplePublish } from './nats.js';
async function main(){
await simplePublish('test', 'Bonjour !');
}
main()
Exemple de code émetteur 2
L'intérêt de ce deuxième exemple est de garder la connexion avec NATS ouverte, puis de la fermer manuellement plutôt que de la réouvrir et refermer à chaque fois comme c'est le cas avec "simplePublish".
import { NATS } from './nats.js';
async function main(){
const nats = new NATS();
await nats.publish('test', 'Bonjour !');
await nats.publish('test', 'Ça va ?');
await nats.close();
}
main()
Exemple de code Worker
Ici, je crée une file d'attente en définissant une "queue" et je limite le nombre d'exécutions simultanées pour ce Worker à 2.
import { NATS } from './nats.js';
(async () => {
const nats = new NATS();
await nats.connect();
console.log('Worker is listening for test events...');
await nats.subscribe(
'test',
async (data) => {
console.log(data);
}
},
{
queue: 'test-group',
max_in_flight: 2
}
);
})();
Aller plus loin
Merci pour votre lecture.