Publiez vos événements métier sur un bus EventBridge, jamais directement dans la file SQS d'un autre service, et ne branchez jamais le bus en direct sur Lambda pour une charge de production. Le bus possède le routage : une règle et une file par consommateur. Chaque file possède la livraison : tampon, retries, backpressure, dead-letter queue. Voici pourquoi chaque étape mérite sa place, et quand une simple file reste le bon choix.
Pourquoi un producteur ne doit-il jamais écrire directement dans SQS ?
Parce qu'une file est la boîte de réception privée d'un seul consommateur. Le producteur qui y publie prend une dépendance dure sur l'existence, le nom et les permissions de ce consommateur. Le fan-out meurt : ajouter un deuxième consommateur impose de modifier le code du producteur. Et le filtrage, l'archive et le replay n'ont jamais existé.
- Couplage. Le producteur doit connaître l'URL de la file et détenir
sqs:SendMessagedessus. Renommez ou scindez le consommateur : le producteur redéploie. - Pas de fan-out. Une file livre chaque message à un seul pool de consommateurs. Un deuxième public exige un deuxième appel de publication dans le code du producteur, puis un troisième.
- Dispersion des permissions. Chaque paire producteur-consommateur ajoute une arête IAM de plus, et bientôt personne ne sait qui lit quoi sans un audit.
- Capacités perdues. Pas de filtrage par contenu, pas d'archive, pas de replay, pas de registre de schémas. Un message consommé a disparu.
Que change la publication sur un bus EventBridge ?
Elle inverse la dépendance. Le producteur émet un événement métier sur le bus et cesse de se soucier de qui écoute. Les règles filtrent sur le contenu : chaque consommateur reçoit sa propre règle, sa propre file et son propre Lambda. En ajouter un ne touche à rien côté producteur, et le bus apporte archive, replay et registre de schémas.
EventBridge donne aussi à chaque cible sa propre dead-letter queue pour les échecs de livraison, et l'archive permet de rejouer une plage temporelle d'événements après un correctif. Le contrat du producteur se réduit à une seule chose : une enveloppe stable.
// Publier un événement métier sur le bus (TypeScript, SDK AWS v3)
import { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';
import { randomUUID } from 'node:crypto';
const client = new EventBridgeClient({});
await client.send(new PutEventsCommand({
Entries: [{
EventBusName: 'orders',
Source: 'com.shop.orders',
DetailType: 'OrderPlaced',
Time: new Date(),
Detail: JSON.stringify({
id: randomUUID(), // id côté producteur : les consommateurs dédupliquent dessus
occurredAt: new Date().toISOString(),
orderId: 'ord_1042',
totalCents: 129900,
currency: 'EUR',
}),
}],
}));
Deux disciplines gardent l'enveloppe durable : le producteur génère l'id d'événement (les consommateurs dédupliquent dessus, y compris après un replay), et detail transporte des faits, pas des instructions. La topologie qui en résulte : un producteur, un bus, des règles qui distribuent vers une file par consommateur, chacune drainée par sa propre fonction.
Pourquoi placer SQS entre la règle et le Lambda ?
Parce qu'une cible Lambda branchée en direct sur la règle n'offre ni tampon ni contrôle. Une file entre les deux absorbe les pics, permet la lecture par lots, rejoue sur un visibility timeout que vous choisissez et écarte les messages toxiques après un nombre borné de lectures. Elle donne aussi à chaque consommateur son propre régulateur et son interrupteur.
- Backpressure. Un consommateur lent fait vieillir les messages dans sa propre file, visible sur la métrique d'âge du plus ancien message. Rien n'est perdu, aucun autre consommateur n'est affecté.
- Lecture par lots. La source d'événements SQS livre les enregistrements par lots avec réponse partielle par lot, bien moins cher qu'une invocation par événement.
- Concurrence par consommateur.
maxConcurrencysur l'event source mapping plafonne un consommateur (le plancher est 2) sans affamer le reste du compte, et protège les dépendances fragiles en aval. - De vrais retries. Visibility timeout, compteur de réceptions et redrive policy vers une DLQ que vous pouvez inspecter et drainer.
- Pause et reprise. Désactivez l'event source mapping pendant un incident : les événements s'accumulent sans risque, puis se drainent à la réactivation.
Votre pipeline d'événements a-t-il besoin d'un bus, d'une file, ou des deux ? Décrivez votre système : diagnostic d'une page sous 48 h.
Recevoir mon diagnostic →Comment relier une règle à une file ?
Deux éléments : un pattern sur la règle, et une resource policy sur la file. Le pattern filtre sur les champs de l'enveloppe : chaque consommateur ne reçoit que ce qu'il a demandé. La policy autorise events.amazonaws.com à envoyer des messages, restreinte à l'ARN de cette seule règle. Ajoutez une dead-letter queue sur la cible pour les échecs de livraison.
// Pattern de la règle facturation : seules les grosses commandes en EUR atteignent cette file
{
"source": ["com.shop.orders"],
"detail-type": ["OrderPlaced"],
"detail": {
"currency": ["EUR"],
"totalCents": [{ "numeric": [">=", 50000] }]
}
}
La syntaxe des patterns d'événements couvre les correspondances par préfixe, numériques et d'existence : les décisions de routage vivent dans la configuration, pas dans le code des consommateurs. Côté file, il faut une policy, et l'oublier est l'échec classique du premier déploiement : la règle matche, la livraison échoue, et seule la métrique FailedInvocations de la règle vous le dit.
// Resource policy sur la file facturation : seule cette règle peut y écrire
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowEventBridgeRule",
"Effect": "Allow",
"Principal": { "Service": "events.amazonaws.com" },
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:eu-west-3:123456789012:billing-orders",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:events:eu-west-3:123456789012:rule/orders/billing-large-orders"
}
}
}]
}
Quand écrire directement dans une file reste-t-il le bon choix ?
Quand vous envoyez un ordre (une command), pas quand vous annoncez un fait. Un ordre vise un exécutant connu et attend une action : redimensionner cette image, envoyer ce reçu. La distribution point à point est exactement ce pour quoi SQS existe. L'anti-pattern n'est pas la file : c'est diffuser des événements via la boîte privée d'un consommateur.
Notre test : si une deuxième équipe demandant le même message serait légitime, c'est un événement, il appartient au bus. Si un deuxième lecteur serait un bug, c'est un ordre, et une file est le contrat honnête. Notre guide sur quand utiliser AWS SQS détaille ces cas point à point.
Quels compromis le bus impose-t-il ?
Trois coûts assumés. EventBridge ne garantit pas l'ordre : quand la séquence compte, ciblez une file FIFO et posez un message group id par agrégat. La livraison est at-least-once de bout en bout : les consommateurs dédupliquent sur l'id d'événement du producteur. Et le saut supplémentaire ajoute une latence, typiquement quelques dizaines de millisecondes.
Rien de tout cela n'est un travail nouveau. Les files standard étaient déjà at-least-once : les handlers idempotents étaient donc déjà obligatoires, et nous avons détaillé ces patterns dans notre article sur les systèmes Lambda et SQS fiables. Quant à la latence : si un utilisateur attend le résultat de façon synchrone, mesurez avant d'ajouter des sauts. Pour le travail d'arrière-plan, le contrôle opérationnel vaut bien plus que ces millisecondes.
File directe ou bus d'abord : la comparaison
Le tableau compare les deux câblages sur les propriétés qui décident du comportement en production. La publication directe ne gagne sur rien, sinon le nombre de sauts. Le câblage par le bus coûte une resource policy et quelques millisecondes, et achète fan-out, filtrage, replay, retries par consommateur et des producteurs qui ne changent jamais.
| Propriété | Producteur → SQS direct | Producteur → EventBridge → SQS → Lambda |
|---|---|---|
| Fan-out | Aucun : une file, un pool de consommateurs | Autant de consommateurs que de règles |
| Ajouter un consommateur | Modification et redéploiement du producteur | Nouvelle règle et nouvelle file ; producteur intact |
| Filtrage | Dans le code du consommateur, après livraison | Par contenu, dans la règle, avant livraison |
| Replay | Aucun une fois les messages consommés | Archive du bus, rejouable par plage de temps |
| Sémantique des retries | Visibility timeout sur l'unique file | Par consommateur : timeout, compteur, DLQ dédiés |
| Backpressure | Oui, mais partagée par tous les types de messages | Par consommateur, avec son propre plafond de concurrence |
| Couplage | Le producteur connaît la file et l'IAM du consommateur | Le producteur ne connaît que le bus et l'enveloppe |
La checklist de câblage
Sept vérifications avant la mise en production d'un pipeline d'événements. Chacune renvoie à une section ci-dessus, et ensemble elles garantissent les propriétés qui comptent : des producteurs qui ignorent leurs consommateurs, des consommateurs qui échouent isolément, et des événements rejouables une fois le correctif déployé.
- Les producteurs publient leurs événements métier sur le bus, jamais dans la file d'un autre service.
- Une règle, une file, une fonction par consommateur.
- Une DLQ sur la cible EventBridge et une redrive policy sur la file.
- Une resource policy sur chaque file, restreinte à l'ARN de sa règle.
maxConcurrencyréglé par consommateur ; alarmes sur l'âge des files et la profondeur des DLQ.- File FIFO et message group id partout où l'ordre compte ; consommateurs idempotents partout, déduplication sur l'id d'événement.
- Les ordres vont en point à point vers une file ; les événements vont sur le bus.