Rilis adalah momen kode berubah menjadi perangkat kerja bagi pengguna — sekaligus momen paling berisiko dalam siklus pengembangan. Perbaikan akar masalah dari chapter sebelumnya, fitur baru, dan perubahan konfigurasi harus menyeberang dari mesin pengembang ke produksi tanpa kehilangan kendali. Release management mengatur tiga hal tersebut: apa yang dirilis (nomor versi yang bercerita), apa yang berubah (changelog yang bisa diaudit), dan bagaimana perubahannya sampai (strategi rollout dengan jalur mundur). Chapter penutup ini merangkai semuanya di atas fondasi pipeline CI/CD yang sudah kita bangun.

Nomor Versi yang Bercerita #

Semantic Versioning pernah kita lihat dari sisi konsumen di chapter Dependency Versioning; sebagai produsen aplikasi, aturan yang sama kini menuntut disiplin dari kita. Format MAJOR.MINOR.PATCH menetapkan arti tiap kenaikan: PATCH perbaikan kompatibel, MINOR fitur baru yang tetap kompatibel, MAJOR perubahan yang memaksa konsumen API menyesuaikan. Nomor versi yang disiplin adalah kontrak — pengguna dan tim integrasi memutuskan seberapa waspada memperbarui hanya dari tiga angka itu. Program berikut menerapkan aturannya lengkap dengan matriks rilis:

import java.util.List;

/** Matriks rilis: jenis perubahan menentukan nomor versi, kanal, dan jalur mundur. */
public class MatriksRilis {

    /** Jenis perubahan sejak rilis terakhir — bahasa Semantic Versioning. */
    enum Jenis { PATCH, MINOR, MAJOR }

    /** Satu perubahan masuk rilis berikutnya. */
    record Perubahan(Jenis jenis, String contoh) {}

    /** Naikkan versi sesuai jenis perubahan: patch +1, minor +1 & patch reset, major +1 & sisanya reset. */
    static String versiBerikutnya(String versi, Jenis jenis) {
        String[] angka = versi.split("\\.");
        int mayor = Integer.parseInt(angka[0]);
        int minor = Integer.parseInt(angka[1]);
        int patch = Integer.parseInt(angka[2]);
        return switch (jenis) {
            case PATCH -> mayor + "." + minor + "." + (patch + 1);
            case MINOR -> mayor + "." + (minor + 1) + ".0";
            case MAJOR -> (mayor + 1) + ".0.0";
        };
    }

    /** Satu lingkungan dalam rantai promosi: nama dan peran pemeriksaannya. */
    record Lingkungan(String nama, String gerbang) {}

    public static void main(String[] args) {
        String versiKini = "2.1.0";
        List<Perubahan> perubahan = List.of(
                new Perubahan(Jenis.PATCH, "perbaiki kwitansi terpotong"),
                new Perubahan(Jenis.MINOR, "laporan penjualan harian"),
                new Perubahan(Jenis.MAJOR, "API pesanan berubah bentuk"));

        System.out.println("== Matriks versi dari " + versiKini + " ==");
        System.out.printf("%-7s %-30s %-10s %s%n", "jenis", "contoh perubahan", "versi", "arti bagi pengguna");
        for (Perubahan p : perubahan) {
            String arti = switch (p.jenis()) {
                case PATCH -> "aman diperbarui (bugfix)";
                case MINOR -> "aman; fitur baru tersedia";
                case MAJOR -> "perlu penyesuaian konsumen API";
            };
            System.out.printf("%-7s %-30s %-10s %s%n",
                    p.jenis(), p.contoh(), versiBerikutnya(versiKini, p.jenis()), arti);
        }
        System.out.println();

        List<Perubahan> rilisBerikut = List.of(
                new Perubahan(Jenis.MINOR, "laporan penjualan harian"),
                new Perubahan(Jenis.PATCH, "perbaiki kwitansi terpotong"));
        String target = versiBerikutnya(versiKini, rilisBerikut.get(0).jenis());
        System.out.println("== Rilis berikutnya: v" + target + " ==");
        for (Perubahan p : rilisBerikut) {
            System.out.println("  perubahan: [" + p.jenis() + "] " + p.contoh());
        }
        System.out.println("  nomor versi ditetapkan dari jenis terbesar: " + target);
        System.out.println();

        System.out.println("== Promosi artefak yang sama lintas lingkungan ==");
        String cerna = "sha256:7f3c19ab"; // sidik jari artefak — identik di seluruh lingkungan
        List<Lingkungan> rantai = List.of(
                new Lingkungan("ci", "pengujian otomatis lulus"),
                new Lingkungan("staging", "pemeriksaan manual + uji beban"),
                new Lingkungan("produksi", "metrik sehat pasca rollout"));
        for (Lingkungan l : rantai) {
            System.out.printf("  %-9s %s  %s%n", l.nama(), cerna, "(" + l.gerbang() + ")");
        }
        System.out.println();

        System.out.println("== Strategi rollout dan jalur mundur ==");
        System.out.printf("%-12s %-22s %s%n", "strategi", "cakupan awal", "jalur mundur");
        System.out.printf("%-12s %-22s %s%n", "big-bang", "100% seketika", "redeploy versi lama");
        System.out.printf("%-12s %-22s %s%n", "rolling", "pod bergantian", "pulihkan pod lama bertahap");
        System.out.printf("%-12s %-22s %s%n", "blue-green", "saklar penuh ke hijau", "alihkan balik ke biru");
        System.out.printf("%-12s %-22s %s%n", "canary", "1-5% trafik", "hentikan canary, trafik tetap di stabil");
    }
}

