Event-Driven Architecture
debt(d7/e7/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no' and the only listed tool is phpstan, which cannot reliably detect architectural misapplications like using events for synchronous flows, missing schema versioning, or overly fine-grained events. These misuses manifest as silent failures or broken consumers in production, requiring careful code review or runtime observation to surface.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix describes decoupling side effects by dispatching domain events after state changes, but common mistakes like missing event versioning, wrong granularity, or misused synchronous flows are architectural in nature. Correcting these touches producers, consumers, message broker configuration, and potentially multiple services — a cross-cutting refactor rather than a localised fix.
Closest to 'strong gravitational pull' (e7). Event-driven architecture applies to web, cli, and queue-worker contexts and sits at the architectural layer. Once adopted, every new feature must consider event contracts, schema versioning, ordering guarantees, and handler registration. The choice shapes how state changes propagate across the entire system, imposing a persistent and wide-reaching tax on every future maintainer.
Closest to 'serious trap' (t7). The canonical misconception conflates event-driven architecture with event sourcing — two distinct patterns that developers routinely treat as synonymous. Additionally, common mistakes show that developers misuse EDA for synchronous request/response flows, ignore ordering guarantees, and skip schema versioning — each a non-obvious failure mode that contradicts intuitions carried over from synchronous or CRUD-style architectures.
Also Known As
TL;DR
Explanation
In event-driven architecture (EDA), services emit events (OrderPlaced, UserRegistered) when state changes. Other services subscribe to events they care about and react asynchronously — no direct coupling between producer and consumer. PHP applications implement this with message brokers (RabbitMQ, Apache Kafka, Redis Streams) and event buses (Symfony EventDispatcher, Laravel Events). Benefits include decoupling, async processing, and an audit trail. Challenges include eventual consistency, debugging distributed flows, and message ordering guarantees.
Diagram
flowchart LR
subgraph Publishers
OS[Order Service]
PS[Payment Service]
US[User Service]
end
BUS[[Event Bus<br/>Message Broker]]
subgraph Subscribers
EMAIL[Email Service<br/>listens: OrderPlaced]
INV[Inventory<br/>listens: OrderPlaced]
ANALYTICS[Analytics<br/>listens: all events]
end
OS -->|OrderPlaced| BUS
PS -->|PaymentFailed| BUS
US -->|UserRegistered| BUS
BUS --> EMAIL & INV & ANALYTICS
style BUS fill:#6e40c9,color:#fff
style EMAIL fill:#238636,color:#fff
style INV fill:#238636,color:#fff
style ANALYTICS fill:#1f6feb,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Using events for synchronous request/response flows — events are for fire-and-forget notifications, not queries.
- Not versioning event schemas — consumers break silently when producers change event structure.
- Publishing too fine-grained events (FieldUpdated) instead of meaningful domain events (OrderShipped).
- Ignoring event ordering guarantees — most message brokers do not guarantee strict order across partitions.
Code Examples
// Tight synchronous coupling — every service call made directly:
class OrderService {
public function placeOrder(Order $o): void {
$this->inventoryService->reserve($o); // Coupled
$this->emailService->sendConfirmation($o); // Coupled
$this->analyticsService->track($o); // Coupled — slow, brittle
}
// Better: dispatch OrderPlaced event; services subscribe independently
}
// Publisher emits an event — knows nothing about subscribers
class OrderService {
public function __construct(private EventBus $bus) {}
public function place(Cart $cart): Order {
$order = Order::create($cart);
$this->bus->publish(new OrderPlaced($order->id, $order->total));
return $order;
}
}
// Subscribers react independently — decoupled from OrderService
class InventorySubscriber implements EventSubscriber {
public function on(OrderPlaced $event): void { /* deduct stock */ }
}
class EmailSubscriber implements EventSubscriber {
public function on(OrderPlaced $event): void { /* send receipt */ }
}