Un bus de commandes plutôt qu'un contrôleur obèse
Un contrôleur qui interroge, vérifie les règles métier, et persiste fait le travail du cas d'usage. La couche framework devrait s'arrêter à « transformer une requête en commande » et « transformer un résultat en réponse ».
Mauvais - le cas d'usage est enterré dans le contrôleur
final class InvoiceController
{
public function markAsPaid(string $id, InvoiceRepository $invoices): Response
{
$invoice = $invoices->find($id);
if ($invoice === null) {
throw new NotFoundHttpException();
}
if ($invoice->status() === InvoiceStatus::CANCELLED) {
throw new ConflictHttpException('Cannot pay a cancelled invoice');
}
$invoice->markAsPaid(new DateTimeImmutable());
$invoices->save($invoice);
return new JsonResponse(['status' => 'paid']);
}
}
L'invariant - une facture annulée ne peut jamais devenir payée - vit à côté des classes d'exception HTTP. Réutilisez-le depuis une commande console ou un consommateur de file d'attente et vous retapez la règle, sans garantie qu'elle reste synchronisée.
Bon - le contrôleur ne fait que traduire
final class InvoiceController
{
public function __construct(private readonly CommandBus $commands) {}
public function markAsPaid(string $id): Response
{
$this->commands->dispatch(new MarkInvoiceAsPaid($id));
return new JsonResponse(['status' => 'paid']);
}
}
final class MarkInvoiceAsPaidHandler
{
public function __construct(private readonly InvoiceRepository $invoices) {}
public function __invoke(MarkInvoiceAsPaid $command): void
{
$invoice = $this->invoices->getById($command->invoiceId);
$invoice->markAsPaid(new DateTimeImmutable());
$this->invoices->save($invoice);
}
}
Invoice::markAsPaid() est l'endroit où vit réellement la vérification de
facture annulée - dans l'agrégat, pas dans la couche transport. Le handler
est appelable depuis un contrôleur HTTP, une commande CLI, ou un
consommateur de message, avec des garanties identiques.
Le compromis : une classe de commande et un handler pour ce qui tenait auparavant en six lignes dans une méthode de contrôleur. Ça vaut le coup dès qu'un deuxième point d'entrée doit déclencher le même cas d'usage, ou dès que la règle est assez importante pour que « ne pas oublier de la revérifier » ne suffise plus.