Table of Contents
▼- Apa Itu Event Driven Architecture?
- Komponen Utama Event Driven Architecture
- Keuntungan Menggunakan Event Driven Architecture
- Implementasi Event Driven Architecture dengan RabbitMQ
- Best Practice Event Driven Architecture
- Event Driven vs Request Response
- Tantangan Event Driven Architecture
- Kapan Menggunakan Event Driven Architecture?
- Kesimpulan
Aplikasi modern semakin kompleks dengan berbagai komponen yang harus berkomunikasi secara efisien.
Event-driven architecture (EDA) menjadi solusi yang powerful untuk mengatasi tantangan ini.
Arsitektur ini memungkinkan komponen aplikasi berkomunikasi melalui event, bukan panggilan langsung yang synchronous.
Hasilnya? Sistem yang lebih loosely coupled, scalable, dan resilient terhadap failure.
Artikel ini akan membahas secara mendalam bagaimana membangun event-driven architecture yang solid untuk aplikasi modern Anda.
Apa Itu Event Driven Architecture?
Event-driven architecture adalah pola desain di mana komponen sistem berkomunikasi dengan mengirim dan menerima event.
Event adalah notifikasi bahwa sesuatu telah terjadi dalam sistem, seperti "user melakukan registrasi" atau "pembayaran berhasil diproses".
Berbeda dengan arsitektur tradisional yang synchronous, EDA memungkinkan komponen bekerja secara asynchronous dan independent.
Producer menghasilkan event tanpa perlu tahu siapa yang akan mengkonsumsinya.
Consumer memproses event sesuai kebutuhan mereka tanpa harus bergantung langsung pada producer.
Konsep ini menciptakan decoupling yang kuat antar komponen sistem.
Komponen Utama Event Driven Architecture
Event Producer
Producer adalah komponen yang menghasilkan event ketika terjadi perubahan state atau aksi tertentu.
Misalnya, service order management mengirim event "OrderCreated" ketika user membuat pesanan baru.
Producer tidak perlu tahu siapa yang akan memproses event tersebut.
Tanggung jawabnya hanya mengirim event ke message broker dengan format yang sudah disepakati.
Event Consumer
Consumer adalah komponen yang subscribe dan memproses event tertentu.
Satu event bisa diproses oleh multiple consumer dengan tujuan berbeda.
Event "OrderCreated" bisa diproses oleh inventory service untuk update stok, email service untuk kirim konfirmasi, dan analytics service untuk tracking.
Setiap consumer bekerja secara independent tanpa saling mempengaruhi.
Event Broker
Event broker adalah middleware yang menjembatani producer dan consumer.
Teknologi populer untuk event broker antara lain RabbitMQ, Apache Kafka, AWS SQS, atau Redis Streams.
Broker bertanggung jawab menerima event dari producer, menyimpannya temporarily, dan mendistribusikan ke consumer yang subscribe.
Broker juga menjamin delivery dan persistence event sesuai konfigurasi yang ditentukan.
Event Channel
Event channel atau topic adalah jalur komunikasi yang mengelompokkan event berdasarkan kategori.
Misalnya channel "orders" untuk semua event terkait order, "payments" untuk event pembayaran, dst.
Consumer subscribe ke channel yang relevan dengan kebutuhan mereka.
Struktur channel yang baik memudahkan routing dan filtering event.
Keuntungan Menggunakan Event Driven Architecture
Loose Coupling
Service tidak perlu tahu detail implementasi service lain.
Mereka hanya perlu tahu struktur event yang akan dikirim atau diterima.
Perubahan di satu service tidak langsung berdampak ke service lain selama kontrak event tetap sama.
Ini memudahkan development dan deployment independent per service.
Scalability
Setiap consumer bisa di-scale secara independent sesuai beban masing-masing.
Jika email service mengalami bottleneck, Anda bisa menambah instance khusus untuk service tersebut tanpa menyentuh komponen lain.
Event broker modern seperti Kafka mendukung horizontal scaling dengan partitioning.
Sistem bisa handle jutaan event per detik dengan arsitektur yang tepat.
Resilience
Jika satu consumer down, producer dan consumer lain tetap berjalan normal.
Event disimpan di broker sampai consumer kembali online dan siap memproses.
Ini membuat sistem lebih fault-tolerant dibanding arsitektur synchronous yang langsung fail ketika ada komponen down.
Retry mechanism dan dead letter queue bisa ditambahkan untuk handle failure scenario.
Audit Trail dan Event Sourcing
Semua event yang terjadi tercatat dan bisa digunakan untuk audit trail.
Anda bisa merekonstruksi state aplikasi dengan replay event dari awal.
Pattern event sourcing memanfaatkan ini untuk menyimpan state sebagai sequence of events, bukan hanya state terakhir.
Ini sangat berguna untuk debugging, compliance, dan analytics.
Implementasi Event Driven Architecture dengan RabbitMQ
Mari kita lihat implementasi sederhana menggunakan RabbitMQ sebagai message broker.
Contoh ini menggunakan PHP dengan library php-amqplib untuk berkomunikasi dengan RabbitMQ.
Setup RabbitMQ Connection
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class EventBroker
{
private $connection;
private $channel;
public function __construct(
string $host = 'localhost',
int $port = 5672,
string $user = 'guest',
string $password = 'guest'
) {
$this->connection = new AMQPStreamConnection(
$host,
$port,
$user,
$password
);
$this->channel = $this->connection->channel();
}
public function declareExchange(
string $exchangeName,
string $exchangeType = 'topic'
): void {
$this->channel->exchange_declare(
$exchangeName,
$exchangeType,
false, // passive
true, // durable
false // auto_delete
);
}
public function close(): void
{
$this->channel->close();
$this->connection->close();
}
public function getChannel()
{
return $this->channel;
}
}
Event Producer Implementation
<?php
class OrderEventProducer
{
private $broker;
private $exchangeName = 'orders_exchange';
public function __construct(EventBroker $broker)
{
$this->broker = $broker;
$this->broker->declareExchange($this->exchangeName);
}
public function publishOrderCreated(array $orderData): void
{
$event = [
'event_type' => 'order.created',
'event_id' => uniqid('event_', true),
'timestamp' => time(),
'data' => $orderData
];
$message = new AMQPMessage(
json_encode($event),
[
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
'content_type' => 'application/json'
]
);
$channel = $this->broker->getChannel();
$channel->basic_publish(
$message,
$this->exchangeName,
'order.created' // routing key
);
echo "Event published: order.created\n";
}
public function publishOrderPaid(int $orderId, array $paymentData): void
{
$event = [
'event_type' => 'order.paid',
'event_id' => uniqid('event_', true),
'timestamp' => time(),
'data' => [
'order_id' => $orderId,
'payment' => $paymentData
]
];
$message = new AMQPMessage(
json_encode($event),
[
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
'content_type' => 'application/json'
]
);
$channel = $this->broker->getChannel();
$channel->basic_publish(
$message,
$this->exchangeName,
'order.paid'
);
echo "Event published: order.paid\n";
}
}
Event Consumer Implementation
<?php
class InventoryEventConsumer
{
private $broker;
private $exchangeName = 'orders_exchange';
private $queueName = 'inventory_queue';
public function __construct(EventBroker $broker)
{
$this->broker = $broker;
$this->setupQueue();
}
private function setupQueue(): void
{
$channel = $this->broker->getChannel();
// Declare exchange
$this->broker->declareExchange($this->exchangeName);
// Declare queue
$channel->queue_declare(
$this->queueName,
false, // passive
true, // durable
false, // exclusive
false // auto_delete
);
// Bind queue to exchange with routing patterns
$channel->queue_bind(
$this->queueName,
$this->exchangeName,
'order.created'
);
$channel->queue_bind(
$this->queueName,
$this->exchangeName,
'order.cancelled'
);
}
public function consume(): void
{
$channel = $this->broker->getChannel();
$callback = function ($msg) {
$event = json_decode($msg->body, true);
echo "Processing event: {$event['event_type']}\n";
try {
switch ($event['event_type']) {
case 'order.created':
$this->handleOrderCreated($event['data']);
break;
case 'order.cancelled':
$this->handleOrderCancelled($event['data']);
break;
}
// Acknowledge message after successful processing
$msg->ack();
} catch (Exception $e) {
echo "Error processing event: {$e->getMessage()}\n";
// Reject and requeue the message
$msg->nack(true);
}
};
$channel->basic_qos(
null, // prefetch_size
1, // prefetch_count
null // global
);
$channel->basic_consume(
$this->queueName,
'', // consumer_tag
false, // no_local
false, // no_ack
false, // exclusive
false, // nowait
$callback
);
echo "Waiting for events...\n";
while ($channel->is_consuming()) {
$channel->wait();
}
}
private function handleOrderCreated(array $orderData): void
{
// Logic untuk update inventory
echo "Updating inventory for order: {$orderData['order_id']}\n";
// Simulate inventory update
foreach ($orderData['items'] as $item) {
echo "- Reducing stock for product {$item['product_id']}: {$item['quantity']} units\n";
}
}
private function handleOrderCancelled(array $orderData): void
{
echo "Restoring inventory for cancelled order: {$orderData['order_id']}\n";
}
}
Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.
Best Practice Event Driven Architecture
Definisikan Event Schema yang Jelas
Setiap event harus memiliki struktur yang konsisten dan well-documented.
Gunakan versioning pada event schema untuk backward compatibility.
Tools seperti JSON Schema atau Protocol Buffers bisa membantu validasi event structure.
Pastikan semua team mengikuti naming convention dan format yang sama.
Gunakan Idempotent Consumer
Consumer harus idempotent, artinya memproses event yang sama berkali-kali menghasilkan result yang sama.
Ini penting karena message broker tidak selalu guarantee exactly-once delivery.
Simpan event ID yang sudah diproses untuk mendeteksi duplicate.
Atau design logic consumer yang naturally idempotent seperti SET operation, bukan INCREMENT.
Implement Dead Letter Queue
Event yang gagal diproses setelah beberapa retry harus dipindah ke dead letter queue.
Ini mencegah poison message yang terus-menerus di-retry dan block processing event lain.
Tim bisa melakukan manual investigation dan fix pada event di DLQ.
Setelah fix, event bisa di-republish ke queue utama.
Monitoring dan Observability
Implement logging yang comprehensive untuk tracking event flow.
Gunakan correlation ID untuk trace single transaction across multiple services.
Monitor metrics seperti event processing time, queue depth, dan error rate.
Setup alerting untuk anomali seperti queue backlog yang terus bertambah atau sudden spike di error rate.
Handle Ordering dan Concurrency
Untuk event yang order-sensitive, gunakan partition key yang sama.
Di Kafka misalnya, event dengan partition key sama akan masuk ke partition yang sama dan diproses secara sequential.
Untuk RabbitMQ, bisa gunakan single consumer per queue atau implement distributed locking.
Pastikan logic tidak assume global ordering kecuali memang di-guarantee oleh broker.
Event Driven vs Request Response
Event-driven bukan pengganti total untuk request-response pattern.
Gunakan event-driven untuk operasi yang asynchronous dan tidak perlu immediate response.
Misalnya mengirim email konfirmasi, update analytics, atau sync data ke sistem eksternal.
Request-response lebih cocok untuk operasi yang butuh immediate feedback atau transactional consistency.
Misalnya validasi login, payment processing yang butuh konfirmasi langsung, atau query data yang butuh response real-time.
Kombinasikan kedua pattern sesuai kebutuhan use case masing-masing.
Tantangan Event Driven Architecture
Debugging yang Lebih Kompleks
Flow asynchronous lebih sulit di-trace dibanding synchronous call.
Gunakan distributed tracing tools seperti Jaeger atau Zipkin.
Implement structured logging dengan correlation ID di setiap event.
Visualisasi event flow membantu tim memahami interaksi antar service.
Eventual Consistency
Data tidak immediately consistent across all services.
Ada delay antara event published dan event processed.
Application logic harus handle scenario di mana data belum fully synchronized.
User interface perlu design yang mengakomodasi eventual consistency, misalnya dengan loading state atau optimistic updates.
Event Schema Evolution
Perubahan struktur event bisa break consumer yang sudah ada.
Implementasikan versioning strategy yang clear.
Support backward compatibility dengan optional fields atau deprecated fields yang masih dipertahankan sementara.
Communication yang baik antar tim penting untuk koordinasi schema changes.
Kapan Menggunakan Event Driven Architecture?
EDA cocok untuk sistem dengan karakteristik berikut:
- Aplikasi dengan multiple services yang perlu berkomunikasi
- Operasi yang naturally asynchronous seperti notification, reporting, atau data sync
- Sistem yang butuh high scalability dan resilience
- Use case yang memerlukan audit trail atau event sourcing
- Integration dengan external systems yang memiliki different SLA
Hindari EDA untuk:
- Aplikasi monolith sederhana yang tidak butuh inter-service communication
- Operasi yang strictly transactional dan butuh immediate consistency
- Team yang belum familiar dengan asynchronous programming dan distributed systems
- Use case dengan simple linear flow yang tidak butuh parallel processing
Kesimpulan
Event-driven architecture adalah paradigm yang powerful untuk membangun sistem modern yang scalable dan resilient.
Dengan memahami komponen utama, best practice, dan trade-off yang ada, Anda bisa mengimplementasikan EDA dengan efektif.
Mulailah dengan use case yang sederhana, pelajari karakteristik message broker yang dipilih, dan iterasi secara bertahap.
Investasi waktu untuk setup monitoring dan observability akan sangat membantu dalam jangka panjang.
Event-driven architecture bukan silver bullet, tapi ketika digunakan di konteks yang tepat, dia bisa significantly improve architecture quality aplikasi Anda.
Selamat membangun sistem event-driven yang robust dan scalable!