PHP Academy · Lezione

Eventi e servizi di dominio

Rappresentate i fatti di business con eventi e servizi di dominio.

Lezione 3 di 413 passaggi

Eventi e servizi di dominio è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.

Rappresentare i fatti di business

Non ogni comportamento si adatta all’interno di una singola entità. Gli Eventi di dominio rappresentano fatti di business significativi già avvenuti, come OrderPlaced, permettendo ad altre parti del sistema di reagire. I Servizi di dominio contengono la logica di dominio che non appartiene naturalmente a una singola entità o a un singolo oggetto valore. Questa lezione mostra come entrambi mantengano il modello espressivo senza appesantire gli aggregati.

Che cos’è un evento di dominio

Un evento di dominio è un record immutabile di qualcosa che è accaduto nel dominio, denominato al passato usando il linguaggio ubiquo: OrderPlaced, PaymentReceived, CustomerRelocated. Contiene i dati che descrivono il fatto e il momento in cui è avvenuto. È un oggetto valore: viene creato una volta e non cambia mai.

<?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;

Gli aggregati registrano gli eventi

La radice dell’aggregato registra gli eventi come parte del proprio comportamento, poi li espone affinché il livello applicativo possa distribuirli dopo il commit della transazione. L’aggregato non distribuisce gli eventi: li registra soltanto. In questo modo il dominio resta libero da preoccupazioni infrastrutturali.

<?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; }

Distribuire dopo il commit

Il servizio applicativo persiste l’aggregato, esegue il commit e poi distribuisce gli eventi rilasciati. La distribuzione dopo il commit garantisce che i gestori non reagiscano mai a un fatto la cui transazione è stata annullata. L’ordine riportato di seguito è il modello sicuro per effetti collaterali come l’invio di email o le chiamate esterne.

<?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;

I gestori reagiscono

Ogni evento può avere più gestori, disaccoppiati dall’aggregato che lo ha generato. Un dispatcher PSR-14 instrada l’evento verso i listener registrati. Per aggiungere una nuova reazione, come l’analisi dei dati o una notifica, basta aggiungere un listener senza modificare l’aggregato.

<?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'));

Eventi oltre i confini

Gli eventi di dominio disaccoppiano gli aggregati che devono restare coerenti senza condividere una transazione. OrderPlaced nel contesto Sales può attivare la prenotazione dell’inventario in un altro contesto tramite un evento. All’interno del sistema sono oggetti; tra servizi diventano spesso eventi di integrazione trasmessi tramite un broker di messaggi. Mantenga distinti gli eventi di dominio interni e gli eventi di integrazione esterni: il contratto pubblico non dovrebbe coincidere con il modello interno.

Che cos’è un servizio di dominio

Un servizio di dominio contiene una logica di dominio che:

  • coinvolge più aggregati o entità, quindi non si adatta a nessuno di essi, e
  • rappresenta un autentico concetto di dominio, espresso nel linguaggio ubiquo.

È senza stato e opera sugli oggetti del dominio. Un servizio FundsTransfer che addebita un conto e accredita un altro è l’esempio classico.

Un servizio di dominio nel codice

Il servizio coordina due aggregati, ma non conserva alcuno stato proprio. La logica non appartiene a nessuno dei due conti, quindi risiede in un servizio denominato in base all’operazione di dominio.

<?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

Servizio di dominio e servizio applicativo

Non si confondano i due concetti:

  • Un servizio di dominio contiene la logica di business e risiede nel livello di dominio; non sa nulla di transazioni o HTTP.
  • Un servizio applicativo coordina un caso d’uso: carica gli aggregati dai repository, richiama la logica di dominio, esegue il commit della transazione e distribuisce gli eventi. È un coordinatore essenziale, privo di proprie regole di business.

Evitare la trappola dei servizi

I servizi di dominio sono facili da utilizzare eccessivamente. Se una logica può risiedere in un’entità o in un oggetto valore, dovrebbe farlo: è così che si evita un modello anemico. Si ricorra a un servizio di dominio solo quando l’operazione coinvolge davvero più aggregati o non ha una collocazione naturale. In caso contrario, si sottrae comportamento alle entità ricreando codice procedurale.

Coordinamento del servizio applicativo

Riassumendo: un servizio applicativo leggero carica gli aggregati dai repository, invoca un servizio di dominio per la logica che coinvolge più aggregati, persiste il risultato e inoltra gli eventi. Contiene solo l'orchestrazione: non ha regole di business proprie.

<?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

Verifica rapida

Eventi e servizi.

Riepilogo

Ha raccolto i fatti di business e la logica senza una collocazione chiara. Gli Eventi di dominio sono registrazioni immutabili al passato che gli aggregati registrano e che il livello applicativo invia dopo il commit, disaccoppiando le reazioni e attraversando i confini tra aggregati. I Servizi di dominio contengono logica di dominio senza stato che coinvolge più aggregati, distinta dai servizi applicativi leggeri che si limitano a orchestrare i casi d'uso. Usi entrambi con moderazione, così il comportamento rimane nelle entità e negli oggetti valore, dove deve stare.

Gratis per iniziare

Impara PHP con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
49
Lezioni
195

Domande Frequenti

La lezione «Eventi e servizi di dominio» è gratuita?

Sì — il testo completo di «Eventi e servizi di dominio» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.

Cosa imparerò in «Eventi e servizi di dominio»?

Rappresentate i fatti di business con eventi e servizi di dominio. Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare PHP Academy?

Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Eventi e servizi di dominio»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione PHP Academy?

Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Blocchi fondamentali del DDD: entità e oggetti valore
  2. Aggregati, repository e factory
  3. Eventi e servizi di dominio
  4. Bounded context e context mapping
← Torna a PHP Academy