Mediator di Go: Pola Behavioral dengan Implementasi Clean/Hexagonal

18/1/2026

Design Pattern
Golang
Behavioral
Clean Architecture
Hexagonal
Thumbnail Mediator di Go: Pola Behavioral dengan Implementasi Clean/Hexagonal

Overview

Mediator adalah design pattern behavioral yang digunakan untuk memusatkan komunikasi antar banyak komponen. Di backend nyata, pattern ini membantu tim menjaga modul tetap terpisah sambil tetap cepat mengirim fitur.

Problem It Solves

Tanpa Mediator, tim sering mencampur orkestrasi, integrasi eksternal, dan aturan domain dalam satu tempat. Dampaknya adalah coupling tinggi, testing sulit, dan refactor mahal.

5W + 1H

What

Mediator adalah pola untuk menyusun kolaborasi antarkomponen agar masalah desain yang berulang bisa ditangani konsisten.

Why

Untuk meningkatkan maintainability, mengisolasi perubahan, dan menjaga batas antara policy bisnis dan detail implementasi.

Who

Backend engineer, platform engineer, dan tim API yang bekerja dengan integrasi serta business logic yang terus berkembang.

When

Gunakan Mediator saat kompleksitas service meningkat dan satu tanggung jawab mulai bocor ke modul lain.

Where

Terapkan pada boundary service seperti use case aplikasi, adapter integrasi, orkestrasi workflow, dan domain policy.

How

Definisikan port yang stabil terlebih dahulu, lalu implementasikan adapter dan lakukan wiring lewat use-case.

Go Implementation (Step-by-step)

1) Definisi port / interface

go
package ports

import "context"

type MediatorInput struct {
	RequestID string
	Payload   map[string]any
}

type MediatorOutput struct {
	Status string
	Data   map[string]any
}

type MediatorPort interface {
	Execute(ctx context.Context, in MediatorInput) (MediatorOutput, error)
}

2) Adapter / implementasi

go
package adapters

import (
	"context"
	"fmt"
	"myapp/internal/ports"
)

type MediatorAdapter struct {
	providerName string
}

func NewMediatorAdapter(providerName string) *MediatorAdapter {
	return &MediatorAdapter{providerName: providerName}
}

func (a *MediatorAdapter) Execute(ctx context.Context, in ports.MediatorInput) (ports.MediatorOutput, error) {
	if in.RequestID == "" {
		return ports.MediatorOutput{}, fmt.Errorf("request id is required")
	}

	return ports.MediatorOutput{
		Status: "ok",
		Data: map[string]any{
			"pattern": "Mediator",
			"provider": a.providerName,
		},
	}, nil
}

3) Use-case / wiring di main

go
package main

import (
	"context"
	"fmt"
	"log"
	"myapp/internal/adapters"
	"myapp/internal/ports"
)

func run(ctx context.Context, svc ports.MediatorPort) error {
	out, err := svc.Execute(ctx, ports.MediatorInput{
		RequestID: "REQ-1001",
		Payload: map[string]any{"source": "api"},
	})
	if err != nil {
		return err
	}

	fmt.Println("status:", out.Status, "pattern:", out.Data["pattern"])
	return nil
}

func main() {
	ctx := context.Background()
	service := adapters.NewMediatorAdapter("default-provider")
	if err := run(ctx, service); err != nil {
		log.Fatal(err)
	}
}

Suggested Project Structure

text
cmd/
  api/
    main.go
internal/
  domain/
    mediator-pattern-go/
      model.go
      policy.go
  application/
    mediator-pattern-go/
      usecase.go
  ports/
    mediator-pattern-go_port.go
  adapters/
    inbound/
      http/
        mediator-pattern-go_handler.go
    outbound/
      mediator-pattern-go_adapter.go
  infrastructure/
    config/
      loader.go
    persistence/
      repository.go

Clean/Hexagonal Placement

Boundary Rules

  1. Dependensi mengarah ke dalam: adapters/infrastructure bergantung pada ports/domain, bukan sebaliknya.
  2. Domain harus framework-agnostic (tanpa import HTTP, driver DB, atau vendor SDK).
  3. Layer application sebaiknya mengetahui interface, bukan adapter konkret.
  4. Infrastructure boleh tahu detail teknis, tetapi tidak boleh menyimpan policy bisnis.

Real-World Use Case

Dalam implementasi produksi, Mediator efektif untuk mengorkestrasi workflow checkout antara cart, payment, dan shipping. Use case bisa tetap stabil walau provider atau implementasi transport diganti.

Benefits & Tradeoffs

Benefits

Tradeoffs

Common Pitfalls

  1. Menaruh domain rules di layer adapter.
  2. Payload vendor bocor langsung ke model domain.
  3. Membuat abstraksi berlebihan tanpa kebutuhan perubahan nyata.
  4. Tidak ada contract test untuk perilaku port.

When NOT to use

Conclusion

Mediator akan paling efektif jika dipasangkan dengan boundary Clean/Hexagonal. Pattern memberi struktur, sementara arsitektur menjaga arah dependensi tetap sehat saat codebase bertumbuh.