Jika Anda mengelola aplikasi Go Fiber, salah satu kebutuhan CI yang paling sering muncul adalah: bagaimana menjalankan lint, test, dan build pada beberapa versi Go tanpa membuat pipeline terlalu lambat. Jawaban praktisnya adalah memakai GitHub Actions matrix strategy untuk validasi lintas versi, lalu menggabungkannya dengan cache dependency/module dan build cache Go secara aman.

Artikel ini fokus pada validasi kualitas kode untuk proyek Fiber, bukan deployment. Tujuannya adalah mempercepat feedback tim: pull request cepat terverifikasi, kompatibilitas versi Go lebih terjaga, dan waktu CI tetap masuk akal walau job bertambah.

Mengapa CI matrix penting untuk proyek Go Fiber

Go Fiber adalah framework HTTP untuk Go. Walaupun Fiber sendiri mungkin berjalan stabil, kode aplikasi Anda tetap bergantung pada toolchain Go, dependency module, dan perilaku test lintas versi. Karena itu, pipeline yang hanya diuji pada satu versi Go berisiko melewatkan masalah seperti:

  • kode yang lolos di versi Go terbaru tetapi gagal di versi yang masih Anda dukung,
  • dependency yang berubah perilaku antar versi toolchain,
  • test yang sensitif terhadap perubahan runtime atau compiler,
  • performa CI yang buruk karena seluruh validasi dijalankan serial tanpa cache.

Matrix strategy cocok dipakai ketika Anda memang perlu menjawab pertanyaan: apakah aplikasi Fiber ini tetap build dan test pada beberapa versi Go yang didukung? Namun matrix tidak selalu harus dipasang ke semua job. Di praktiknya, lint sering cukup dijalankan pada satu versi Go yang menjadi baseline tim, sedangkan test dan build dijalankan pada beberapa versi.

Struktur workflow yang disarankan

Untuk menjaga workflow tetap mudah dipelihara, pecah tanggung jawab job secara jelas:

  • lint: pemeriksaan statis, biasanya cukup satu versi Go,
  • test: unit test atau integration test ringan, cocok memakai matrix beberapa versi Go,
  • build: memastikan aplikasi Fiber dapat dikompilasi, bisa memakai matrix yang sama atau subset versi.

Pemisahan ini penting karena kebutuhan cache, waktu eksekusi, dan tingkat kegagalan tiap tahap berbeda. Jika semua dijadikan satu job besar, debugging jadi lebih sulit dan paralelisme hilang.

Contoh struktur file

Letakkan workflow pada:

.github/workflows/ci.yml

Contoh workflow berikut cukup realistis untuk proyek Go Fiber:

name: ci

on:
  pull_request:
    branches:
      - main
      - develop
  push:
    branches:
      - main
      - develop

concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    name: Lint (Go ${{ matrix.go-version }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        go-version: ['1.22']
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go-version }}
          cache: true
          cache-dependency-path: |
            go.sum

      - name: Verify modules
        run: go mod verify

      - name: Vet
        run: go vet ./...

      - name: Format check
        run: test -z "$(gofmt -l .)"

  test:
    name: Test (Go ${{ matrix.go-version }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        go-version: ['1.21', '1.22']
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go-version }}
          cache: true
          cache-dependency-path: |
            go.sum

      - name: Print Go version
        run: go version

      - name: Download modules
        run: go mod download

      - name: Run tests
        env:
          CGO_ENABLED: 0
        run: go test ./... -count=1 -race

  build:
    name: Build (Go ${{ matrix.go-version }})
    runs-on: ubuntu-latest
    needs: [lint, test]
    strategy:
      fail-fast: false
      matrix:
        go-version: ['1.21', '1.22']
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go-version }}
          cache: true
          cache-dependency-path: |
            go.sum

      - name: Build app
        env:
          CGO_ENABLED: 0
        run: go build -v ./...

