Aller au contenu
Retour aux projets
Plan d'architectureProjet personnelEn cours9 min de lecture11 sections

OpsPulse

Plateforme d'exploitation et d'incidents en temps réel

Ingère des événements système en volume, déduplique les alertes en incidents et pilote la prise en charge et l'escalade via une machine à états explicite. Les écritures passent par EF Core ; les tableaux de bord lisent des projections Dapper et se mettent à jour en direct via SignalR.

Ingestion à haut débit, séparation lecture/écriture et diffusion en temps réel.

  • .NET 10
  • ASP.NET Core
  • Dapper
  • RabbitMQ
  • SignalR
  • Hangfire
  • Serilog
  • Docker

Projet personnel — pas encore de dépôt public

01

Le problème qu'elle sert à travailler

OrderFlow, c'est peu de données et beaucoup de règles. OpsPulse est l'image miroir : beaucoup de données et une petite machine à états au milieu. C'est volontaire — les deux réunis couvrent l'essentiel de ce sur quoi on demande à un architecte backend de raisonner.

La tension centrale est que l'ingestion veut être rapide et bête, tandis que la gestion d'incidents veut être prudente et à état. Mettre les deux sur un même chemin d'écriture en dégrade nécessairement une.

02

Le chemin d'ingestion

Services supervisésémetteurs
Sondes planifiéesémetteurs
poster les événements
API d'ingestionaccepter et mettre en file
publier
RabbitMQMassTransit
diffuser
Journal d'événementsbrut, append-only
Moteur de détectiondédoublonner et corréler
déclencher
Incidentagrégat
projeter et pousser
Modèles de lecture Dappertableau de bord
SignalRclients en direct
Le seul travail de l'API est d'accepter et de mettre en file. La détection se fait hors du thread de requête.

Accepter un événement ne déclenche aucune analyse. On valide la forme, on l'écrit et on répond — parce que la seule chose pire qu'une alerte manquée est un point d'ingestion qui ralentit assez pour commencer à les refuser pendant l'incident même qu'il devait attraper.

03

Le dédoublonnage, c'est tout le produit

Un service en panne n'émet pas un événement. Il en émet des milliers, et une plateforme qui transforme chacun d'eux en incident est pire que pas de plateforme du tout, parce qu'elle apprend aux gens à l'ignorer. Condenser un déluge en un incident assorti d'un compteur est la fonctionnalité que tout le reste sert à soutenir.

  • Les événements portent une empreinte — service, contrôle et type de défaillance — et ceux qui partagent une empreinte se replient dans l'incident ouvert correspondant plutôt que d'en créer un nouveau.
  • Des fenêtres de suppression empêchent un service instable de réouvrir sans cesse le même incident.
  • Le flux d'événements brut est conservé intact et en append-only : un incident dédoublonné peut donc toujours être redéployé en tout ce qui l'a alimenté.

Un système d'alerte se juge à ce pour quoi il ne vous réveille pas.

04

La machine à états d'un incident

Le statut d'un incident est une machine à états sur l'agrégat, pas une colonne de statut. Les mouvements légaux sont peu nombreux, et l'intérêt de les encoder est qu'un incident ne peut pas être résolu sans avoir été pris en charge, ni pris en charge deux fois par des personnes différentes, quel que soit l'ordre d'arrivée des messages.

  1. 1OuvertDéclenché par le moteur de détection. Les minuteurs d'escalade démarrent ici.
  2. 2Pris en chargeUn humain s'en est saisi. L'escalade s'arrête ; les minuteurs de SLA continuent.
  3. 3EscaladéAucune prise en charge dans la fenêtre prévue par la politique — l'astreinte suivante est notifiée.
  4. 4RésoluÉtat terminal. Une réouverture crée un incident lié plutôt que de modifier un incident clos.

Chaque transition s'ajoute à une chronologie immuable. Après l'incident, la question est toujours « quand l'avons-nous su, et quand avons-nous agi » — à laquelle on ne peut répondre que si la réponse a été enregistrée au fil de l'eau.

05

Escalade et astreinte

