Merilis versi baru aplikasi bukan sekadar git tag v1.2.3. Kita perlu strategi penomoran versi yang jelas, changelog yang terbaca, strategi rollback bila ada masalah, dan checklist rilis untuk memastikan tidak ada yang terlewat. Pada chapter ini kita akan mempraktikkan semantic versioning, melihat siklus release candidate hingga stabil, dan menutup dengan strategi rollback seperti blue-green dan canary deployment.
Semantic Versioning #
Semantic versioning (SemVer) adalah konvensi penomoran versi dengan format MAJOR.MINOR.PATCH. Setiap segmen memiliki arti yang konsisten:
| Segmen | Kapan dinaikkan | Contoh |
|---|---|---|
MAJOR | Ada breaking change (API lama tidak kompatibel) | 1.2.3 → 2.0.0 |
MINOR | Fitur baru backward compatible | 1.2.3 → 1.3.0 |
PATCH | Bug fix, tidak ada API baru | 1.2.3 → 1.2.4 |
prerelease | RC, beta, alpha | 2.0.0-RC1, 2.0.0-beta.3 |
Cuplikan berikut mendemonstrasikan struktur Versi dan perbandingannya:
// release_management.kt
// Mendemonstrasikan konsep release management:
// - Semantic versioning (major.minor.patch)
// - Release candidate (RC) dan stable
// - Changelog dan release notes
// - Rollback dan blue-green deployment
//
// Mensimulasikan proses release dan menampilkan ringkasan.
data class Versi(val major: Int, val minor: Int, val patch: Int, val label: String = "") : Comparable<Versi> {
override fun compareTo(other: Versi): Int {
return when {
major != other.major -> major.compareTo(other.major)
minor != other.minor -> minor.compareTo(other.minor)
patch != other.patch -> patch.compareTo(other.patch)
else -> label.compareTo(other.label)
}
}
override fun toString(): String = "$major.$minor.$patch${if (label.isEmpty()) "" else "-$label"}"
}
data class Release(
val versi: Versi,
val tanggal: String,
val tipe: String,
val commitSha: String,
val changelog: List<String>,
val rollbackKe: Versi?
)
fun tipeRelease(v: Versi, prev: Versi?): String {
if (prev == null) return "initial"
return when {
v.major > prev.major -> "major"
v.minor > prev.minor -> "minor"
v.patch > prev.patch -> "patch"
else -> "prerelease"
}
}
fun main() {
println("=== Release Management ===")
println()
println("--- 1. Semantic versioning ---")
val contohVersi = listOf(
Versi(1, 0, 0),
Versi(1, 1, 0),
Versi(1, 1, 1),
Versi(2, 0, 0),
Versi(2, 0, 0, "RC1"),
Versi(2, 0, 0, "RC2")
)
contohVersi.forEach { v ->
val kategori = when {
v.major == 0 -> "0.x (API belum stabil)"
v.label.startsWith("RC") -> "release candidate"
v.label.startsWith("beta") -> "beta"
v.label.isEmpty() -> "stabil"
else -> "prerelease"
}
println(" $v ($kategori)")
}
println()
println("--- 2. Perbandingan versi ---")
val v1 = Versi(1, 2, 3)
val v2 = Versi(1, 2, 4)
val v3 = Versi(1, 3, 0)
println(" $v1 < $v2 : ${v1 < v2}")
println(" $v2 < $v3 : ${v2 < v3}")
println(" $v3 > $v1 : ${v3 > v1}")
println()
println("--- 3. Daftar release (simulasi) ---")
val releases = listOf(
Release(Versi(1, 0, 0), "2025-01-15", "initial", "abc1234",
listOf("Rilis awal aplikasi"), null),
Release(Versi(1, 0, 1), "2025-01-22", "patch", "def5678",
listOf("Fix crash di login", "Update README"), Versi(1, 0, 0)),
Release(Versi(1, 1, 0), "2025-02-10", "minor", "ghi9012",
listOf("Tambah fitur export CSV", "Tambah API /report"), Versi(1, 0, 1)),
Release(Versi(2, 0, 0, "RC1"), "2025-03-01", "prerelease", "jkl3456",
listOf("Refactor arsitektur ke microservice"), Versi(1, 1, 0)),
Release(Versi(2, 0, 0), "2025-03-15", "major", "mno7890",
listOf("BREAKING: hapus API v1", "BREAKING: ganti format response ke JSON:API",
"Tambah fitur multi-tenant"), Versi(1, 1, 0))
)
releases.forEach { r ->
println(" ${r.versi} ${r.tanggal} ${r.tipe.padEnd(12)} sha=${r.commitSha}")
r.changelog.take(3).forEach { println(" - $it") }
if (r.rollbackKe != null) println(" rollback -> ${r.rollbackKe}")
}
println()
println("--- 4. Tipe perubahan dan kenaikan versi ---")
val rules = listOf(
Triple("patch (+0.0.1)", "bug fix, tidak ada API baru", "1.2.3 -> 1.2.4"),
Triple("minor (+0.1.0)", "fitur baru backward compatible", "1.2.3 -> 1.3.0"),
Triple("major (+1.0.0)", "breaking change", "1.2.3 -> 2.0.0"),
Triple("prerelease", "RC, beta, alpha", "2.0.0-RC1 -> 2.0.0-RC2 -> 2.0.0")
)
rules.forEach { (t, d, c) ->
println(" $t.padEnd(20) $d.padEnd(38) $c")
}
println()
println("--- 5. Changelog format (Keep a Changelog) ---")
println(" # Changelog")
println(" ## [2.0.0] - 2025-03-15")
println(" ### Added")
println(" - Fitur multi-tenant")
println(" ### Changed")
println(" - Format response ke JSON:API (BREAKING)")
println(" ### Removed")
println(" - API v1 (BREAKING)")
println(" ### Fixed")
println(" - Race condition di cache layer")
println()
println("--- 6. Strategi rollback ---")
val strategi = listOf(
"Blue-green: deploy ke environment biru, switch router, rollback = switch balik",
"Canary: 1% traffic ke versi baru, naikkan bertahap, rollback = turunkan ke 0%",
"Feature flag: matikan fitur baru tanpa redeploy",
"Database: forward-only migration (tidak ada rollback schema)",
"Configuration: keep last N versions untuk redeploy cepat"
)
strategi.forEach { println(" $it") }
println()
println("--- 7. Checklist rilis ---")
val checklist = listOf(
"[ ] Semua test lulus (unit + integration + e2e)",
"[ ] Performance test lulus (regression <5%)",
"[ ] Security scan (SAST + dependency) bersih",
"[ ] Changelog ditulis lengkap",
"[ ] Versi di build.gradle.kts diupdate",
"[ ] Tag Git dibuat (v2.0.0)",
"[ ] Artifact di-publish ke registry",
"[ ] Release note di GitHub/GitLab",
"[ ] Stakeholder diberi tahu (email, Slack)",
"[ ] Monitoring dashboard siap",
"[ ] Rollback plan diuji di staging"
)
checklist.forEach { println(" $it") }
}Output yang Dihasilkan:
=== Release Management ===
--- 1. Semantic versioning ---
1.0.0 (stabil)
1.1.0 (stabil)
1.1.1 (stabil)
2.0.0 (stabil)
2.0.0-RC1 (release candidate)
2.0.0-RC2 (release candidate)
--- 2. Perbandingan versi ---
1.2.3 < 1.2.4 : true
1.2.4 < 1.3.0 : true
1.3.0 > 1.2.3 : true
--- 3. Daftar release (simulasi) ---
1.0.0 2025-01-15 initial sha=abc1234
- Rilis awal aplikasi
1.0.1 2025-01-22 patch sha=def5678
- Fix crash di login
- Update README
rollback -> 1.0.0
1.1.0 2025-02-10 minor sha=ghi9012
- Tambah fitur export CSV
- Tambah API /report
rollback -> 1.0.1
2.0.0-RC1 2025-03-01 prerelease sha=jkl3456
- Refactor arsitektur ke microservice
rollback -> 1.1.0
2.0.0 2025-03-15 major sha=mno7890
- BREAKING: hapus API v1
- BREAKING: ganti format response ke JSON:API
- Tambah fitur multi-tenant
rollback -> 1.1.0
--- 4. Tipe perubahan dan kenaikan versi ---
patch (+0.0.1).padEnd(20) bug fix, tidak ada API baru.padEnd(38) 1.2.3 -> 1.2.4
minor (+0.1.0).padEnd(20) fitur baru backward compatible.padEnd(38) 1.2.3 -> 1.3.0
major (+1.0.0).padEnd(20) breaking change.padEnd(38) 1.2.3 -> 2.0.0
prerelease.padEnd(20) RC, beta, alpha.padEnd(38) 2.0.0-RC1 -> 2.0.0-RC2 -> 2.0.0
--- 5. Changelog format (Keep a Changelog) ---
# Changelog
## [2.0.0] - 2025-03-15
### Added
- Fitur multi-tenant
### Changed
- Format response ke JSON:API (BREAKING)
### Removed
- API v1 (BREAKING)
### Fixed
- Race condition di cache layer
--- 6. Strategi rollback ---
Blue-green: deploy ke environment biru, switch router, rollback = switch balik
Canary: 1% traffic ke versi baru, naikkan bertahap, rollback = turunkan ke 0%
Feature flag: matikan fitur baru tanpa redeploy
Database: forward-only migration (tidak ada rollback schema)
Configuration: keep last N versions untuk redeploy cepat
--- 7. Checklist rilis ---
[ ] Semua test lulus (unit + integration + e2e)
[ ] Performance test lulus (regression <5%)
[ ] Security scan (SAST + dependency) bersih
[ ] Changelog ditulis lengkap
[ ] Versi di build.gradle.kts diupdate
[ ] Tag Git dibuat (v2.0.0)
[ ] Artifact di-publish ke registry
[ ] Release note di GitHub/GitLab
[ ] Stakeholder diberi tahu (email, Slack)
[ ] Monitoring dashboard siap
[ ] Rollback plan diuji di stagingPada cuplikan di atas, Versi mengimplementasikan Comparable sehingga kita bisa membandingkan dua versi langsung lewat <, >. tipeRelease() menentukan kategori rilis berdasarkan selisih dengan versi sebelumnya — penting untuk menulis changelog yang akurat.
Release Candidate dan Stabil #
Untuk versi mayor yang punya banyak breaking change, rilis release candidate (RC) terlebih dahulu. RC adalah versi hampir final — fitur lengkap, tetapi mungkin masih ada bug yang ditemukan komunitas early adopter:
2.0.0-alpha.1 -> Fitur awal, API belum stabil
2.0.0-beta.1 -> API stabil, fokus pada bug fix
2.0.0-RC1 -> Kandidat rilis, mohon feedback komunitas
2.0.0-RC2 -> Bug fix dari RC1
2.0.0 -> Stabil, siap produksiRC biasanya dipublikasikan 2–4 minggu sebelum stabil. Pemakai library bisa menguji RC dan melaporkan bug sebelum versi stabil dirilis. Tanpa RC, breaking change sering mengejutkan pengguna library dan menyebabkan kemarahan.
Changelog (Keep a Changelog) #
Format changelog yang baik adalah mengikuti standar Keep a Changelog (https://keepachangelog.com/). Setiap entri dikategorikan:
# Changelog
## [2.0.0] - 2025-03-15
### Added
- Fitur multi-tenant
- Endpoint /api/v2/reports dengan paginasi
### Changed
- Format response ke JSON:API (BREAKING)
- Default timeout naik dari 5s ke 10s
### Removed
- API v1 (BREAKING) — gunakan API v2
### Fixed
- Race condition di cache layer
- Memory leak di Kafka consumerKategori yang umum: Added (fitur baru), Changed (perubahan backward-incompatible), Deprecated (fitur yang akan dihapus), Removed (fitur yang dihapus di versi ini), Fixed (bug fix), Security (perbaikan keamanan). Hindari entry generik seperti “bug fix” — sebutkan bug spesifik yang diperbaiki.
Strategi Rollback #
Bila rilis baru bermasalah, kita perlu bisa rollback ke versi lama dengan cepat. Tiga strategi paling umum:
| Strategi | Cara | Kecepatan rollback | Biaya |
|---|---|---|---|
| Blue-green | Dua environment identik; switch router | detik | tinggi (2x resource) |
| Canary | Traffic 1% → 10% → 50% → 100%; rollback = turunkan ke 0% | menit | sedang |
| Feature flag | Matikan fitur baru tanpa redeploy | detik | rendah |
Blue-green cocok untuk aplikasi dengan state rendah (stateless). Canary cocok untuk aplikasi dengan banyak pengguna — bila ada masalah, hanya 1% pengguna terdampak. Feature flag cocok untuk fitur yang masih dalam eksperimen.
val strategi = listOf(
"Blue-green: deploy ke environment biru, switch router, rollback = switch balik",
"Canary: 1% traffic ke versi baru, naikkan bertahap, rollback = turunkan ke 0%",
"Feature flag: matikan fitur baru tanpa redeploy",
"Database: forward-only migration (tidak ada rollback schema)",
"Configuration: keep last N versions untuk redeploy cepat"
)Checklist Rilis #
Checklist berikut memastikan rilis berjalan mulus:
[ ] Semua test lulus (unit + integration + e2e)
[ ] Performance test lulus (regression < 5%)
[ ] Security scan (SAST + dependency) bersih
[ ] Changelog ditulis lengkap (Keep a Changelog)
[ ] Versi di build.gradle.kts diupdate
[ ] Tag Git dibuat (v2.0.0)
[ ] Artifact di-publish ke registry
[ ] Release note di GitHub/GitLab
[ ] Stakeholder diberi tahu (email, Slack)
[ ] Monitoring dashboard siap
[ ] Rollback plan diuji di stagingUntuk library publik, tambahkan langkah:
- Verifikasi binary compatibility lewat
binary-compatibility-validator. - Sign artifact dengan GPG (untuk Maven Central).
- Upload ke Maven Central staging, lalu promote ke release.
- Update dokumen di situs dokumentasi.
Referensi Lanjutan #
- Gradle User Guide — dokumentasi Gradle untuk release management dan publishing artefak
- Gradle Version Catalogs — version catalogs Gradle untuk mengelola versi rilis dependency
Selanjutnya #
Section deployment dan production selesai. Section berikutnya (Sumber Belajar) akan mengarahkan kita ke dokumentasi resmi, API reference, dan panduan spesifik per domain — agar kita tahu ke mana harus mencari jawaban saat menemui masalah nyata di luar materi course ini.