Mengapa struktur ini efektif

  • Lint terpisah membuat error statis terlihat cepat tanpa menunggu test selesai.
  • Test dengan matrix memberi jaminan kompatibilitas lintas versi Go.
  • Build setelah lint dan test mencegah kompilasi yang tidak perlu ketika quality gate dasar sudah gagal.
  • concurrency membatalkan run lama pada branch yang sama, sehingga runner tidak terbuang untuk commit yang sudah tidak relevan.

Kapan memakai matrix, dan kapan tidak perlu

Matrix strategy menambah cakupan validasi, tetapi juga menambah biaya komputasi dan durasi total. Karena itu, gunakan matrix secara selektif.

Gunakan matrix ketika

  • Anda mendukung lebih dari satu versi Go secara resmi.
  • Tim memiliki pengguna internal atau layanan lain yang belum pindah ke versi Go terbaru.
  • Library atau service Fiber Anda dipakai lintas repo sehingga kompatibilitas penting.
  • Anda sering menemui regresi yang hanya muncul pada versi Go tertentu.

Tidak perlu matrix penuh ketika

  • Proyek internal hanya berjalan pada satu versi Go yang dikontrol ketat.
  • Pipeline sudah terlalu mahal, dan risiko incompatibility rendah.
  • Lint atau format check tidak bergantung pada multi-versi.

Pola yang umum dipakai adalah:

  • lint di satu versi Go,
  • test di dua atau lebih versi Go,
  • build di versi yang sama dengan test atau hanya versi target utama.

Jika Anda baru membangun CI untuk proyek Fiber, mulailah dari 2 versi Go yang benar-benar didukung. Menambahkan terlalu banyak versi sejak awal sering membuat feedback lebih lambat tanpa nilai tambah yang sebanding.

Cache module dan build cache Go di GitHub Actions

Bagian ini penting karena banyak pipeline Go lambat bukan karena test-nya berat, tetapi karena dependency selalu diunduh ulang dan kompilasi paket diulang dari nol.

Apa yang dicache

Secara umum, ada dua jenis cache yang relevan:

  • module cache: hasil unduhan dependency dari go mod download,
  • build cache: hasil kompilasi paket yang dapat digunakan ulang oleh perintah seperti go test dan go build.

Pada GitHub Actions, actions/setup-go dapat membantu caching secara otomatis jika diaktifkan. Ini biasanya cukup untuk banyak proyek, selama key cache mengikuti perubahan dependency dengan benar.

Konfigurasi cache yang aman

Contoh yang aman dan sederhana adalah:

- name: Setup Go
  uses: actions/setup-go@v5
  with:
    go-version: ${{ matrix.go-version }}
    cache: true
    cache-dependency-path: |
      go.sum

Mengapa ini aman?

  • go-version ikut membedakan cache antar versi toolchain.
  • go.sum dipakai sebagai penentu perubahan dependency, sehingga cache tidak dipakai secara membabi buta setelah module berubah.
  • Anda tidak perlu menulis key cache manual kecuali ada kebutuhan khusus.

Kapan perlu cache-dependency-path lebih dari satu file

Jika repo Anda memakai monorepo atau memiliki lebih dari satu module Go, Anda bisa menambahkan beberapa file go.sum:

cache-dependency-path: |
  go.sum
  services/api/go.sum
  services/worker/go.sum

Ini membantu cache invalidation tetap akurat. Kesalahan umum adalah hanya menunjuk satu go.sum padahal dependency tersebar di beberapa module.

Kesalahan umum yang membuat cache tidak konsisten

  • Memakai cache key terlalu umum, sehingga cache lama terbaca untuk dependency yang sudah berubah.
  • Mencampur cache lintas versi Go, yang dapat menghasilkan hit cache yang tidak relevan.
  • Meng-cache terlalu banyak direktori kustom tanpa memahami isinya, lalu sulit membedakan cache valid dan cache rusak.
  • Mengandalkan cache untuk kebenaran build. Cache hanya akselerator, bukan sumber kebenaran.

Prinsipnya: cache harus mempercepat, bukan mengubah hasil. Jika tanpa cache pipeline lolos tetapi dengan cache sering gagal acak, berarti strategi cache perlu disederhanakan.

