Event-Driven Architecture
Event-Driven Architecture (EDA) is a software architecture paradigm where the flow of an application is determined by events. Instead of a traditional request-response model, components communicate by producing and consuming events. This promotes loose coupling, scalability, and responsiveness. Events represent a significant change in state, and components react to these events asynchronously.
EDA is particularly useful in distributed systems, microservices architectures, and applications requiring real-time processing. Common use cases include logging, monitoring, data pipelines, user interface updates, and integrating disparate systems. It allows for building highly scalable and resilient applications where components can fail independently without bringing down the entire system.
Usage
EDA is widely used in modern software development for:
- Microservices Communication: Services publish events when their state changes, allowing other services to react without direct dependencies.
- Real-time Data Processing: Applications like fraud detection or stock trading rely on immediate responses to events.
- Decoupled Systems: Integrating systems with different technologies and lifecycles without tight coupling.
- IoT Platforms: Handling streams of data from numerous devices.
- Serverless Computing: Functions are triggered by events, enabling pay-per-use scaling.
Examples
-
Kafka: Apache Kafka is a distributed streaming platform often used as an event bus in EDA. Producers write events to Kafka topics, and consumers subscribe to those topics to receive and process events. It’s used by Netflix for real-time monitoring and LinkedIn for activity tracking.
-
AWS EventBridge: A serverless event bus service that makes it easier to build event-driven applications at scale. It allows you to route events between AWS services, SaaS applications, and your own custom applications. Many AWS customers use EventBridge to connect their services and automate workflows.
-
Node.js
EventEmitter: A core module in Node.js that provides a simple event handling mechanism. Components can emit events, and other components can listen for and respond to those events. This is a foundational pattern for building asynchronous and reactive applications in Node.js.
Specimens
15 implementationsThe Event-Driven Architecture (EDA) decouples components by having them communicate through events. Components (event producers) emit events when something significant happens, and other components (event consumers) react to those events without needing direct knowledge of the producers. This promotes flexibility and scalability.
This Dart implementation uses StreamController to manage event streams. The EventBus class acts as a central hub for publishing and subscribing to events. Producers call publish() with an event object. Consumers subscribe to specific event types using stream.listen(). Dart’s asynchronous stream handling with StreamController and Stream is a natural fit for EDA, allowing for non-blocking event processing. The use of a dedicated EventBus class encapsulates the event management logic, keeping components clean and focused.
// event_bus.dart
import 'dart:async';
class EventBus {
final _controller = StreamController<dynamic>();
Stream<T> stream<T>() {
return _controller.stream.map((event) => event as T);
}
void publish<T>(T event) {
_controller.add(event);
}
void close() {
_controller.close();
}
}
// main.dart
import 'event_bus.dart';
class Counter {
int _count = 0;
void increment() {
_count++;
eventBus.publish(CounterIncrementedEvent(_count));
}
int get count => _count;
}
class CounterIncrementedEvent {
final int newCount;
CounterIncrementedEvent(this.newCount);
}
void main() {
final eventBus = EventBus();
final counter = Counter();
eventBus.stream<CounterIncrementedEvent>().listen((event) {
print('Counter incremented to: ${event.newCount}');
});
counter.increment();
counter.increment();
eventBus.close();
}
The Event-Driven Architecture (EDA) decouples system components by allowing them to communicate through events. Components (event producers) emit events without knowing who will handle them, and other components (event consumers) subscribe to events they’re interested in. This promotes scalability, flexibility, and resilience.
This Scala implementation uses a simple Event trait and a Publisher class to broadcast events. Subscribers register with the Publisher and receive events matching their interest. We leverage Scala’s functional programming capabilities with higher-order functions for event handling and immutable data structures for events. The use of traits and classes aligns with Scala’s OOP style, while the functional aspects keep the event handling concise and safe. This approach avoids tight coupling and allows for dynamic addition/removal of subscribers.
trait Event {
val timestamp: Long = System.currentTimeMillis()
}
case class MessageEvent(message: String) extends Event
case class UserCreatedEvent(userId: Int, username: String) extends Event
class Publisher {
private var subscribers: List[Event => Unit] = List()
def subscribe(subscriber: Event => Unit): Unit = {
subscribers = subscriber :: subscribers
}
def unsubscribe(subscriber: Event => Unit): Unit = {
subscribers = subscribers.filter(_ != subscriber)
}
def publish(event: Event): Unit = {
subscribers.foreach(_(event))
}
}
object EventDrivenExample {
def main(args: Array[String]): Unit = {
val publisher = new Publisher()
val messageHandler: Event => Unit = event => {
if (event.isInstanceOf[MessageEvent]) {
val msgEvent = event.asInstanceOf[MessageEvent]
println(s"Message Handler: Received message - ${msgEvent.message}")
}
}
val userHandler: Event => Unit = event => {
if (event.isInstanceOf[UserCreatedEvent]) {
val userEvent = event.asInstanceOf[UserCreatedEvent]
println(s"User Handler: User created - ${userEvent.username} (ID: ${userEvent.userId})")
}
}
publisher.subscribe(messageHandler)
publisher.subscribe(userHandler)
publisher.publish(MessageEvent("Hello, EDA!"))
publisher.publish(UserCreatedEvent(123, "Alice"))
publisher.publish(MessageEvent("Another message."))
publisher.unsubscribe(messageHandler)
publisher.publish(MessageEvent("This won't be printed."))
}
}
The Event-Driven Architecture (EDA) decouples software components by allowing them to react to events without knowing who created them. Components (event producers) emit events, and other components (event consumers) subscribe to specific events they’re interested in. This promotes flexibility and scalability.
This PHP example uses a simple event emitter class and a registry of event listeners. The EventEmitter class allows attaching listeners to events via on() and triggering events via emit(). Listeners are simple callable functions (closures or methods). This approach is idiomatic PHP as it leverages closures and the dynamic nature of the language for flexible event handling without requiring complex interfaces or inheritance. It’s a lightweight implementation suitable for smaller applications or as a building block for more robust event systems.
<?php
class EventEmitter
{
private $listeners = [];
public function on(string $event, callable $listener): void
{
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = $listener;
}
public function emit(string $event, ...$args): void
{
if (isset($this->listeners[$event])) {
foreach ($this->listeners[$event] as $listener) {
call_user_func($listener, ...$args);
}
}
}
}
// Example Usage
$emitter = new EventEmitter();
$emitter->on('user.created', function ($user) {
echo "User created: " . $user['name'] . "\n";
// Perform actions like sending a welcome email
});
$emitter->on('user.created', function ($user) {
echo "Logging user creation for: " . $user['name'] . "\n";
// Log the event to a database or file
});
$emitter->emit('user.created', ['name' => 'Alice', 'id' => 123]);
$emitter->emit('user.updated', ['name' => 'Bob', 'id' => 456]); // No listeners for this event
?>
The Event-Driven Architecture (EDA) decouples components by allowing them to communicate through events. Components (event producers) emit events without knowing who will handle them, and other components (event consumers) subscribe to events they’re interested in. This promotes scalability and flexibility.
This Ruby implementation uses a simple event emitter and listener system. The EventEmitter class holds a list of subscribers (callbacks) for each event type. emit triggers all subscribers for a given event. The example demonstrates emitting and handling a “user.created” event. Using blocks (procs) as event handlers is a very Ruby-esque approach, leveraging the language’s first-class function capabilities for concise and flexible event handling. The EventEmitter itself is a simple class, keeping the core logic focused and readable.
class EventEmitter
def initialize
@subscribers = {}
end
def subscribe(event_name, &callback)
@subscribers[event_name] ||= []
@subscribers[event_name] << callback
end
def emit(event_name, data = {})
@subscribers[event_name]&.each { |callback| callback.call(data) }
end
end
# Example Usage
emitter = EventEmitter.new
emitter.subscribe("user.created") do |user_data|
puts "User created: #{user_data[:username]}"
puts "Sending welcome email..."
end
emitter.subscribe("user.created") do |user_data|
puts "Updating user statistics..."
end
emitter.emit("user.created", { username: "john.doe", email: "john.doe@example.com" })
emitter.subscribe("order.placed") do |order_data|
puts "Order placed: #{order_data[:order_id]}"
end
emitter.emit("order.placed", { order_id: "12345", total: 100.00 })
The Event-Driven Architecture (EDA) decouples software components by allowing them to communicate through events. Components (event producers) emit events when something significant happens, and other components (event consumers) react to those events without needing to know who produced them. This promotes flexibility and scalability.
This Swift implementation uses a simple NotificationCenter as the event bus. Observable objects post notifications with specific names (event types) and payloads. Observer objects register for these notifications and execute closures when events occur. This approach is idiomatic Swift because NotificationCenter is a built-in, type-safe mechanism for event handling, aligning with Swift’s emphasis on safety and clarity. Using closures for event handling is also a standard Swift practice.
// Observable.swift
protocol Observable {
var notificationCenter: NotificationCenter { get }
func postEvent(name: String, object: Any?)
}
extension Observable {
var notificationCenter: NotificationCenter {
return .default
}
func postEvent(name: String, object: Any?) {
notificationCenter.post(name: NSNotification.Name(name), object: object)
}
}
// Observer.swift
protocol Observer {
func observeEvent(name: String, handler: @escaping (Notification) -> Void)
}
extension Observer {
func observeEvent(name: String, handler: @escaping (Notification) -> Void) {
notificationCenter.addObserver(self, selector: #selector(eventHandler(_:)), name: NSNotification.Name(name), object: nil)
// Swift's selector mechanism requires a method to be defined.
// We use a helper method to call the closure.
@objc private func eventHandler(_ notification: Notification) {
handler(notification)
}
}
}
// Example Usage
class DataProvider: Observable {
var data: String = "Initial Data"
func updateData(newData: String) {
data = newData
postEvent(name: "DataUpdated", object: data)
}
}
class DataConsumer: Observer {
func observeEvent(name: String, handler: @escaping (Notification) -> Void) {
super.observeEvent(name: name, handler: handler)
}
}
let dataProvider = DataProvider()
let dataConsumer = DataConsumer()
dataConsumer.observeEvent(name: "DataUpdated") { notification in
if let updatedData = notification.object as? String {
print("Data updated: \(updatedData)")
}
}
dataProvider.updateData(newData: "New Data from Source")
dataProvider.updateData(newData: "Even Newer Data")
The Event-Driven Architecture (EDA) decouples components by allowing them to communicate through events. Components (event producers) emit events when something significant happens, and other components (event consumers) react to those events without knowing who produced them. This promotes scalability and flexibility.
This Kotlin example uses a simple EventBus class to manage event publishing and subscription. Events are represented as data classes. Consumers register callbacks for specific event types. When an event is published, the EventBus iterates through the registered callbacks and invokes them. Kotlin’s data classes and functional programming features (higher-order functions for callbacks) make this implementation concise and readable, aligning with the language’s emphasis on clarity and immutability. The use of interfaces for events and listeners further enhances decoupling.
// Event Interface
interface Event
// Listener Interface
interface Listener<T : Event> {
fun onEvent(event: T)
}
// Concrete Events
data class UserCreatedEvent(val userId: String, val username: String) : Event
data class UserUpdatedEvent(val userId: String, val newUsername: String) : Event
// Event Bus
class EventBus {
private val listeners: MutableMap<Class<*>, MutableList<Listener<*>>> = mutableMapOf()
fun <T : Event> subscribe(eventClass: Class<T>, listener: Listener<T>) {
listeners.getOrPut(eventClass) { mutableListOf() } += listener
}
fun publish(event: Event) {
listeners[event::class]?.forEach {
@Suppress("UNCHECKED_CAST")
(it as Listener<Event>).onEvent(event)
}
}
}
// Example Usage
fun main() {
val eventBus = EventBus()
// User Service (Producer)
fun createUser(userId: String, username: String) {
println("Creating user: $username")
eventBus.publish(UserCreatedEvent(userId, username))
}
// Notification Service (Consumer)
fun setupUserCreatedNotification() {
eventBus.subscribe(UserCreatedEvent::class, object : Listener<UserCreatedEvent> {
override fun onEvent(event: UserCreatedEvent) {
println("Sending welcome email to: ${event.username}")
}
})
}
// Analytics Service (Consumer)
fun setupUserCreatedAnalytics() {
eventBus.subscribe(UserCreatedEvent::class, object : Listener<UserCreatedEvent> {
override fun onEvent(event: UserCreatedEvent) {
println("Logging user creation for analytics: ${event.username}")
}
})
}
setupUserCreatedNotification()
setupUserCreatedAnalytics()
createUser("123", "Alice")
createUser("456", "Bob")
}
The Event-Driven Architecture (EDA) decouples system components by having them communicate through events. Components (event producers) emit events without knowing who will handle them, and other components (event consumers) subscribe to events they’re interested in. This promotes scalability and flexibility.
This Rust example uses the crossbeam-channel crate for a simple, in-memory event bus. Event is a trait representing all events. EventHandler is another trait that defines how components react to events. The EventBus manages subscriptions and dispatches events to registered handlers. The code demonstrates an event producer (TemperatureSensor) sending temperature events and a consumer (HeaterController) reacting to them. Using traits for Event and EventHandler allows for easy extension with new event types and handlers. crossbeam-channel is a suitable choice for Rust’s concurrency model and efficient event passing.
use std::sync::Arc;
use crossbeam_channel::{unbounded, Receiver, Sender};
use std::thread;
// Define the Event trait
trait Event {
fn event_type(&self) -> &'static str;
}
// Define the EventHandler trait
trait EventHandler: Send + Sync {
fn handle_event(&self, event: &dyn Event);
}
// EventBus struct
struct EventBus {
sender: Sender<Box<dyn Event>>,
handlers: Arc<Vec<Box<dyn EventHandler>>>,
}
impl EventBus {
fn new() -> Self {
let (sender, receiver) = unbounded();
let handlers = Arc::new(Vec::new());
let bus = EventBus { sender, handlers };
let handlers_clone = handlers.clone();
thread::spawn(move || {
let mut receiver = receiver;
loop {
let event = receiver.recv().unwrap();
for handler in handlers_clone.iter() {
handler.handle_event(&*event);
}
}
});
bus
}
fn subscribe(&mut self, handler: Box<dyn EventHandler>) {
self.handlers.push(handler);
}
fn publish(&self, event: Box<dyn Event>) {
self.sender.send(event).unwrap();
}
}
// Example Event: TemperatureEvent
struct TemperatureEvent {
temperature: f32,
}
impl Event for TemperatureEvent {
fn event_type(&self) -> &'static str {
"TemperatureEvent"
}
}
// Example EventHandler: HeaterController
struct HeaterController;
impl EventHandler for HeaterController {
fn handle_event(&self, event: &dyn Event) {
if let Some(temp_event) = event.downcast_ref::<TemperatureEvent>() {
if temp_event.temperature < 20.0 {
println!("Temperature is low: {}. Turning on heater.", temp_event.temperature);
} else {
println!("Temperature is okay: {}. Heater is off.", temp_event.temperature);
}
}
}
}
// Example Event Producer: TemperatureSensor
struct TemperatureSensor {
bus: Arc<EventBus>,
}
impl TemperatureSensor {
fn new(bus: Arc<EventBus>) -> Self {
TemperatureSensor { bus }
}
fn simulate_temperature_reading(&self, temperature: f32) {
let event = Box::new(TemperatureEvent { temperature });
self.bus.publish(event);
}
}
fn main() {
let bus = Arc::new(EventBus::new());
let heater_controller = Box::new(HeaterController);
bus.subscribe(heater_controller);
let sensor = TemperatureSensor::new(bus.clone());
sensor.simulate_temperature_reading(15.0);
sensor.simulate_temperature_reading(22.0);
sensor.simulate_temperature_reading(18.0);
thread::sleep(std::time::Duration::from_secs(1)); // Allow events to be processed
}
The Event-Driven Architecture (EDA) decouples components by allowing them to communicate through events. Components (event producers) emit events without knowing who will handle them, and other components (event consumers) subscribe to events they’re interested in. This promotes scalability and flexibility.
This Go implementation uses channels to represent the event bus. Event is an interface for all events. Subscriber registers channels to receive specific event types. Producer publishes events to the bus. The main function demonstrates a simple producer and subscriber setup. Using interfaces and channels is idiomatic Go for concurrency and decoupling, making this a natural fit for EDA. Error handling is included for channel operations.
package main
import (
"fmt"
"time"
)
// Event is the interface for all events.
type Event interface {
Type() string
}
// UserCreatedEvent represents a user creation event.
type UserCreatedEvent struct {
UserID int
Username string
}
func (e *UserCreatedEvent) Type() string {
return "UserCreated"
}
// Subscriber manages event subscriptions.
type Subscriber struct {
eventChans map[string]chan Event
}
func NewSubscriber() *Subscriber {
return &Subscriber{
eventChans: make(map[string]chan Event),
}
}
func (s *Subscriber) Subscribe(eventType string) <-chan Event {
ch := make(chan Event, 10) // Buffered channel
s.eventChans[eventType] = ch
return ch
}
func (s *Subscriber) Unsubscribe(eventType string) {
delete(s.eventChans, eventType)
}
// Producer publishes events to subscribers.
type Producer struct {
subscriber *Subscriber
}
func NewProducer(subscriber *Subscriber) *Producer {
return &Producer{subscriber: subscriber}
}
func (p *Producer) Publish(event Event) {
eventType := event.Type()
if ch, ok := p.subscriber.eventChans[eventType]; ok {
select {
case ch <- event:
fmt.Println("Event published:", eventType)
default:
fmt.Println("Channel full, event dropped:", eventType)
}
} else {
fmt.Println("No subscribers for event:", eventType)
}
}
func main() {
subscriber := NewSubscriber()
userCreatedChan := subscriber.Subscribe("UserCreated")
producer := NewProducer(subscriber)
go func() {
for event := range userCreatedChan {
userCreatedEvent, ok := event.(*UserCreatedEvent)
if ok {
fmt.Printf("Received UserCreated event: ID=%d, Username=%s\n", userCreatedEvent.UserID, userCreatedEvent.Username)
}
}
}()
producer.Publish(&UserCreatedEvent{UserID: 1, Username: "Alice"})
producer.Publish(&UserCreatedEvent{UserID: 2, Username: "Bob"})
time.Sleep(1 * time.Second) // Allow events to be processed
subscriber.Unsubscribe("UserCreated")
producer.Publish(&UserCreatedEvent{UserID: 3, Username: "Charlie"}) // No subscribers
}
The Event-Driven Architecture (EDA) decouples software components by allowing them to communicate through events. Components (event producers) emit events without knowing who will handle them, and other components (event consumers) subscribe to specific events and react accordingly. This promotes flexibility and scalability.
This C implementation uses function pointers as event handlers. An Event struct holds event data, and a Dispatcher manages a list of handlers for each event type. Producers call dispatch_event, and the dispatcher iterates through registered handlers, calling them with the event data. This approach avoids direct dependencies between components. Using function pointers is a common and efficient way to achieve callback mechanisms in C, fitting its procedural nature. Structs are used for data organization, also idiomatic.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Event Data
typedef struct {
int event_type;
void *data;
} Event;
// Event Handler Type
typedef void (*EventHandler)(Event *event);
// Dispatcher Structure
typedef struct {
EventHandler handlers[10]; // Assuming a maximum of 10 handlers per type
int num_handlers[10];
} Dispatcher;
// Function to register an event handler
void register_handler(Dispatcher *dispatcher, int event_type, EventHandler handler) {
if (event_type >= 0 && event_type < 10) {
dispatcher->handlers[event_type] = handler;
dispatcher->num_handlers[event_type] = 1;
}
}
// Function to dispatch an event
void dispatch_event(Dispatcher *dispatcher, Event *event) {
if (event->event_type >= 0 && event->event_type < 10 && dispatcher->num_handlers[event->event_type] > 0) {
dispatcher->handlers[event->event_type](event);
}
}
// Example Event Handlers
void handle_sensor_data(Event *event) {
int *sensor_value = (int *)event->data;
printf("Sensor Data Received: %d\n", *sensor_value);
}
void handle_button_press(Event *event) {
int *button_id = (int *)event->data;
printf("Button Pressed: %d\n", *button_id);
}
int main() {
Dispatcher dispatcher;
for (int i = 0; i < 10; i++) {
dispatcher.num_handlers[i] = 0;
}
// Register Handlers
register_handler(&dispatcher, 0, handle_sensor_data); // Event type 0: Sensor Data
register_handler(&dispatcher, 1, handle_button_press); // Event type 1: Button Press
// Create and Dispatch Events
Event sensor_event;
sensor_event.event_type = 0;
int sensor_value = 42;
sensor_event.data = &sensor_value;
dispatch_event(&dispatcher, &sensor_event);
Event button_event;
button_event.event_type = 1;
int button_id = 5;
button_event.data = &button_id;
dispatch_event(&dispatcher, &button_event);
return 0;
}
The Event-Driven Architecture (EDA) decouples software components by allowing them to communicate through events. Components don’t directly call each other; instead, they publish events, and other components subscribe to those events to react accordingly. This promotes flexibility and scalability.
This C++ implementation uses a simple event system with a central EventManager class. Events are represented as function pointers. Components register their interest in specific events with the EventManager. When an event occurs, the EventManager iterates through its registered handlers and invokes them. This uses function pointers, a common C++ technique for callbacks, and avoids complex dependencies between components. The use of std::function allows for more flexible event handlers (lambdas, function objects, etc.).
#include <iostream>
#include <vector>
#include <functional>
#include <map>
class EventManager {
public:
using EventHandler = std::function<void()>;
void registerEvent(const std::string& eventName, EventHandler handler) {
eventHandlers[eventName].push_back(handler);
}
void unregisterEvent(const std::string& eventName, EventHandler handler) {
auto& handlers = eventHandlers[eventName];
handlers.erase(std::remove_if(handlers.begin(), handlers.end(),
[&](const EventHandler& h) { return h.target<void (*)()>() == handler.target<void (*)()>(); }),
handlers.end());
}
void publishEvent(const std::string& eventName) {
auto it = eventHandlers.find(eventName);
if (it != eventHandlers.end()) {
for (const auto& handler : it->second) {
handler();
}
}
}
private:
std::map<std::string, std::vector<EventHandler>> eventHandlers;
};
// Example Components
class Sensor {
public:
Sensor(EventManager& eventManager) : eventManager_(eventManager) {}
void detectMotion() {
std::cout << "Motion detected!" << std::endl;
eventManager_.publishEvent("motionDetected");
}
private:
EventManager& eventManager_;
};
class Logger {
public:
Logger(EventManager& eventManager) : eventManager_(eventManager) {
eventManager_.registerEvent("motionDetected", [this]() { logMotion(); });
}
~Logger() {
eventManager_.unregisterEvent("motionDetected", [this]() { logMotion(); });
}
void logMotion() {
std::cout << "Logging motion event..." << std::endl;
}
private:
EventManager& eventManager_;
};
int main() {
EventManager eventManager;
Sensor sensor(eventManager);
Logger logger(eventManager);
sensor.detectMotion();
return 0;
}
The Event-Driven Architecture (EDA) decouples system components by allowing them to communicate through events. Components (event producers) emit events when a state change occurs, and other components (event consumers) subscribe to these events and react accordingly, without needing direct knowledge of the producers. This promotes scalability, flexibility, and resilience.
The C# example uses the built-in event keyword and delegates to implement a simple EDA. OrderService publishes an OrderPlaced event when an order is created. EmailService and LoggingService subscribe to this event and perform their respective actions. This approach is idiomatic C# because it leverages the language’s type-safe event handling mechanism, promoting loose coupling and maintainability. The use of delegates provides a clean and concise way to define event handlers.
// Define the event arguments
public class OrderPlacedEventArgs : EventArgs
{
public int OrderId { get; }
public string CustomerName { get; }
public OrderPlacedEventArgs(int orderId, string customerName)
{
OrderId = orderId;
CustomerName = customerName;
}
}
// Event Producer
public class OrderService
{
// Define the event
public event EventHandler<OrderPlacedEventArgs> OrderPlaced;
public void CreateOrder(string customerName)
{
int orderId = new Random().Next(1000); // Simulate order ID generation
Console.WriteLine($"Order created with ID: {orderId} for {customerName}");
// Raise the event
OnOrderPlaced(new OrderPlacedEventArgs(orderId, customerName));
}
protected virtual void OnOrderPlaced(OrderPlacedEventArgs e)
{
// Make a local copy to avoid race conditions
EventHandler<OrderPlacedEventArgs> handler = OrderPlaced;
handler?.Invoke(this, e);
}
}
// Event Consumers
public class EmailService
{
public void HandleOrderPlaced(object sender, OrderPlacedEventArgs e)
{
Console.WriteLine($"EmailService: Sending confirmation email to {e.CustomerName} for order {e.OrderId}");
}
}
public class LoggingService
{
public void HandleOrderPlaced(object sender, OrderPlacedEventArgs e)
{
Console.WriteLine($"LoggingService: Logging order placement - Order ID: {e.OrderId}, Customer: {e.CustomerName}");
}
}
// Usage
public class Program
{
public static void Main(string[] args)
{
OrderService orderService = new OrderService();
EmailService emailService = new EmailService();
LoggingService loggingService = new LoggingService();
// Subscribe to the event
orderService.OrderPlaced += emailService.HandleOrderPlaced;
orderService.OrderPlaced += loggingService.HandleOrderPlaced;
// Create an order
orderService.CreateOrder("Alice Smith");
orderService.CreateOrder("Bob Johnson");
}
}
The Event-Driven Architecture (EDA) decouples components by allowing them to communicate through events. Components (event producers) emit events without knowing who will handle them, and other components (event consumers) subscribe to events they’re interested in. This promotes scalability and flexibility.
This TypeScript implementation uses a simple EventEmitter class to manage event subscriptions and emissions. EventProducer emits events with associated data. EventConsumer subscribes to specific events and handles them with a callback function. TypeScript’s type safety is leveraged by defining event types and callback signatures, improving code maintainability and reducing errors. This approach aligns with TypeScript’s focus on strong typing and object-oriented principles, offering a clean and scalable way to build event-driven systems.
// event-emitter.ts
class EventEmitter {
private events: { [event: string]: ((data: any) => void)[] } = {};
subscribe(event: string, callback: (data: any) => void): void {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event: string, data: any): void {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
}
// event-producer.ts
class EventProducer {
private emitter: EventEmitter;
constructor(emitter: EventEmitter) {
this.emitter = emitter;
}
produceEvent(type: string, data: any): void {
this.emitter.emit(type, data);
}
}
// event-consumer.ts
class EventConsumer {
private emitter: EventEmitter;
constructor(emitter: EventEmitter) {
this.emitter = emitter;
}
consumeEvent(type: string, callback: (data: any) => void): void {
this.emitter.subscribe(type, callback);
}
}
// main.ts
const emitter = new EventEmitter();
const producer = new EventProducer(emitter);
const consumer = new EventConsumer(emitter);
consumer.consumeEvent('userCreated', (userData: { id: number; name: string }) => {
console.log(`User created: ID=${userData.id}, Name=${userData.name}`);
});
producer.produceEvent('userCreated', { id: 1, name: 'Alice' });
producer.produceEvent('userCreated', { id: 2, name: 'Bob' });
The Event-Driven Architecture (EDA) decouples software components by allowing them to communicate through events. Components (event producers) emit events without knowing who will handle them, and other components (event consumers) subscribe to events they’re interested in. This promotes scalability and flexibility.
This JavaScript example uses a simple event emitter/listener pattern. The EventEmitter class manages event subscriptions and dispatches events. OrderService acts as an event producer, emitting ‘orderCreated’ and ‘orderShipped’ events. EmailService and InventoryService are event consumers, subscribing to specific events and reacting accordingly. This approach is idiomatic JavaScript due to its reliance on callbacks and the flexibility of object-oriented programming, avoiding tight coupling between services.
// event_emitter.js
class EventEmitter {
constructor() {
this.listeners = {};
}
on(event, listener) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(listener => {
listener(data);
});
}
}
}
// order_service.js
class OrderService {
constructor(emitter) {
this.emitter = emitter;
}
createOrder(order) {
// Simulate order creation logic
console.log(`Order created: ${order.id}`);
this.emitter.emit('orderCreated', order);
}
shipOrder(order) {
// Simulate order shipping logic
console.log(`Order shipped: ${order.id}`);
this.emitter.emit('orderShipped', order);
}
}
// email_service.js
class EmailService {
constructor(emitter) {
this.emitter = emitter;
this.emitter.on('orderCreated', this.handleOrderCreated.bind(this));
}
handleOrderCreated(order) {
console.log(`Sending order confirmation email to ${order.customer}`);
}
}
// inventory_service.js
class InventoryService {
constructor(emitter) {
this.emitter = emitter;
this.emitter.on('orderShipped', this.handleOrderShipped.bind(this));
}
handleOrderShipped(order) {
console.log(`Updating inventory for order: ${order.id}`);
}
}
// main.js
const emitter = new EventEmitter();
const orderService = new OrderService(emitter);
const emailService = new EmailService(emitter);
const inventoryService = new InventoryService(emitter);
orderService.createOrder({ id: '123', customer: 'john.doe' });
orderService.shipOrder({ id: '123', customer: 'john.doe' });
The Event-Driven Architecture (EDA) decouples components by having them communicate through events. Components (event producers) emit events when something significant happens, and other components (event consumers) listen for specific events and react accordingly. This promotes scalability and flexibility.
This Python example uses a simple dictionary to represent an event bus. Producers publish events to the bus with an event type, and consumers register for specific event types. When an event is published, the bus iterates through registered consumers and calls their handler functions. This implementation leverages Python’s first-class functions for event handling and dictionaries for efficient event dispatch, fitting Python’s dynamic and flexible nature.
class EventBus:
def __init__(self):
self.listeners = {}
def subscribe(self, event_type, callback):
if event_type not in self.listeners:
self.listeners[event_type] = []
self.listeners[event_type].append(callback)
def publish(self, event_type, data=None):
if event_type in self.listeners:
for callback in self.listeners[event_type]:
callback(data)
# Example Usage
def handle_user_created(user_data):
print(f"User created: {user_data}")
def handle_order_placed(order_data):
print(f"Order placed: {order_data}")
event_bus = EventBus()
# Subscribe consumers to events
event_bus.subscribe("user_created", handle_user_created)
event_bus.subscribe("order_placed", handle_order_placed)
# Producers publish events
event_bus.publish("user_created", {"user_id": 123, "username": "john.doe"})
event_bus.publish("order_placed", {"order_id": 456, "user_id": 123, "total": 100.00})
The Event-Driven Architecture (EDA) decouples components by having them communicate through events. Components (event producers) emit events when something significant happens, and other components (event consumers) listen for these events and react accordingly, without needing direct knowledge of the producers. This promotes scalability and flexibility.
This Java example uses a simple Observer pattern as a foundation for EDA. An Event interface defines the event data. Concrete events like OrderCreatedEvent are created by producers. EventDispatcher manages event registration and dispatch. Consumers register their interest via EventDispatcher and receive events through the Observer interface. This approach is idiomatic Java due to its reliance on interfaces for abstraction and the built-in Observer pattern for loosely coupled communication. Using a dedicated EventDispatcher centralizes event management, improving maintainability.
// Event Interface
interface Event {
String getType();
}
// Concrete Event
class OrderCreatedEvent implements Event {
private final String orderId;
public OrderCreatedEvent(String orderId) {
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
@Override
public String getType() {
return "order.created";
}
}
// Observer Interface
interface EventListener {
void onEvent(Event event);
}
// Event Dispatcher
class EventDispatcher {
private final List<EventListener> listeners = new ArrayList<>();
public void registerListener(EventListener listener) {
listeners.add(listener);
}
public void dispatchEvent(Event event) {
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}
// Event Consumer
class OrderService implements EventListener {
private final EventDispatcher dispatcher;
public OrderService(EventDispatcher dispatcher) {
this.dispatcher = dispatcher;
dispatcher.registerListener(this);
}
@Override
public void onEvent(Event event) {
if (event instanceof OrderCreatedEvent) {
OrderCreatedEvent orderCreatedEvent = (OrderCreatedEvent) event;
System.out.println("Order Service received OrderCreatedEvent: " + orderCreatedEvent.getOrderId());
// Process the order
}
}
}
// Event Producer
class OrderProcessor {
private final EventDispatcher dispatcher;
public OrderProcessor(EventDispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public void createOrder(String orderId) {
System.out.println("Creating order: " + orderId);
// Create order logic here
Event event = new OrderCreatedEvent(orderId);
dispatcher.dispatchEvent(event);
}
}
// Main Application
import java.util.ArrayList;
import java.util.List;
public class EventDrivenExample {
public static void main(String[] args) {
EventDispatcher dispatcher = new EventDispatcher();
OrderService orderService = new OrderService(dispatcher);
OrderProcessor orderProcessor = new OrderProcessor(dispatcher);
orderProcessor.createOrder("123");
orderProcessor.createOrder("456");
}
}