Aller au contenu

Émettre un événement métier

Ce guide s'adresse aux auteurs de modules. Il décrit l'enveloppe à publier, la convention de nommage des types, et la manière de l'émettre depuis un module Java.

Le principe

Un module qui vient d'écrire quelque chose de notable publie une enveloppe sur le sujet logs d'Apache Pulsar. Il ne s'adresse pas au module Journal d'événements, ne l'attend pas, et ne sait pas ce qu'il en fait. Le journal consomme ce sujet pour son propre compte.

Un événement décrit un fait accompli. On l'émet donc après l'écriture, jamais avant, et son verbe est au passé.

L'enveloppe

L'enveloppe est un objet JSON :

{
  "specversion": "1",
  "id": "6f1c8e2a-9b34-4d51-8a7e-2c3f5b1d0e77",
  "time": "2026-08-23T09:41:12Z",
  "source": "stocks",
  "type": "stocks.inventory.validated",
  "organization_id": "c81b0d44-2f5a-4a19-9d7c-8e0a6b3f21ce",
  "actor": {
    "id": "3a7e1f90-5c26-4b83-a1d4-9f0e7c2b6d15",
    "name": "Awa Koné"
  },
  "subject": {
    "type": "inventory",
    "id": "1d9c4b7e-8a05-4f62-b3e1-7c25a0f9d834",
    "label": "INV-2026-0042"
  },
  "level": "INFO",
  "correlation_id": "b2f4a681-3d70-49ce-8b52-1a6e0c94f7d3",
  "data": {
    "warehouse": "Entrepôt principal",
    "lines": 17
  }
}
Champ Obligatoire Rôle
specversion Oui Version de l'enveloppe, "1"
id Oui UUID de l'événement, employé pour la déduplication
time Oui Instant du fait, ISO 8601 en UTC
source Oui Module émetteur, identique au premier segment de type
type Oui Nature du fait, selon la convention ci-dessous
organization_id Oui Organisation à laquelle le fait appartient
actor Oui Auteur du fait : son identifiant et son nom
subject Oui Entité concernée : son type, son identifiant et son libellé
level Oui INFO, WARN, SECURITY ou ERROR
correlation_id Non Relie plusieurs événements issus d'un même traitement
data Oui Données métier libres, objet JSON de 16 KiB au plus

L'enveloppe entière ne dépasse pas 32 KiB, dont 16 KiB au plus pour data. Au-delà, le message part en lettre morte plutôt que d'être tronqué.

Les libellés sont dénormalisés à l'émission

subject.label et actor.name sont recopiés dans l'enveloppe au moment de l'émission, et jamais relus ensuite. Un événement dit donc ce que valait le libellé ce jour-là : renommer un article plus tard ne réécrit pas l'histoire. C'est voulu.

La convention de nommage

Un type se lit <source>.<entité>.<verbe-au-passé>, en ASCII minuscule :

[a-z0-9_]+(\.[a-z0-9_]+){2}

Trois segments exactement, séparés par des points. Le premier est le nom du module et doit être égal au champ source. Le deuxième nomme l'entité au singulier. Le troisième est le verbe, au participe passé.

Les verbes recommandés, à préférer à toute invention :

created, updated, deleted, validated, cancelled, executed, closed, archived, imported, exported

Correct Incorrect Pourquoi
stocks.inventory.validated stocks.validate_inventory Deux segments, et un verbe à l'infinitif
sales.cash_receipt.closed sales.cashReceipt.closed Le camelCase n'entre pas dans le motif
stocks.article.updated stock.article.updated La source est le module stocks

Le niveau

INFO est le cas par défaut. On ne s'en écarte que pour une raison :

  • WARN — un fait qui mérite l'attention sans être une faute
  • SECURITY — une connexion, une permission refusée, un export de données
  • ERROR — un échec métier notable

L'acteur système

Un événement émis hors d'une requête HTTP — traitement planifié, consommation d'un message, migration — n'a pas d'utilisateur derrière lui. Il emploie alors l'acteur système :

  • actor.id : 00000000-0000-0000-0000-000000000000
  • actor.name : le nom du module

En Java

La bibliothèque com.minlessika.erp:business-events, paquet com.minlessika.events, porte l'enveloppe et sa publication.

final Envelope envelope = new Envelope(
    new Occurrence(
        new Stamp(),
        new EventType("stocks.inventory.validated"),
        orgId
    ),
    new Actor(userId, "Awa Koné"),
    new Subject("inventory", id, "INV-2026-0042"),
    new Details(Level.INFO, data)
);
broadcast.emitLater(new BusinessEventMessage(envelope));

Stamp fixe l'identifiant et l'instant de l'événement. emitLater diffère la publication : le message est écoulé après le commit de la transaction, et purgé si elle est annulée. Un fait qui n'a pas eu lieu n'est donc jamais journalisé.

L'appel se place après l'écriture métier, dans la même transaction qu'elle.

Chaque module enregistre LogsPublishing une fois, au démarrage, pour disposer de broadcast.

Une fois émis

Le module Journal d'événements consomme le sujet logs et range les enveloppes dans une table events partitionnée par mois. La déduplication se fait sur id : republier deux fois la même enveloppe ne crée pas deux lignes.

Une enveloppe que le consommateur ne sait pas lire part vers logs-DLQ et devient visible dans l'écran Lettres mortes. Un type hors motif ou une enveloppe trop grosse s'y retrouvent : c'est là qu'il faut regarder quand un événement attendu n'apparaît pas dans le journal.

Pensez enfin à déclarer votre nouvel événement dans le Catalogue des événements.

Changelog

  • 1.0 (23 août 2026) : création du document.