Mencegah job lambat dan pemborosan runner

Pipeline multi-versi mudah menjadi lambat jika semua hal dijalankan di semua kombinasi matrix. Beberapa strategi berikut biasanya cukup efektif.

1. Jalankan lint sekali saja

Lint, gofmt, dan go vet umumnya tidak perlu diulang di semua versi Go jika target Anda adalah feedback cepat. Menjalankannya pada satu versi baseline sudah cukup untuk banyak tim.

2. Pakai fail-fast sesuai konteks

Pada matrix, fail-fast: false berarti semua kombinasi tetap berjalan walaupun satu versi gagal. Ini berguna jika Anda ingin melihat apakah kegagalan hanya terjadi pada satu versi Go atau menyeluruh.

Namun ada trade-off:

  • fail-fast: false: diagnosis lebih lengkap, biaya runner lebih besar.
  • fail-fast: true: lebih hemat saat ada kegagalan awal, tetapi informasi lebih sedikit.

Untuk validasi kompatibilitas Go, false sering lebih berguna. Untuk job yang mahal, Anda bisa mempertimbangkan true.

3. Batalkan run lama dengan concurrency

Pada PR yang aktif, commit bisa masuk berkali-kali dalam beberapa menit. Tanpa concurrency, semua run lama tetap berjalan dan menghabiskan runner.

concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Konfigurasi ini cocok untuk workflow validasi kode karena hasil yang paling relevan adalah commit terbaru.

4. Hindari matrix berlebihan

Jangan langsung menggabungkan versi Go, OS, dan variasi environment lain jika belum benar-benar dibutuhkan. Matrix seperti ini bisa meledak jumlah job-nya:

2 versi Go x 3 OS x 2 mode test = 12 job

Untuk aplikasi Fiber biasa, validasi di ubuntu-latest sering cukup sebagai baseline CI. Tambahan OS hanya masuk akal bila aplikasi Anda memang harus kompatibel di lingkungan tersebut.

5. Pisahkan test cepat dan test berat

Jika ada integration test yang lambat atau flaky, jangan campur dengan unit test utama pada matrix yang sama. Pisahkan sebagai workflow atau job terpisah agar feedback dasar tetap cepat.

Strategi branch dan pull request

Workflow CI yang baik bukan hanya soal YAML, tetapi juga kapan dijalankan.

Trigger yang umum dipakai

  • pull_request: wajib untuk validasi sebelum merge,
  • push ke branch utama seperti main atau develop: menjaga branch tetap sehat setelah merge.

Contoh trigger yang konservatif:

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [main, develop]

Pola praktis untuk tim

  • Jalankan lint + test matrix pada semua PR ke branch utama.
  • Jalankan build pada PR dan push ke branch utama untuk menjaga integritas kompilasi.
  • Jika biaya CI tinggi, pertimbangkan agar build multi-versi hanya berjalan di PR penting atau di branch utama, sementara branch fitur cukup menjalankan subset.

Trade-off-nya jelas: semakin banyak validasi di PR, semakin aman sebelum merge, tetapi feedback bisa lebih lambat dan runner lebih mahal.

Debugging pipeline yang flaky atau hasilnya tidak stabil

Pipeline flaky pada proyek Go sering bukan karena Fiber-nya, melainkan karena test, cache, atau asumsi environment yang tidak eksplisit.

Gejala umum

  • test kadang lolos, kadang gagal, tanpa perubahan kode,
  • job tertentu lambat secara acak,
  • versi Go tertentu gagal sementara versi lain lolos,
  • setelah dependency berubah, cache terasa tidak sinkron.

