The naming decides the coupling. ReserveInventory names a recipient and expects an outcome; OrderPlaced names neither, so a second consumer can be added without touching the publisher.
// command — one handler, the sender cares whether it worked
final class ReserveInventory
{
public function __construct(int $orderId, array $lines) { /* ... */ }
}
// event — any number of subscribers, the publisher has moved on
final class OrderPlaced
{
public function __construct(
int $orderId, array $lines, DateTimeImmutable $at
) { /* ... */ }
}
Past tense for events is a convention worth enforcing in review, because it makes the mistake visible in the class name. The test for whether something should be an event: if the publisher has to know what happens next, it is a command wearing an event’s clothes. Events carrying enough data to be handled without a callback to the publisher is the other half — an event containing only an id forces every subscriber into a synchronous read.