Une commande console peut rester fiable pendant des mois, puis devenir le point le plus fragile du système le jour où son volume triple, où deux ordonnanceurs la lancent ensemble ou où un partenaire ralentit. Le symptôme visible est un processus tué ou un lot incomplet ; la cause réelle est souvent l’absence de contrat entre le code, l’ordonnanceur et l’équipe qui devra reprendre l’exécution.
Le vrai enjeu est simple : une commande Symfony qui modifie le métier doit être conçue comme un service exploité, pas comme un script caché derrière bin/console. Elle possède une unité de travail, un identifiant d’exécution, des limites de ressources, une sémantique de relance et une sortie comprise sans ouvrir le code. Plus le SI est exigeant, moins le succès peut se résumer à « le processus a rendu zéro ».
Contre-intuitivement, déplacer immédiatement tout traitement long vers une file de messages n’est pas toujours un progrès. Une commande découpée, observable et reprenable peut être plus simple qu’un système distribué mal maîtrisé. Messenger devient utile lorsque l’indépendance des unités, la régulation ou la reprise unitaire compensent réellement les nouveaux états à exploiter.
Dans une application web métier sur mesure, la bonne décision relie donc l’effet métier au mode d’exécution. La méthode propose des critères, des seuils locaux à calibrer et un plan de mise en œuvre pour que cron, opérateurs et développeurs partagent le même verdict.
Donner un contrat d’exploitation à chaque commande
Le nom et la description indiquent l’intention, mais le contrat commence par les entrées acceptées, l’unité traitée et l’effet produit. Une commande de réconciliation doit préciser si elle rapproche une période, un compte ou une sélection explicite. Un argument implicite comme « la veille » rend un rejeu historique dépendant de l’horloge ; une date ou un identifiant de campagne le rend reproductible.
Le contrat ferme aussi le résultat. Il distingue « terminé sans anomalie », « terminé avec rejets métier », « interrompu avant validation » et « échec technique sans verdict ». Ces états ne se confondent pas avec une exception PHP. Ils sont persistés avec l’identifiant d’exécution afin qu’un tableau de bord, une alerte ou un opérateur puisse les retrouver après la disparition du processus.
La documentation officielle du composant Console fournit arguments, options, aides et codes de sortie pour l’interface CLI. Le domaine doit encore définir ce que « réussi » signifie et quelle preuve rend ce verdict opposable.
Choisir entre console, message et service
La console convient à une orchestration bornée : maintenance demandée par un opérateur, traitement planifié, import déclenché par fichier ou migration pilotée. Elle reste une porte d’entrée. Le cas d’usage vit dans un service applicatif testable sans démarrer le noyau Console ; la commande convertit les options, crée le contexte, appelle le service et traduit son résultat.
Messenger devient préférable lorsque chaque unité peut avancer indépendamment, qu’une pression doit être absorbée ou qu’un échec unitaire ne doit pas annuler le lot. Il ne faut pas confondre ce besoin avec une simple durée longue. Mille lignes séquentielles avec un checkpoint peuvent être plus sûres que mille messages si l’ordre, la transaction ou la limitation du partenaire imposent une coordination forte.
Un service synchrone appelé depuis HTTP convient lorsque l’utilisateur attend un résultat court et que la limite de temps est compatible avec l’expérience. L’équipe décide par effet : même règle métier, plusieurs adaptateurs possibles. Une commande peut préparer une campagne, Messenger exécuter ses unités et une API exposer son avancement sans dupliquer la décision.
Rendre le traitement relançable sans double effet
L’idempotence ne signifie pas « relancer toute la commande sans réfléchir ». Elle signifie qu’une même intention identifiée ne crée pas deux factures, deux notifications ou deux transitions. La clé doit être métier : campagne et contrat, fichier et numéro de ligne, clôture et période. Un UUID différent à chaque tentative protège seulement le transport, pas l’effet.
Pour une écriture locale, une contrainte unique et une transaction courte ferment souvent le doublon. Pour un appel externe, la commande transmet une clé d’idempotence si le partenaire la supporte, puis conserve requête, réponse et statut inconnu. Après un timeout, « on ne sait pas » est un état normal : réessayer aveuglément transformerait une incertitude réseau en double opération.
Seuil local à qualifier. Une équipe peut décider qu’après une tentative automatique sur un verdict inconnu, le traitement passe en réconciliation humaine. Ce nombre n’est pas universel : il dépend du coût d’un doublon, de la capacité du partenaire à rechercher une opération et du délai métier acceptable.
Découper les volumes et poser des checkpoints
Un lot utile se tient dans un budget de mémoire et dans une transaction assez courte pour ne pas bloquer le reste du système. L’équipe mesure le poids réel des objets hydratés, les requêtes et la durée de verrouillage avant de fixer une taille. « Cent éléments » n’est pas une bonne pratique universelle : cent documents lourds et cent identifiants n’ont pas le même profil.
Le checkpoint enregistre la dernière unité confirmée, le nombre de rejets et la version des règles. Il est écrit après l’effet durable, jamais avant. Si le tri peut changer pendant l’exécution, un simple offset est instable ; un curseur sur une clé immuable ou une sélection figée évite de sauter des objets ajoutés entre deux pages.
Cas concret. Un import de 80 000 tarifs est découpé par fichier, ligne et compte client. Le pilote commence sur 2 000 lignes représentatives. La taille augmente seulement tant que le pic mémoire reste sous le budget local, que le verrou médian reste compatible avec les écritures en ligne et que la reprise tient dans la fenêtre d’intervention décidée.
Maîtriser concurrence, verrous et chevauchements
Un verrou global empêche parfois deux clôtures simultanées, mais il bloque aussi des périmètres indépendants. La clé doit refléter le conflit réel : organisation et période, fichier ou campagne. Un verrou local fondé sur le système de fichiers ne coordonne pas plusieurs nœuds ; un store partagé ajoute au contraire une dépendance dont la panne doit être prévue.
Les verrous expirants demandent une marge et un rafraîchissement. Un TTL plus court que le traitement autorise un second processus à entrer pendant le premier ; un TTL très long prolonge inutilement l’indisponibilité après un crash. La documentation officielle du composant Lock explicite cette tension et le rafraîchissement des tâches longues.
Le verrou ne remplace jamais l’idempotence. Un redéploiement, une rupture de connexion ou une erreur de store peut le faire disparaître. La défense solide combine exclusion au meilleur effort, contrainte métier en base et détection des intentions déjà appliquées.
Normaliser sorties, erreurs et codes de retour
La sortie humaine sert à suivre une exécution interactive ; la sortie structurée sert à l’automatisation. Une option JSON peut publier l’identifiant d’exécution, les compteurs et le verdict sans obliger l’ordonnanceur à analyser une phrase colorée. Les secrets, données personnelles et contenus sensibles restent exclus dans les deux formats.
Les codes de sortie sont peu nombreux et documentés. Zéro signifie que le contrat de succès est rempli. Un code non nul distingue au minimum l’entrée invalide, le conflit d’exécution, l’échec technique et le lot terminé avec un seuil de rejets dépassé. Multiplier cinquante codes fragiles déplace simplement la complexité vers le shell.
Une erreur actionnable contient l’étape, le périmètre, l’identifiant de corrélation et la prochaine action sûre. La pile technique reste dans la trace ; l’opérateur doit surtout savoir si la relance est permise, depuis quel checkpoint et si un système externe a pu recevoir l’effet.
Gérer signaux, interruption et ressources
En production, un arrêt propre peut venir d’un déploiement, d’un timeout d’orchestrateur ou d’une intervention. La commande cesse de prendre de nouvelles unités, termine ou annule l’unité courante selon son contrat, écrit son checkpoint puis rend un verdict d’interruption. Intercepter un signal sans borner la durée de fermeture ne garantit pas un arrêt propre.
Le budget porte sur mémoire, CPU, connexions et débit vers les dépendances. Une commande qui appelle une API à vingt requêtes par seconde sur un poste ne doit pas supposer ce débit en production. Les limites sont injectées par configuration, observées et associées à une politique de ralentissement.
Signal faible. L’usage régulier d’une mémoire illimitée ou d’un timeout désactivé indique souvent que l’unité de travail est trop grosse. Le premier remède est de réduire l’état conservé, libérer les références et découper le lot ; augmenter la machine vient après mesure, pas avant diagnostic.
Brancher cron et ordonnanceur sans dette cachée
L’ordonnanceur possède le calendrier, les retries de lancement et la collecte des sorties. La commande possède les effets métier et la reprise interne. Si cron relance toutes les cinq minutes une tâche qui dure parfois huit minutes, le conflit doit produire un état visible plutôt qu’un second traitement silencieux.
Les horaires tiennent compte du fuseau, des changements d’heure et des fermetures métier. Une clôture « tous les jours à deux heures » ne suffit pas : faut-il traiter la journée locale de chaque filiale, attendre une source amont ou geler une période ? La date métier est passée explicitement, puis enregistrée avec son fuseau.
Le déploiement évite de lancer une nouvelle version au milieu d’un lot sans compatibilité de checkpoint. Une commande lit la version de schéma de son exécution et refuse une reprise incompatible avec un message clair. Le rollback applicatif conserve les migrations et événements nécessaires à la version encore active.
Observer l’exécution avec un verdict métier
Chaque lancement crée une exécution portant commande, version, arguments non sensibles, initiateur, dates et verdict. Les métriques suivent durée, unités réussies, rejetées ou inconnues, âge du dernier succès et retard sur l’échéance métier. Une alerte sur « processus absent » est moins utile qu’une alerte sur « clôture de 7 h non confirmée à 8 h 15 ».
La cardinalité reste maîtrisée : l’identifiant de client appartient aux traces consultables, pas aux labels de métriques. Les journaux structurés relient exécution, lot et unité. Une pile d’exception conserve le diagnostic technique, tandis qu’un événement métier explique le refus attendu sans le classer en panne.
Le seuil d’alerte part d’une distribution observée. Sur un traitement habituellement terminé entre 18 et 24 minutes, l’équipe peut ouvrir une alerte à 35 minutes et une intervention à 45 minutes, après une période témoin. Ces valeurs restent locales : elles changent avec la volumétrie, la fenêtre métier et la capacité d’astreinte.
Fermer droits, secrets et périmètre d’action
Le compte système applique le moindre privilège. Une commande de lecture ne reçoit pas un rôle capable de supprimer ; une commande d’une filiale ne peut pas choisir arbitrairement une autre organisation par option. Les autorisations vivent aussi dans le cas d’usage, car un appel hors CLI doit conserver la même frontière.
Les secrets viennent du mécanisme d’exécution, jamais d’un argument visible dans l’historique du shell ou la liste des processus. Les journaux filtrent jetons, chaînes de connexion et payloads sensibles. Un mode de simulation ne garantit pas l’innocuité s’il appelle un partenaire, verrouille des tables ou consomme une file : ses effets permis sont écrits et testés.
Les commandes destructrices demandent un périmètre explicite et une confirmation adaptée au contexte. En automatisation, une option d’approbation seule est insuffisante ; l’autorisation est portée par le pipeline, l’identité d’exécution et une limite vérifiable comme une campagne préparée auparavant.
Cas concret : recalculer des contrats B2B
Une entreprise doit recalculer chaque nuit les échéanciers de 120 000 contrats après réception d’indices externes. L’ancienne commande chargeait toutes les entités, écrivait au fil de l’eau et envoyait une notification. Après un timeout du fournisseur, l’équipe ignorait quels contrats avaient reçu deux messages et recommençait le lot complet.
La nouvelle conception crée une campagne par date métier et version d’indice. Elle fige les contrats éligibles, les répartit en lots mesurés, puis confie chaque unité à un service idempotent. La notification part d’un événement enregistré avec la nouvelle version d’échéancier ; un relais indépendant l’expédie et peut la reprendre.
Le pilote couvre deux agences, 3 000 contrats et trois pannes simulées : indisponibilité de l’indice, arrêt après écriture et timeout de notification. Le passage suivant exige zéro doublon, une réconciliation des états inconnus et une reprise par l’exploitation dans la fenêtre locale. Ce seuil appartient à ce risque financier ; il ne devient pas une norme pour tous les batches.
Tester succès, reprise et scénarios dégradés
Le test unitaire porte sur le cas d’usage et ses règles. Le test de commande vérifie le mapping des arguments, les codes de sortie et les messages essentiels. Un test d’intégration utilise le store de verrou, la base et les transactions réelles afin de révéler les hypothèses invisibles dans un double en mémoire.
La matrice de reprise interrompt avant sélection, après effet local, pendant l’appel externe et avant checkpoint. Elle relance avec la même intention puis vérifie état final, compteur et absence d’effet double. Les tests de charge observent surtout mémoire maximale, durée de transaction et débit soutenable, pas un record isolé.
La recette appartient aussi au run. Une personne qui n’a pas écrit la commande suit l’aide, déclenche un petit périmètre, retrouve l’exécution et applique la procédure d’échec. Si elle doit demander quelle requête SQL lancer ou quel fichier modifier, le traitement n’est pas encore exploitable.
Préparer le runbook et la reprise opérateur
Le runbook commence par les symptômes : retard, conflit, rejets métier, panne de dépendance ou verdict inconnu. Pour chacun, il indique la vérification, l’action sûre, la personne qui décide et le critère d’escalade. Il ne demande jamais de « relancer pour voir » sans préciser l’identifiant et la garantie contre les doubles effets.
Le retour arrière peut signifier annuler une campagne non commencée, restaurer une projection ou appliquer une compensation. Il n’est pas toujours possible d’effacer un effet externe. Le runbook distingue donc rollback technique et correction métier, conserve la preuve de l’état précédent et refuse les promesses de réversibilité fictive.
Après incident, l’équipe examine le contrat qui a manqué : unité trop large, état inconnu non modélisé, alerte tardive ou procédure inaccessible. La correction renforce ce point précis. Ajouter un retry global sans comprendre la causalité augmente souvent la durée de l’incident suivant.
Qualifier les équipes et contextes concernés
Cette méthode concerne les commandes qui écrivent des données métier, pilotent des imports, rapprochent des systèmes, génèrent des documents ou préparent des migrations. Elle est particulièrement utile lorsque plusieurs nœuds peuvent exécuter le même traitement, qu’une fenêtre métier existe ou qu’une équipe d’exploitation distincte intervient.
Une commande locale de génération de code, sans effet partagé et lancée par un développeur, n’a pas besoin d’un registre d’exécution complet. Elle mérite toujours une aide claire et des erreurs propres. La gouvernance reste proportionnée à l’impact, pas au prestige technique du composant.
Le product owner définit verdict métier et tolérance aux rejets ; le développeur possède idempotence et checkpoints ; la plateforme garantit ressources, identité et signaux ; l’exploitation possède alertes et procédure. Une responsabilité sans pouvoir de décision n’est qu’une étiquette.
Erreurs fréquentes qui rendent le batch opaque
- Traiter tout dans une transaction. La promesse d’atomicité devient un verrou long et une reprise intégrale. Il faut réduire l’unité ou préparer une campagne dont chaque résultat reste cohérent.
- Confondre retry et reprise. Un retry répète une tentative ; une reprise sait ce qui a été confirmé, refusé ou laissé inconnu. Sans ce modèle, le retry automatise les doubles effets.
- Utiliser un verrou global sans expiration pensée. Il masque la vraie clé de conflit et peut bloquer tous les clients après un crash. Le verrou est qualifié par périmètre, store et comportement de perte.
- Retourner zéro malgré des rejets critiques. L’ordonnanceur croit le contrat rempli alors qu’une file manuelle grandit. Le seuil de rejets et le code associé sont décidés avec le métier.
- Journaliser chaque objet sans hiérarchie. Le volume rend l’incident illisible et peut exposer des données. Les compteurs résument ; les traces ciblées permettent d’enquêter à partir d’un identifiant.
Plan d’action : industrialiser en six semaines
Semaines 1 et 2 : inventorier et choisir un pilote
L’équipe recense les commandes avec fréquence, durée, volume, effets, dépendances, propriétaire et procédure actuelle. Elle rapproche incidents et reprises manuelles. Elle choisit un pilote important mais borné, capable de révéler concurrence, interruption et appel externe sans engager immédiatement toute la clôture annuelle.
Elle écrit contrat de succès, états d’exécution et clé d’intention. Une baseline mesure mémoire, durée, transactions et rejets sur un échantillon représentatif. Les seuils du pilote sont explicitement locaux et accompagnés de leur raison.
Semaines 3 et 4 : construire la reprise
Le cas d’usage sort de la classe Command. L’équipe ajoute campagne, lots, checkpoint, contrainte d’idempotence et verrou de concurrence. Elle structure sorties et métriques, puis simule les quatre points d’interruption. Les états inconnus sont réconciliés avant toute répétition automatique.
Le runbook est exécuté par l’exploitation sur le pilote. Chaque explication orale est transformée en information du produit, contrôle ou décision documentée. La procédure inclut un arrêt propre et un retour vers la version précédente lorsque le schéma le permet.
Semaines 5 et 6 : déployer et décider l’extension
Le pilote tourne d’abord sur un périmètre réduit, en doublon de calcul sans double écriture lorsque cela est possible. Les résultats sont comparés objet par objet. L’équipe augmente la volumétrie seulement si fenêtre, taux de rejets, pic mémoire et reprise restent dans les bornes décidées.
La revue classe les autres commandes : conserver simples, appliquer le patron de lot, déplacer vers Messenger ou supprimer. Elle finance l’observabilité et la maintenance des dépendances. Le succès n’est pas le nombre de commandes migrées, mais la réduction prouvée du temps de diagnostic et du risque de double effet.
- À faire d’abord : documenter l’intention, le verdict et les effets du pilote.
- À corriger ensuite : découper les unités et rendre la même intention idempotente.
- À tester avant le go : provoquer arrêt, chevauchement, timeout externe et reprise.
- À différer : déplacer vers Messenger tant que la reprise séquentielle reste plus lisible.
- À refuser : étendre un traitement que l’exploitation ne sait pas diagnostiquer sans requête manuelle.
Approfondir Messenger et l’observabilité
Lorsque les unités deviennent indépendantes, l’usage de Symfony Messenger pour le run aide à décider si file, retries et transports ajoutent une garantie réelle.
Pour éprouver les arrêts et exceptions, la méthode de test des workflows métier complète le dispositif. L’observabilité d’un workflow montre ensuite comment relier chaque signal à une décision opérateur.
Conclusion : traiter la commande comme un produit
Une commande console fiable n’est ni un long contrôleur ni un cron chanceux. C’est une porte d’entrée vers un cas d’usage, entourée d’un contrat d’exécution qui nomme intention, unités, verdicts et limites.
La reprise se construit avant l’incident. Idempotence, checkpoint et état inconnu empêchent une relance de devenir un pari. Le verrou réduit les chevauchements, mais les invariants métier restent la dernière défense.
Le bon niveau d’industrialisation dépend de l’impact. Les petits outils restent simples ; les traitements critiques gagnent un registre, des alertes, un runbook éprouvé et une responsabilité claire. Cette proportion évite aussi bien le script opaque que la plateforme de batch surdimensionnée.
Si vos commandes Symfony portent des clôtures, imports ou synchronisations difficiles à exploiter, Dawap peut vous aider à les cadrer, tester leur reprise et les intégrer dans une application web métier maintenable.