L'escalade est pilotée par le temps, ce qui en fait du travail planifié et non du travail événementiel. Hangfire détient les minuteurs ; quand l'un se déclenche, il publie un événement et l'escalade elle-même circule par le même chemin de messagerie que tout le reste.

06

Lecture et écriture, séparées

C'est ici que CQRS cesse d'être un nom de pattern pour commencer à payer son loyer. Les écritures sont petites, fréquentes et transactionnelles. Les lectures sont larges, agrégées et constantes — un tableau de bord d'exploitation est une charge de requêtes permanente.

Côté écritureCôté lecture
FormeUn incident à la foisCompteurs, plages et agrégations sur beaucoup
OutilEF Core 10, suivi, transactionnelDapper, SQL explicite, projections
Optimisé pourLa justesse sous concurrenceLa latence à charge constante
PaginationSans objetPar keyset, parce que les offsets dérivent sous les insertions

La pagination par keyset compte davantage ici que dans la plupart des systèmes : des lignes sont insérées en continu, et une pagination par offset sur une table qui bouge montre au lecteur des doublons et des trous à mesure qu'il tourne les pages.

07

Le tableau de bord en direct

Un tableau de bord qu'il faut rafraîchir n'est pas un tableau de bord d'exploitation. SignalR pousse la création, la prise en charge et la résolution des incidents vers les clients connectés au moment où elles se produisent, et le client les applique à l'état qu'il détient déjà plutôt que de tout recharger.

  • Les envois sont des diffs, pas des rechargements complets du tableau de bord.
  • Un client qui se reconnecte se resynchronise depuis le modèle de lecture : une socket coupée ne peut donc pas laisser l'interface silencieusement périmée.
  • Uniquement côté client — le trafic entre services reste sur RabbitMQ.
08

Le travail planifié

  • Sondes de santé contre les services enregistrés, pour que le silence se distingue de la bonne santé.
  • Minuteurs d'escalade et de SLA.
  • Retraitement des événements dont la détection a échoué, hors du chemin d'ingestion.
  • Rétention et agrégation du flux d'événements brut.
09

Observer l'observateur

Une plateforme de supervision que l'on ne peut pas superviser est un point de défaillance unique avec des étapes en plus. Le même dispositif Serilog et OpenTelemetry s'applique, auquel s'ajoutent les questions propres à cette forme : retard d'ingestion, retard de détection, et l'écart entre les modèles de lecture et le côté écriture.

  • Retard d'ingestion et profondeur de file comme métriques de premier ordre, pas comme lignes de logs.
  • Corrélation de l'événement entrant jusqu'à l'incident qu'il a alimenté, en passant par la détection.
  • Liveness et readiness séparées, pour qu'une file pleine ne se lise pas comme un processus mort.
10

En quoi cela diffère d'OrderFlow

Les deux ne sont volontairement pas le même système avec d'autres noms. Ils sollicitent des parties différentes de la même boîte à outils, et c'est précisément l'intérêt de construire les deux.

OrderFlowOpsPulse
Le point durLa cohérence transactionnelle à travers un workflowLe débit, et la condensation du volume en sens
Poids du domaineLourd — des règles par commandeLéger — une petite machine à états
Poids des donnéesLégerLourd
Défaillance à craindreUne commande bloquée en cours de workflowUne tempête d'alertes, ou une alerte manquée
Côté lectureUtilePorteur
11

Les compromis que j'accepte

DécisionCoût
Ingestion « accepter et mettre en file »L'appelant n'obtient aucun résultat de détection ; les incidents apparaissent un instant plus tard
Dédoublonnage par empreinteUne mauvaise empreinte fusionne des défaillances distinctes, ou en scinde une
Événements bruts en append-onlyLe stockage croît vite ; rétention et agrégation sont obligatoires, pas optionnelles
Modèles de lecture DapperLes projections doivent être maintenues à la main à mesure que le modèle d'écriture change

Envie d'en discuter la conception ?

C'est une conception : elle est donc encore discutable. Si vous pensez que l'un des compromis ci-dessus est mauvais, je préfère l'entendre maintenant plutôt qu'après la construction.