Output yang Dihasilkan:

text
== Matriks versi dari 2.1.0 ==
jenis   contoh perubahan               versi      arti bagi pengguna
PATCH   perbaiki kwitansi terpotong    2.1.1      aman diperbarui (bugfix)
MINOR   laporan penjualan harian       2.2.0      aman; fitur baru tersedia
MAJOR   API pesanan berubah bentuk     3.0.0      perlu penyesuaian konsumen API

== Rilis berikutnya: v2.2.0 ==
  perubahan: [MINOR] laporan penjualan harian
  perubahan: [PATCH] perbaiki kwitansi terpotong
  nomor versi ditetapkan dari jenis terbesar: 2.2.0

== Promosi artefak yang sama lintas lingkungan ==
  ci        sha256:7f3c19ab  (pengujian otomatis lulus)
  staging   sha256:7f3c19ab  (pemeriksaan manual + uji beban)
  produksi  sha256:7f3c19ab  (metrik sehat pasca rollout)

== Strategi rollout dan jalur mundur ==
strategi     cakupan awal           jalur mundur
big-bang     100% seketika          redeploy versi lama
rolling      pod bergantian         pulihkan pod lama bertahap
blue-green   saklar penuh ke hijau  alihkan balik ke biru
canary       1-5% trafik            hentikan canary, trafik tetap di stabil

Mari kita bedah matriksnya:

  1. versiBerikutnya(versi, jenis): aturan kenaikan Semantic Versioning dalam satu switch — PATCH menambah angka terakhir, MINOR menaikkan angka tengah dan me-reset patch, MAJOR menaikkan angka pertama dan me-reset keduanya. Tiga baris case itu adalah seluruh spesifikasinya.
  2. Rilis berikutnya 2.2.0: daftar berisi satu MINOR dan satu PATCH, tetapi versi ditetapkan dari jenis terbesar — cukup satu MINOR untuk menjadikan seluruh rilis berversi minor. Angka mengikuti jenis perubahan paling berdampak.
  3. Kolom arti bagi pengguna: 2.2.0 memberi tahu pengguna bahwa pembaruan aman, 3.0.0 memberi tahu konsumen API untuk bersiap menyesuaikan kode. Nomor yang melompat tanpa alasan merusak kepercayaan pada kontrak ini.
  4. Rantai promosi: sidik jari sha256:7f3c19ab identik di tiga lingkungan — aplikasi tidak dibangun ulang untuk produksi, melainkan dipromosikan. Build once, deploy many dari chapter CI/CD menjadi baris yang bisa diperiksa: bandingkan cerna artefak antar lingkungan.

Changelog yang Bisa Diaudit #

Nomor versi menjawab “seberapa besar berubahnya”; changelog menjawab “apa persisnya yang berubah”. Format Keep a Changelog mengelompokkan perubahan per kategori dengan tanggal rilis beku:

markdown CHANGELOG.md — format Keep a Changelog (cuplikan)
## [2.2.0] - 2026-01-15
### Added
- Laporan penjualan harian dengan ekspor CSV.

### Fixed
- Kwitansi terpotong pada printer 58 mm.

## [2.1.1] - 2026-01-08
### Fixed
- Zona waktu laporan harian kini mengikuti Asia/Jakarta.

Setiap rilis menjadi satu bagian ## [versi] - tanggal yang dikelompokkan per kategori (Added, Changed, Fixed, Removed), ditutup tautan pembanding antar tag.

Changelog seperti ini ditulis dari commit yang rapi — dan tag v2.2.0 yang dipendorong memicu workflow rilis dari chapter GitHub Actions: pipeline membangun artefak sekali, menempel versi sebagai tag image, lalu mengunggah GitHub Release berisi JAR dan catatan rilisnya. Nomor versi, tag git, tag image, dan changelog merujuk satu momen yang sama.

Strategi Rollout dan Jalur Mundur #

Rilis yang sehat punya rencana mundur sebelum digulirkan. Empat strategi menawarkan kompromi yang berbeda antara kecepatan dan pengamanan:

StrategiCakupan awalKebutuhan infrastrukturCocok untuk
Big-bangseluruh trafik seketikapaling sederhanaaplikasi internal berdampak kecil
Rollingpod bergantian sedikit demi sedikitreplika lebih dari satulayanan tanpa downtime
Blue-greenversi baru utuh diuji sebelum saklarkapasitas dua kali lipat + saklar trafikrilis besar yang butuh putar balik cepat
Canary1-5% trafik ke versi barupemisahan trafik + metrik per versilayanan dengan jumlah pengguna besar

