Overview
Bridge adalah design pattern structural yang digunakan untuk memisahkan abstraksi dari implementasi agar keduanya bisa berkembang independen. Di backend nyata, pattern ini membantu tim menjaga modul tetap terpisah sambil tetap cepat mengirim fitur.
Problem It Solves
Tanpa Bridge, tim sering mencampur orkestrasi, integrasi eksternal, dan aturan domain dalam satu tempat. Dampaknya adalah coupling tinggi, testing sulit, dan refactor mahal.
5W + 1H
What
Bridge 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 Bridge 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
package ports
import "context"
type BridgeInput struct {
RequestID string
Payload map[string]any
}
type BridgeOutput struct {
Status string
Data map[string]any
}
type BridgePort interface {
Execute(ctx context.Context, in BridgeInput) (BridgeOutput, error)
}2) Adapter / implementasi
package adapters
import (
"context"
"fmt"
"myapp/internal/ports"
)
type BridgeAdapter struct {
providerName string
}
func NewBridgeAdapter(providerName string) *BridgeAdapter {
return &BridgeAdapter{providerName: providerName}
}
func (a *BridgeAdapter) Execute(ctx context.Context, in ports.BridgeInput) (ports.BridgeOutput, error) {
if in.RequestID == "" {
return ports.BridgeOutput{}, fmt.Errorf("request id is required")
}
return ports.BridgeOutput{
Status: "ok",
Data: map[string]any{
"pattern": "Bridge",
"provider": a.providerName,
},
}, nil
}3) Use-case / wiring di main
package main
import (
"context"
"fmt"
"log"
"myapp/internal/adapters"
"myapp/internal/ports"
)
func run(ctx context.Context, svc ports.BridgePort) error {
out, err := svc.Execute(ctx, ports.BridgeInput{
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.NewBridgeAdapter("default-provider")
if err := run(ctx, service); err != nil {
log.Fatal(err)
}
}Suggested Project Structure
cmd/
api/
main.go
internal/
domain/
bridge-pattern-go/
model.go
policy.go
application/
bridge-pattern-go/
usecase.go
ports/
bridge-pattern-go_port.go
adapters/
inbound/
http/
bridge-pattern-go_handler.go
outbound/
bridge-pattern-go_adapter.go
infrastructure/
config/
loader.go
persistence/
repository.goClean/Hexagonal Placement
- Domain: menyimpan aturan bisnis murni terkait penggunaan Bridge.
- Application (Use Case): mengorkestrasi alur request dan koordinasi domain + ports.
- Ports: mendefinisikan kontrak stabil (
BridgePort) yang dipakai core logic. - Adapters: mengimplementasikan perilaku port (HTTP, gRPC, vendor API, queue consumer).
- Infrastructure: setup yang spesifik framework dan provider eksternal.
Boundary Rules
- Dependensi mengarah ke dalam: adapters/infrastructure bergantung pada ports/domain, bukan sebaliknya.
- Domain harus framework-agnostic (tanpa import HTTP, driver DB, atau vendor SDK).
- Layer application sebaiknya mengetahui interface, bukan adapter konkret.
- Infrastructure boleh tahu detail teknis, tetapi tidak boleh menyimpan policy bisnis.
Real-World Use Case
Dalam implementasi produksi, Bridge efektif untuk memisahkan workflow notifikasi dari implementasi channel transport. Use case bisa tetap stabil walau provider atau implementasi transport diganti.
Benefits & Tradeoffs
Benefits
- Modularitas dan maintainability lebih baik.
- Testing lebih mudah lewat mocked ports.
- Refactor lebih aman karena boundary jelas.
Tradeoffs
- Menambah jumlah file dan lapisan abstraksi.
- Butuh adaptasi tim jika belum terbiasa dengan arsitektur berlapis.
Common Pitfalls
- Menaruh domain rules di layer adapter.
- Payload vendor bocor langsung ke model domain.
- Membuat abstraksi berlebihan tanpa kebutuhan perubahan nyata.
- Tidak ada contract test untuk perilaku port.
When NOT to use
- Service masih sangat kecil dan tidak diperkirakan berkembang.
- Tim butuh prototipe cepat dan overhead arsitektur menghambat delivery.
- Hanya ada satu integrasi stabil tanpa variasi.
Conclusion
Bridge akan paling efektif jika dipasangkan dengan boundary Clean/Hexagonal. Pattern memberi struktur, sementara arsitektur menjaga arah dependensi tetap sehat saat codebase bertumbuh.