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 which one something should be: 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 calling back to the publisher is the other half — an event containing only an id forces every subscriber into a synchronous read and reintroduces the coupling.