← Back to Cecep Azhar
Cecep Azhar #event-driven#architecture#messaging#kafka#scalability

Event-Driven Architecture: Membangun Sistem yang Scalable dan Resilient

Cecep Saeful Azhar • 16 Oct 2026
Event-Driven Architecture: Membangun Sistem yang Scalable dan Resilient
Pelajari Event-Driven Architecture untuk membangun sistem yang scalable dan resilient. Kafka, message queues, dan event sourcing dari pengalaman production.

Kenapa Event-Driven?

Request-response architecture (REST API) sudah cukup untuk banyak use case, tapi ada saat di mana kita butuh arsitektur yang lebih loose-coupled dan scalable. Di sinilah Event-Driven Architecture (EDA) berperan. Dari beberapa proyek yang saya handle, EDA sangat efektif untuk sistem yang memiliki banyak service yang perlu berkomunikasi tapi tidak boleh tightly coupled.

Contoh nyata: di sebuah startup e-commerce di Bandung, kami migrasi dari synchronous API calls ke event-driven untuk order processing. Hasilnya? Latency turun 60% dan sistem jauh lebih resilient terhadap service failures.

Konsep Dasar Event-Driven

EDA berputar di sekitar konsep event - fakta yang sudah terjadi di sistem. Berikut implementasi dasarnya:

// Event definition
type Event struct {
    ID        string                 `json:"id"`
    Type      string                 `json:"type"`
    Payload   map[string]interface{} `json:"payload"`
    Timestamp time.Time              `json:"timestamp"`
    Source    string                 `json:"source"`
    Metadata  map[string]string      `json:"metadata"`
}

// Event Publisher
type EventPublisher interface {
    Publish(ctx context.Context, topic string, event Event) error
}

// Event Handler
type EventHandler interface {
    Handle(ctx context.Context, event Event) error
    EventType() string
}

// Contoh handler untuk order created
type OrderCreatedHandler struct {
    inventoryService InventoryService
    notificationService NotificationService
}

func (h *OrderCreatedHandler) EventType() string {
    return "order.created"
}

func (h *OrderCreatedHandler) Handle(ctx context.Context, event Event) error {
    var order OrderCreatedPayload
    json.Unmarshal(event.Payload["order"], &order)
    
    // Reserve inventory
    if err := h.inventoryService.ReserveStock(ctx, order.ProductID, order.Quantity); err != nil {
        return fmt.Errorf("failed to reserve stock: %w", err)
    }
    
    // Send confirmation email
    return h.notificationService.SendOrderConfirmation(ctx, order)
}

Kafka untuk High-Throughput Events

Untuk sistem yang membutuhkan high-throughput event processing, Kafka adalah pilihan utama. Berikut cara saya setup Kafka producer dan consumer di Go:

import "github.com/segmentio/kafka-go"

// Producer
type KafkaPublisher struct {
    writer *kafka.Writer
}

func NewKafkaPublisher(brokers []string, topic string) *KafkaPublisher {
    return &KafkaPublisher{
        writer: &kafka.Writer{
            Addr:     kafka.TCP(brokers...),
            Topic:    topic,
            Balancer: &kafka.LeastBytes{},
            BatchTimeout: 10 * time.Millisecond,
        },
    }
}

func (p *KafkaPublisher) Publish(ctx context.Context, event Event) error {
    data, err := json.Marshal(event)
    if err != nil {
        return err
    }
    
    return p.writer.WriteMessages(ctx, kafka.Message{
        Key:   []byte(event.Type),
        Value: data,
    })
}

// Consumer
func StartConsumer(brokers []string, topic, groupID string, handler EventHandler) {
    reader := kafka.NewReader(kafka.ReaderConfig{
        Brokers:  brokers,
        GroupID:  groupID,
        Topic:    topic,
        MinBytes: 1,
        MaxBytes: 10e6,
    })
    
    for {
        msg, err := reader.ReadMessage(context.Background())
        if err != nil {
            log.Error().Err(err).Msg("failed to read message")
            continue
        }
        
        var event Event
        json.Unmarshal(msg.Value, &event)
        
        if err := handler.Handle(context.Background(), event); err != nil {
            log.Error().Err(err).Msg("failed to handle event")
            // Implement retry logic here
        }
    }
}

Saga Pattern untuk Distributed Transactions

Di event-driven architecture, kita tidak bisa menggunakan traditional database transactions lintas service. Solusinya adalah Saga Pattern yang mengkoordinasi beberapa steps dengan compensation logic jika ada kegagalan:

type Saga struct {
    steps    []SagaStep
    compensations []CompensationFunc
}

type SagaStep struct {
    Name         string
    Execute      func(ctx context.Context) error
    Compensate   func(ctx context.Context) error
}

func (s *Saga) Run(ctx context.Context) error {
    for i, step := range s.steps {
        if err := step.Execute(ctx); err != nil {
            // Compensation: undo yang sudah dilakukan
            for j := i - 1; j >= 0; j-- {
                if compErr := s.steps[j].Compensate(ctx); compErr != nil {
                    log.Error().Err(compErr).Str("step", s.steps[j].Name).Msg("compensation failed")
                }
            }
            return fmt.Errorf("saga failed at step %s: %w", step.Name, err)
        }
    }
    return nil
}

Kesimpulan

Event-Driven Architecture memang lebih complex dari synchronous API, tapi untuk sistem yang membutuhkan scalability, resilience, dan loose coupling, ini adalah pilihan yang tepat. Yang penting adalah mulai dari kebutuhan bisnis yang nyata, bukan karena tren semata.

← More articles from Cecep Azhar Central Content Hub Home →