Lompat ke konten
dokumentasi.id
Commit ecba1e8
v2
Star2,8 rb

2

Arsitektur Modul

Modul repository ini bernama github.com/gojek/heimdall/v8 (go.mod:1). 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-23). Plugin menetapkan tiga callback siklus hidup request, yaitu OnRequestStart, OnRequestEnd, dan OnError (plugin.go:9-13). Retriable hanya menuntut NextInterval(retry int) time.Duration (retry.go:6-8), dan Backoff hanya menuntut Next(retry int) time.Duration (backoff.go:9-12).

KontrakMethod wajibImplementasi bawaan
ClientGet, Post, Put, Patch, Delete, Do, AddPluginhttpclient.Client (httpclient/client.go:32) dan hystrix.Client (hystrix/hystrix_client.go:51)
DoerDo(*http.Request)*http.Client yang dipasang NewClient secara default (httpclient/client.go:35-40), dapat diganti lewat WithHTTPClient (httpclient/options.go:37-44)
PluginOnRequestStart, OnRequestEnd, OnErrorplugins.requestLogger dari NewRequestLogger (plugins/request_logger.go:27-38)
RetriableNextInterval(int)retrier dari NewRetrier, RetriableFunc, dan noRetrier (retry.go:19-38)
BackoffNext(int)constantBackoff dan exponentialBackoff (backoff.go:14-35)

Kedua paket klien mengimpor paket akar dan internal, bukan sebaliknya (httpclient/client.go:3-13, hystrix/hystrix_client.go:3-16). 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.
Fig. 1: Peta paket dan kontrak heimdallPaket 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-37). Sebaliknya httpclient.Client hanya mengenal interface: client heimdall.Doer, plugins []heimdall.Plugin, retrier heimdall.Retriable, dan retryErrorBudget *internal.ErrorBudget (httpclient/client.go:16-32). Karena transport HTTP masuk sebagai Doer, penggantinya tidak perlu mengubah httpclient sama sekali: WithHTTPClient hanya menimpa field client (httpclient/options.go:37-44).

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-28), dan WithHTTPClient memanggil httpclient.WithHTTPClient(client) (hystrix/options.go:96-102).

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.
Fig. 2: Rantai pemanggilan dari hystrix ke httpclientSatu 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-207), 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-241). 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-190, httpclient/client.go:204-220).

⚠️ Daftar plugin hanya hidup di httpclient.Client. AddPlugin pada klien hystrix tidak menyimpan apa pun; ia langsung meneruskan ke hhc.client.AddPlugin(p) (hystrix/hystrix_client.go:243-246). Mendaftarkan plugin lewat klien hystrix maupun lewat httpclient di bawahnya akan menulis ke slice yang sama, dan callback hanya dipanggil dari loop httpclient.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-16).

BerkasSimbolDipakai untuk
error_budget.goErrorBudget, NewTokenErrorBudget, NewPercentErrorBudget, Success, Failuremenghentikan retry saat budget habis; disimpan sebagai field retryErrorBudget di kedua client (internal/error_budget.go:11-19)
request_helper.goSetRequestGetBody, CloneRequestmenyiapkan body agar request bisa diulang saat retry (internal/request_helper.go:11-45)
sleep.goSleepInterruptiblejeda antar percobaan yang tetap menghormati context.Context (internal/sleep.go:8-16)
ctx.goIsCtxDonememutus loop retry ketika context selesai (internal/ctx.go:5-11)
multi_error.goBuildMultiErrormenggabungkan error dari beberapa percobaan menjadi satu nilai (internal/multi_error.go:5-14)

ErrorBudget bersifat nil-safe: Success mengembalikan false bila penerimanya nil (internal/error_budget.go:77-84). 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.