Langkah debugging yang praktis

  1. Pastikan versi Go benar-benar yang diharapkan
    Tambahkan go version di awal job. Ini sederhana tetapi sering membantu memastikan matrix berjalan sesuai definisi.
  2. Nonaktifkan asumsi cache saat investigasi
    Jika curiga cache bermasalah, uji sementara tanpa cache atau ubah dependency agar cache invalidated. Tujuannya memastikan apakah akar masalah berasal dari source code atau artefak cache.
  3. Gunakan -count=1 pada test
    Ini membantu menghindari reuse hasil test cache saat Anda sedang memburu perilaku yang tidak konsisten.
  4. Periksa test yang bergantung pada waktu, urutan, atau port
    Test HTTP pada aplikasi Fiber kadang gagal jika memakai port tetap, berbagi state global, atau tidak membersihkan resource setelah selesai.
  5. Jalankan job yang sama secara lokal
    Minimal cocokkan perintah: go vet ./..., go test ./... -count=1 -race, dan go build ./.... Jika hanya gagal di CI, biasanya masalah ada pada environment assumption.
  6. Periksa perubahan pada go.sum
    Dependency yang berubah tetapi tidak tercermin dengan benar di cache path bisa menyebabkan perilaku aneh atau unduhan berulang.

Penyebab flaky yang sering terlewat

  • Test paralel yang berbagi state global.
  • Penggunaan environment variable yang tidak di-set eksplisit di workflow.
  • Race condition yang baru terlihat pada runner CI yang lebih cepat atau lebih lambat dari mesin lokal.
  • Timeout implisit pada test HTTP atau akses resource eksternal.

Jika sebuah test Fiber memerlukan service eksternal, pertimbangkan untuk memisahkannya dari unit test utama. CI dasar untuk PR sebaiknya memprioritaskan test yang deterministik dan cepat.

Trade-off kecepatan vs biaya CI

Menambah versi Go dalam matrix hampir selalu meningkatkan kepercayaan kompatibilitas, tetapi juga menambah konsumsi runner. Karena itu, desain workflow harus mengikuti risiko nyata proyek Anda.

Pendekatan hemat

  • Lint pada satu versi Go,
  • test pada dua versi Go yang didukung,
  • build hanya pada versi utama atau setelah test sukses,
  • aktifkan concurrency agar run lama dibatalkan.

Pendekatan lebih ketat

  • test dan build pada semua versi Go yang didukung,
  • tetap simpan lint di satu versi,
  • pisahkan test cepat dan test berat.

Tidak ada konfigurasi tunggal yang terbaik untuk semua tim. Untuk sebagian besar aplikasi Fiber, pendekatan hemat di atas sudah memberi keseimbangan yang baik antara feedback cepat dan coverage kompatibilitas.

Checklist implementasi untuk proyek Go Fiber

  • Tentukan versi Go yang memang didukung tim atau produk.
  • Gunakan matrix hanya pada job yang butuh validasi lintas versi, terutama test dan build.
  • Aktifkan cache di actions/setup-go dan arahkan cache-dependency-path ke file go.sum yang relevan.
  • Jalankan lint terpisah agar feedback error statis muncul cepat.
  • Gunakan concurrency untuk membatalkan run lama pada branch yang sama.
  • Pilih fail-fast berdasarkan kebutuhan diagnosis versus efisiensi runner.
  • Hindari matrix yang terlalu besar jika belum ada kebutuhan kompatibilitas OS atau mode test tambahan.
  • Saat pipeline flaky, validasi dulu tanpa asumsi cache dan pastikan test deterministik.

Penutup

Untuk Go Fiber: CI Matrix Go Version dan Cache Module di GitHub Actions, pola yang paling praktis adalah memisahkan lint, test, dan build; memakai matrix pada versi Go yang benar-benar didukung; lalu mengaktifkan cache module dan build cache melalui actions/setup-go dengan dependency path yang tepat. Dengan pendekatan ini, Anda bisa meningkatkan kepercayaan kompatibilitas tanpa membuat feedback tim terlalu lambat.

Mulailah dari workflow sederhana, ukur durasi job, lalu optimalkan secara bertahap. Dalam banyak kasus, perbaikan paling besar bukan datang dari menambah fitur CI, tetapi dari membatasi matrix secara sadar, memakai cache dengan benar, dan membuang test yang tidak deterministik dari jalur validasi utama.