Événements et services de domaine
Capturez les faits métier avec des événements et des services de domaine.
Événements et services de domaine est une leçon PHP Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage PHP Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours PHP Academy comprend 4 leçons au total.
Capturer les faits métier
Tout comportement ne trouve pas naturellement sa place dans une seule entité. Les événements de domaine capturent des faits métier importants qui se sont déjà produits, comme OrderPlaced, afin que d'autres parties du système puissent réagir. Les services de domaine contiennent la logique métier qui n'appartient naturellement ni à une entité ni à un objet-valeur. Cette leçon montre comment ces deux mécanismes rendent votre modèle expressif sans gonfler les agrégats.
Qu'est-ce qu'un événement de domaine
Un événement de domaine est un enregistrement immuable d'un fait survenu dans le domaine. Il est nommé au passé, selon le langage omniprésent : OrderPlaced, PaymentReceived, CustomerRelocated. Il contient les données décrivant le fait ainsi que le moment où celui-ci s'est produit. C'est un objet-valeur : il est créé une fois et n'est jamais modifié.
<?php
final class OrderPlaced {
public function __construct(
public readonly string $orderId,
public readonly int $totalCents,
public readonly DateTimeImmutable $occurredOn = new DateTimeImmutable()
) {}
}
$e = new OrderPlaced('o1', 1300);
echo $e->orderId, ' @ ', $e->occurredOn->format('H:i'), PHP_EOL;
Les agrégats enregistrent les événements
La racine de l'agrégat enregistre les événements dans le cadre de son comportement, puis les expose afin que la couche applicative puisse les distribuer après la validation de la transaction. L'agrégat lui-même ne les distribue pas : il se contente de les enregistrer. Le domaine reste ainsi indépendant des préoccupations d'infrastructure.
<?php
final class OrderPlaced {
public function __construct(public readonly string $orderId) {}
}
abstract class AggregateRoot {
private array $events = [];
protected function record(object $e): void { $this->events[] = $e; }
public function releaseEvents(): array {
$out = $this->events; $this->events = []; return $out;
}
}
final class Order extends AggregateRoot {
public function __construct(public readonly string $id) {
$this->record(new OrderPlaced($id));
}
}
$o = new Order('o1');
foreach ($o->releaseEvents() as $e) { echo get_class($e), PHP_EOL; }
Distribuer après validation
Le service applicatif assure la persistance de l'agrégat, valide la transaction, puis distribue les événements libérés. Les distribuer après la validation garantit qu'aucun gestionnaire ne réagira à un fait dont la transaction aurait été annulée. L'ordre présenté ci-dessous constitue le modèle sûr pour les effets de bord comme les courriels ou les appels externes.
<?php
final class PlaceOrderService {
public function __construct(
private object $repo,
private object $dispatcher
) {}
public function handle(object $order): void {
// 1. persist inside a transaction
$this->repo->save($order); // commit happens here
// 2. only AFTER commit, release and dispatch
foreach ($order->releaseEvents() as $event) {
$this->dispatcher->dispatch($event);
}
}
}
echo "order persisted, then events dispatched", PHP_EOL;
Les gestionnaires réagissent
Chaque événement peut avoir plusieurs gestionnaires, découplés de l'agrégat qui l'a déclenché. Un répartiteur PSR-14 achemine l'événement vers les écouteurs enregistrés. Ajouter une nouvelle réaction, comme l'analyse ou une notification, consiste à ajouter un écouteur sans jamais toucher à l'agrégat.
<?php
final class OrderPlaced { public function __construct(public readonly string $orderId) {} }
final class Dispatcher {
private array $listeners = [];
public function on(string $event, callable $l): void { $this->listeners[$event][] = $l; }
public function dispatch(object $event): void {
foreach ($this->listeners[$event::class] ?? [] as $l) { $l($event); }
}
}
$d = new Dispatcher();
$d->on(OrderPlaced::class, fn($e) => print("email for {$e->orderId}\n"));
$d->on(OrderPlaced::class, fn($e) => print("analytics for {$e->orderId}\n"));
$d->dispatch(new OrderPlaced('o1'));
Des événements entre les frontières
Les événements de domaine découplent les agrégats qui doivent rester cohérents sans partager une transaction. OrderPlaced dans le contexte des Ventes peut déclencher la réservation de stock dans un autre contexte au moyen d'un événement. En interne, ce sont des objets ; entre services, ils deviennent souvent des événements d'intégration transmis par un courtier de messages. Distinguez les événements de domaine internes des événements d'intégration externes : le contrat public ne doit pas être votre modèle interne.
Qu'est-ce qu'un service de domaine
Un service de domaine contient une logique métier qui :
- implique plusieurs agrégats ou entités, et ne peut donc appartenir à aucun d'eux ;
- correspond à un véritable concept métier, exprimé dans le langage omniprésent.
Il est sans état et agit sur des objets du domaine. Un service FundsTransfer qui débite un compte et crédite un autre en est l'exemple classique.
Un service de domaine dans le code
Le service coordonne deux agrégats, mais ne possède pas d'état propre. La logique n'appartient à aucun des deux comptes ; elle réside donc dans un service nommé d'après l'opération métier.
<?php
final class Account {
public function __construct(public readonly string $id, private int $cents) {}
public function withdraw(int $c): void {
if ($c > $this->cents) { throw new DomainException('insufficient funds'); }
$this->cents -= $c;
}
public function deposit(int $c): void { $this->cents += $c; }
public function balance(): int { return $this->cents; }
}
final class MoneyTransferService {
public function transfer(Account $from, Account $to, int $cents): void {
$from->withdraw($cents);
$to->deposit($cents);
}
}
$a = new Account('a', 1000); $b = new Account('b', 0);
(new MoneyTransferService())->transfer($a, $b, 400);
echo $a->balance(), '/', $b->balance(), PHP_EOL; // 600/400
Service de domaine ou service applicatif
Ne les confondez pas :
- Un service de domaine contient la logique métier et appartient à la couche du domaine ; il ne sait rien des transactions ni de HTTP.
- Un service applicatif orchestre un cas d'utilisation : il charge les agrégats depuis les dépôts, appelle la logique du domaine, valide la transaction et distribue les événements. C'est un coordinateur léger, sans règles métier propres.
Éviter le piège des services
Les services de domaine sont faciles à utiliser à l'excès. Si une logique peut résider dans une entité ou un objet-valeur, elle le devrait : c'est ainsi que vous évitez un modèle anémique. Ne recourez à un service de domaine que lorsque l'opération s'étend réellement à plusieurs agrégats ou n'a aucun emplacement naturel. Sinon, vous retirerez le comportement de vos entités et recréerez du code procédural.
Le service applicatif coordonne
Pour tout assembler : un service applicatif léger charge les agrégats depuis les référentiels, invoque un service de domaine pour la logique interagrégats, persiste le résultat et distribue les événements. Il ne contient que l’orchestration — aucune règle métier qui lui soit propre.
<?php
final class Account {
public function __construct(public readonly string $id, public int $cents) {}
}
final class MoneyTransferService { // domain service: the rule
public function transfer(Account $from, Account $to, int $c): void {
if ($c > $from->cents) { throw new DomainException('insufficient'); }
$from->cents -= $c; $to->cents += $c;
}
}
final class TransferApp { // application service: orchestration only
public function __construct(private MoneyTransferService $domain) {}
public function run(Account $a, Account $b, int $c): void {
$this->domain->transfer($a, $b, $c); // then repo->save + dispatch
}
}
$a = new Account('a', 1000); $b = new Account('b', 0);
(new TransferApp(new MoneyTransferService()))->run($a, $b, 250);
echo $a->cents, '/', $b->cents, PHP_EOL; // 750/250
Vérification rapide
Événements et services.
Récapitulatif
Vous avez capturé les faits métier et la logique orpheline. Les événements de domaine sont des enregistrements immuables rédigés au passé, que les agrégats enregistrent et que la couche applicative distribue après validation, ce qui découple les réactions et permet de franchir les frontières des agrégats. Les services de domaine contiennent une logique de domaine sans état qui s’étend sur plusieurs agrégats, contrairement aux services applicatifs légers qui se contentent d’orchestrer les cas d’utilisation. Utilisez les deux avec parcimonie afin que le comportement reste là où il doit être : dans les entités et les objets-valeurs.
Questions Fréquemment Posées
La leçon « Événements et services de domaine » est-elle gratuite ?
Oui — le texte complet de « Événements et services de domaine » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours PHP Academy, passe à CoddyKit PRO. Le cours PHP Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Événements et services de domaine » ?
Capturez les faits métier avec des événements et des services de domaine. Tu pratiques PHP Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer PHP Academy ?
Aucune expérience préalable n'est requise. PHP Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « Événements et services de domaine » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon PHP Academy ?
Oui. Chaque leçon PHP Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Éléments constitutifs du DDD : entités et objets-valeurs
- Agrégats, dépôts et fabriques
- Événements et services de domaine
- Contextes délimités et cartographie des contextes