2
Arsitektur Modul
Modul repository ini bernama github.com/gojek/heimdall/v8 (go.mod:11). Di dalamnya ada lima paket: paket akar heimdall, httpclient, hystrix, plugins, dan internal. Paket akar hanya memuat kontrak dan strategi retry; semua kode yang benar-benar mengirim request berada di httpclient dan hystrix.
Kontrak di paket akar
Client dan Doer didefinisikan berdampingan. Doer cukup memiliki satu method, Do(*http.Request) (*http.Response, error), sedangkan Client menambahkan lima method verb HTTP dan AddPlugin (client.go:10-232). Plugin menetapkan tiga callback siklus hidup request, yaitu OnRequestStart, OnRequestEnd, dan OnError (plugin.go:9-133). Retriable hanya menuntut NextInterval(retry int) time.Duration (retry.go:6-84), dan Backoff hanya menuntut Next(retry int) time.Duration (backoff.go:9-125).
| Kontrak | Method wajib | Implementasi bawaan |
|---|---|---|
Client | Get, Post, Put, Patch, Delete, Do, AddPlugin | httpclient.Client (httpclient/client.go:326) dan hystrix.Client (hystrix/hystrix_client.go:517) |
Doer | Do(*http.Request) | *http.Client yang dipasang NewClient secara default (httpclient/client.go:35-408), dapat diganti lewat WithHTTPClient (httpclient/options.go:37-449) |
Plugin | OnRequestStart, OnRequestEnd, OnError | plugins.requestLogger dari NewRequestLogger (plugins/request_logger.go:27-3810) |
Retriable | NextInterval(int) | retrier dari NewRetrier, RetriableFunc, dan noRetrier (retry.go:19-3811) |
Backoff | Next(int) | constantBackoff dan exponentialBackoff (backoff.go:14-3512) |
Kedua paket klien mengimpor paket akar dan internal, bukan sebaliknya (httpclient/client.go:3-1313, hystrix/hystrix_client.go:3-1614). Karena arah import ini tetap satu arah, heimdall.Client bisa dipakai sebagai tipe parameter tanpa menyeret httpclient atau hystrix.
Peta ketergantungan paket
graph TD
app["caller"]
subgraph root["heimdall (paket akar)"]
Client["Client"]
Doer["Doer"]
Plugin["Plugin"]
Retriable["Retriable"]
Backoff["Backoff"]
end
HC["httpclient.Client"]
HX["hystrix.Client"]
PL["plugins.requestLogger"]
IN["internal"]
NATIVE["http.Client"]
app --> HC
app --> HX
HX -->|"client *httpclient.Client"| HC
HC -->|"implements"| Client
HX -->|"implements"| Client
HC -->|"client heimdall.Doer"| Doer
NATIVE -->|"satisfies"| Doer
HC -->|"plugins []heimdall.Plugin"| Plugin
PL -->|"implements"| Plugin
HC -->|"retrier heimdall.Retriable"| Retriable
Retriable -->|"NextInterval"| Backoff
HC --> IN
HX --> IN
style Client fill:#f59e0b,color:#000
style Doer fill:#f59e0b,color:#000
style Plugin fill:#f59e0b,color:#000
style Retriable fill:#f59e0b,color:#000
style Backoff fill:#f59e0b,color:#000
style HC fill:#2d7d46,color:#fff
style HX fill:#2d7d46,color:#fff
style PL fill:#8b5cf6,color:#fff
style IN fill:#8b5cf6,color:#fff
%% fig: Peta paket dan kontrak heimdall
%% desc: Paket akar menyediakan interface, dua paket klien mengimplementasikannya, dan paket internal dipakai bersama oleh keduanya.Arah panah menunjukkan siapa menyimpan siapa. hystrix.Client memegang pointer ke httpclient.Client yang konkret, ditambah parameter circuit breaker (hystrix/hystrix_client.go:22-3715). Sebaliknya httpclient.Client hanya mengenal interface: client heimdall.Doer, plugins []heimdall.Plugin, retrier heimdall.Retriable, dan retryErrorBudget *internal.ErrorBudget (httpclient/client.go:16-3216). Karena transport HTTP masuk sebagai Doer, penggantinya tidak perlu mengubah httpclient sama sekali: WithHTTPClient hanya menimpa field client (httpclient/options.go:37-449).
Dua lapis client
Pembungkusan itu punya satu konsekuensi praktis. Opsi hystrix yang mengatur HTTP harus menembus ke instance httpclient di dalamnya: WithHTTPTimeout memanggil httpclient.WithHTTPTimeout(timeout)(c.client) (hystrix/options.go:23-2817), dan WithHTTPClient memanggil httpclient.WithHTTPClient(client) (hystrix/options.go:96-10218).
graph LR
Caller["caller memanggil Do"] --> HXDo["hystrix.Client.Do: loop retry + retrier"]
HXDo --> HXD["hystrixDo"]
HXD --> DoC["hystrix.DoC dengan command name"]
DoC --> Fallback["fallbackFunc"]
DoC --> HCDo["httpclient.Client.Do: loop retry kedua"]
HCDo --> DoerDo["c.client.Do sebagai heimdall.Doer"]
HCDo --> Plugins["reportRequestStart, reportRequestEnd, reportError"]
HCDo --> Sleep["internal.SleepInterruptible dan internal.CloneRequest"]
style HXDo fill:#2d7d46,color:#fff
style HCDo fill:#2d7d46,color:#fff
style DoerDo fill:#2d7d46,color:#fff
style Plugins fill:#8b5cf6,color:#fff
style Sleep fill:#8b5cf6,color:#fff
style Fallback fill:#dc2626,color:#fff
%% fig: Rantai pemanggilan dari hystrix ke httpclient
%% desc: Satu request melewati loop retry hystrix, closure hystrix.DoC, lalu loop retry httpclient sebelum mencapai Doer dan plugin.hystrix.Client.Do menjalankan loop retry miliknya sendiri (hystrix/hystrix_client.go:180-20719), lalu setiap percobaan masuk ke hystrixDo yang memanggil hystrix.DoC dengan closure berisi hhc.client.Do(request) dan hhc.fallbackFunc (hystrix/hystrix_client.go:220-24120). Closure itu menyeberang ke httpclient.Client.Do, yang punya loop retry tersendiri, memakai internal.SleepInterruptible dan internal.CloneRequest, serta melaporkan setiap percobaan lewat reportRequestStart, reportRequestEnd, dan reportError (httpclient/client.go:144-19021, httpclient/client.go:204-22022).
⚠️ Daftar plugin hanya hidup di
httpclient.Client.AddPluginpada klien hystrix tidak menyimpan apa pun; ia langsung meneruskan kehhc.client.AddPlugin(p)(hystrix/hystrix_client.go:243-24623). Mendaftarkan plugin lewat klien hystrix maupun lewathttpclientdi bawahnya akan menulis ke slice yang sama, dan callback hanya dipanggil dari loophttpclient.Client.Do, bukan dari loop retry hystrix.
Paket internal
internal adalah tempat bersama untuk hal-hal yang tidak ingin diekspos sebagai API publik. Kedua paket klien mengimpor paket ini (hystrix/hystrix_client.go:3-1614).
| Berkas | Simbol | Dipakai untuk |
|---|---|---|
error_budget.go | ErrorBudget, NewTokenErrorBudget, NewPercentErrorBudget, Success, Failure | menghentikan retry saat budget habis; disimpan sebagai field retryErrorBudget di kedua client (internal/error_budget.go:11-1924) |
request_helper.go | SetRequestGetBody, CloneRequest | menyiapkan body agar request bisa diulang saat retry (internal/request_helper.go:11-4525) |
sleep.go | SleepInterruptible | jeda antar percobaan yang tetap menghormati context.Context (internal/sleep.go:8-1626) |
ctx.go | IsCtxDone | memutus loop retry ketika context selesai (internal/ctx.go:5-1127) |
multi_error.go | BuildMultiError | menggabungkan error dari beberapa percobaan menjadi satu nilai (internal/multi_error.go:5-1428) |
ErrorBudget bersifat nil-safe: Success mengembalikan false bila penerimanya nil (internal/error_budget.go:77-8429). Client yang tidak mengaktifkan opsi budget tetap bisa melewati pemeriksaan budget tanpa panic.
Rincian perilaku tiap bagian ada di halaman lain: mekanisme jeda di Retry dan Backoff, pemakaian budget di Error Budget untuk Retry, pemanggilan callback di Plugin dan Request Logger, dan pembungkusan circuit breaker di Klien Circuit Breaker.