Canary adalah bentuk paling hati-hati: sebagian kecil trafik sungguhan menguji rilis baru sementara metrik latensi, error, dan bisnis dipantau per versi — keputusan melanjutkan atau mundur diambil dari angka. Kubernetes Deployment menjalankan rolling secara bawaan; blue-green dan canary memakai service mesh atau alat rollout khusus.

Section ini menuntaskan perjalanan aplikasi Java dari ratusan berkas .class menjadi rilis bernomor versi yang teruji, terkontainerisasi, terobservasi, dan punya jalur mundur. Peralatan berikutnya di course ini menggeser fokus dari menyala dan terkendali menjadi berjalan cepat: section berikutnya membahas performance dan optimasi JVM secara mendalam.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

MatriksRilis.java

Unduh berkas
java
import java.util.List;

/** Matriks rilis: jenis perubahan menentukan nomor versi, kanal, dan jalur mundur. */
public class MatriksRilis {

    /** Jenis perubahan sejak rilis terakhir — bahasa Semantic Versioning. */
    enum Jenis { PATCH, MINOR, MAJOR }

    /** Satu perubahan masuk rilis berikutnya. */
    record Perubahan(Jenis jenis, String contoh) {}

    /** Naikkan versi sesuai jenis perubahan: patch +1, minor +1 & patch reset, major +1 & sisanya reset. */
    static String versiBerikutnya(String versi, Jenis jenis) {
        String[] angka = versi.split("\\.");
        int mayor = Integer.parseInt(angka[0]);
        int minor = Integer.parseInt(angka[1]);
        int patch = Integer.parseInt(angka[2]);
        return switch (jenis) {
            case PATCH -> mayor + "." + minor + "." + (patch + 1);
            case MINOR -> mayor + "." + (minor + 1) + ".0";
            case MAJOR -> (mayor + 1) + ".0.0";
        };
    }

    /** Satu lingkungan dalam rantai promosi: nama dan peran pemeriksaannya. */
    record Lingkungan(String nama, String gerbang) {}

    public static void main(String[] args) {
        String versiKini = "2.1.0";
        List<Perubahan> perubahan = List.of(
                new Perubahan(Jenis.PATCH, "perbaiki kwitansi terpotong"),
                new Perubahan(Jenis.MINOR, "laporan penjualan harian"),
                new Perubahan(Jenis.MAJOR, "API pesanan berubah bentuk"));

        System.out.println("== Matriks versi dari " + versiKini + " ==");
        System.out.printf("%-7s %-30s %-10s %s%n", "jenis", "contoh perubahan", "versi", "arti bagi pengguna");
        for (Perubahan p : perubahan) {
            String arti = switch (p.jenis()) {
                case PATCH -> "aman diperbarui (bugfix)";
                case MINOR -> "aman; fitur baru tersedia";
                case MAJOR -> "perlu penyesuaian konsumen API";
            };
            System.out.printf("%-7s %-30s %-10s %s%n",
                    p.jenis(), p.contoh(), versiBerikutnya(versiKini, p.jenis()), arti);
        }
        System.out.println();

        List<Perubahan> rilisBerikut = List.of(
                new Perubahan(Jenis.MINOR, "laporan penjualan harian"),
                new Perubahan(Jenis.PATCH, "perbaiki kwitansi terpotong"));
        String target = versiBerikutnya(versiKini, rilisBerikut.get(0).jenis());
        System.out.println("== Rilis berikutnya: v" + target + " ==");
        for (Perubahan p : rilisBerikut) {
            System.out.println("  perubahan: [" + p.jenis() + "] " + p.contoh());
        }
        System.out.println("  nomor versi ditetapkan dari jenis terbesar: " + target);
        System.out.println();

        System.out.println("== Promosi artefak yang sama lintas lingkungan ==");
        String cerna = "sha256:7f3c19ab"; // sidik jari artefak — identik di seluruh lingkungan
        List<Lingkungan> rantai = List.of(
                new Lingkungan("ci", "pengujian otomatis lulus"),
                new Lingkungan("staging", "pemeriksaan manual + uji beban"),
                new Lingkungan("produksi", "metrik sehat pasca rollout"));
        for (Lingkungan l : rantai) {
            System.out.printf("  %-9s %s  %s%n", l.nama(), cerna, "(" + l.gerbang() + ")");
        }
        System.out.println();

        System.out.println("== Strategi rollout dan jalur mundur ==");
        System.out.printf("%-12s %-22s %s%n", "strategi", "cakupan awal", "jalur mundur");
        System.out.printf("%-12s %-22s %s%n", "big-bang", "100% seketika", "redeploy versi lama");
        System.out.printf("%-12s %-22s %s%n", "rolling", "pod bergantian", "pulihkan pod lama bertahap");
        System.out.printf("%-12s %-22s %s%n", "blue-green", "saklar penuh ke hijau", "alihkan balik ke biru");
        System.out.printf("%-12s %-22s %s%n", "canary", "1-5% trafik", "hentikan canary, trafik tetap di stabil");
    }
}