Saat Aplikasi Mobile Anda Rusak karena Pengguna Tidak Mau Update

Anda mendorong endpoint backend baru. Versi aplikasi terbaru menanganinya dengan sempurna. Semua terlihat hijau di pipeline CI/CD Anda. Lalu laporan crash mulai berdatangan.

Pengguna dengan versi aplikasi lama mengakses endpoint yang sama, tetapi aplikasi mereka tidak bisa mengurai format respons yang baru. Mereka mendapatkan layar kosong, crash, atau lebih buruk lagi—kehilangan data secara diam-diam. Anda tidak mengubah backend untuk semua orang. Anda mengubahnya untuk versi aplikasi terbaru. Namun backend tidak pandang bulu. Ia memberikan respons yang sama kepada setiap klien yang bertanya.

Inilah biaya tersembunyi dari pengiriman aplikasi mobile. Berbeda dengan aplikasi web di mana Anda mengendalikan klien, aplikasi mobile hidup di perangkat yang bukan milik Anda. Pengguna yang memutuskan kapan akan memperbarui. Beberapa memperbarui segera. Beberapa menunggu berminggu-minggu. Beberapa masih menjalankan versi yang Anda rilis enam bulan lalu. Dan backend Anda terus berkembang selama waktu itu.

Masalah Kesenjangan Versi

Ketegangan intinya sederhana: backend berubah secara terus-menerus, tetapi klien mobile memperbarui sesuai jadwal mereka sendiri. Setiap kali Anda menambahkan endpoint baru, mengubah struktur respons, atau menandai field sebagai tidak digunakan lagi, Anda menciptakan potensi titik patah bagi versi aplikasi yang lebih lama.

Diagram sekuens berikut menunjukkan bagaimana perubahan backend dapat merusak versi aplikasi lama sementara versi baru berfungsi dengan baik:

sequenceDiagram participant AppV1 as App v1.0 participant AppV2 as App v2.0 participant Backend as Backend Note over Backend: Backend berevolusi AppV1->>Backend: GET /api/orders (format lama) Backend-->>AppV1: 200 OK (respons lama) Note over AppV1: Berfungsi baik AppV2->>Backend: GET /api/v2/orders (format baru) Backend-->>AppV2: 200 OK (respons baru) Note over AppV2: Berfungsi baik AppV1->>Backend: GET /api/v2/orders (format baru) Backend-->>AppV1: 200 OK (respons baru) Note over AppV1: CRASH - tidak bisa mengurai format baru Note over Backend: Solusi: pertahankan endpoint lama untuk kompatibilitas mundur

Kebanyakan tim tidak menyadari hal ini sampai produksi rusak. Mereka menguji aplikasi terbaru dengan backend terbaru, semuanya lolos, dan mereka merilis. Namun rangkaian pengujian tidak pernah menjalankan aplikasi lama dengan backend baru. Kombinasi itu tidak terlihat sampai pengguna nyata mengalaminya.

Masalahnya semakin parah seiring bertambahnya basis pengguna Anda. Lebih banyak pengguna berarti lebih banyak versi yang beredar. Setiap versi memiliki ekspektasinya sendiri tentang apa yang harus dikembalikan oleh backend. Backend Anda harus memuaskan semuanya secara bersamaan, setidaknya untuk sementara waktu.

Ketahui Versi Apa yang Beredar

Sebelum Anda dapat mengelola kompatibilitas, Anda perlu visibilitas. Toko aplikasi memberi Anda beberapa data—Google Play Console dan App Store Connect sama-sama menampilkan distribusi versi. Namun data itu tertunda dan teragregasi. Data itu memberi tahu Anda apa yang diinstal pengguna, bukan apa yang secara aktif mereka gunakan.

Pendekatan yang lebih baik: kirimkan versi aplikasi dengan setiap permintaan. Tambahkan header kustom seperti X-App-Version atau enkode dalam string User-Agent Anda. Backend Anda mencatat informasi ini, dan Anda dapat mengagregasinya ke dalam dasbor yang menunjukkan adopsi versi secara real-time.

Data ini menjawab pertanyaan-pertanyaan kritis:

  • Berapa persentase pengguna aktif pada setiap versi?
  • Seberapa cepat pengguna mengadopsi rilis terbaru?
  • Versi lama mana yang masih memiliki lalu lintas signifikan?
  • Kapan Anda dapat dengan aman menghentikan dukungan untuk versi lama?

Tanpa data ini, Anda membuat keputusan secara buta. Anda mungkin menghentikan versi yang masih memiliki 30% pengguna aktif, atau terus mendukung versi yang hanya digunakan 2%.

Jaga Backend Tetap Kompatibel

Pendekatan standarnya adalah kompatibilitas mundur. Saat Anda mengubah endpoint, jangan langsung hapus format respons lama. Tambahkan field baru di samping yang lama, atau buat versi endpoint API Anda secara eksplisit.

Misalnya, alih-alih memodifikasi /api/orders, buat /api/v2/orders dan pertahankan /api/v1/orders tetap berjalan. Aplikasi terbaru Anda berbicara dengan v2, aplikasi lama tetap menggunakan v1. Ini memberi pengguna waktu untuk meningkatkan versi tanpa merusak pengalaman mereka.

Namun kompatibilitas mundur memiliki batas. Anda tidak bisa mempertahankan lima versi dari setiap endpoint selamanya. Biayanya bertambah seiring dengan setiap versi yang Anda dukung. Pada titik tertentu, Anda perlu memutus versi lama.

Di sinilah pemantauan versi Anda menjadi penting. Ketika data menunjukkan bahwa versi lama telah turun di bawah ambang batas yang dapat diterima—katakanlah 5% pengguna aktif—Anda dapat mengumumkan penghentian. Kirimkan pemberitahuan dalam aplikasi yang meminta pengguna untuk memperbarui. Beri mereka tenggat waktu. Setelah tanggal itu, endpoint lama berhenti berfungsi.

Paksa Pembaruan Saat Diperlukan

Terkadang Anda tidak bisa menunggu adopsi bertahap. Patch keamanan, perbaikan bug kritis, atau perubahan regulasi mungkin memerlukan pembaruan segera. Dalam kasus tersebut, Anda memerlukan mekanisme untuk memaksa pengguna meningkatkan versi.

Polanya sederhana: backend Anda memeriksa header X-App-Version pada setiap permintaan. Jika versi di bawah ambang batas minimum, backend mengembalikan kode respons atau payload khusus. Aplikasi mendeteksi ini dan menampilkan layar pembaruan wajib. Pengguna tidak dapat melanjutkan sampai mereka mengunduh versi terbaru dari toko.

Ini adalah alat yang kasar. Gunakan dengan hemat. Setiap pembaruan paksa menciptakan gesekan dan berisiko mendapatkan ulasan negatif. Namun saat Anda membutuhkannya, ini lebih baik daripada membiarkan pengguna pada versi yang rentan atau rusak.

Gunakan Remote Config sebagai Jaring Pengaman

Remote config memberi Anda jalan tengah antara kompatibilitas penuh dan pembaruan paksa. Alih-alih mengubah kode, Anda mengubah konfigurasi dari server. Aplikasi mengambil konfigurasi ini secara berkala—URL, timeout, feature toggle, versi endpoint—tanpa memerlukan pembaruan toko.

Begini cara membantu masalah kompatibilitas. Misalkan versi aplikasi terbaru Anda memiliki bug yang hanya muncul dengan endpoint backend tertentu. Anda tidak bisa memperbaiki aplikasi dengan cepat karena peninjauan toko memakan waktu berhari-hari. Namun Anda dapat mengubah remote config untuk mengarahkan versi aplikasi itu ke endpoint lama yang stabil. Bug itu hilang tanpa satu baris kode pun diubah.

Remote config juga membantu selama peluncuran bertahap. Jika Anda melihat bahwa pengguna pada versi 4.2 mengalami crash pada fitur baru, Anda dapat menonaktifkan fitur itu hanya untuk versi 4.2, sementara tetap aktif untuk versi 4.3. Konfigurasi sadar versi, sehingga setiap versi aplikasi mendapatkan perilaku yang bisa ditanganinya.

Berikut ini contoh payload remote config yang sadar versi:

{
  "config": {
    "new_checkout_flow": {
      "enabled": true,
      "disabled_versions": ["4.2.0", "4.2.1"]
    },
    "api_base_url": "https://api.example.com/v2",
    "legacy_api_base_url": "https://api.example.com/v1",
    "timeout_ms": 10000
  },
  "flags": {
    "dark_mode": true,
    "experimental_search": false
  }
}

Aplikasi membaca daftar disabled_versions dan melewati alur checkout baru untuk versi 4.2.0 dan 4.2.1, kembali ke alur lama. Tidak perlu pembaruan aplikasi.

Feature Flags untuk Pemulihan Darurat

Feature flags bekerja dengan cara yang sama tetapi berfokus pada mengaktifkan/menonaktifkan fungsionalitas daripada nilai konfigurasi. Ketika fitur baru menyebabkan masalah di produksi, Anda mematikan flag dari dasbor Anda. Fitur itu menghilang dari aplikasi. Pengguna tidak melihatnya, tidak crash karenanya, dan tidak mengeluh tentangnya.

Keuntungan dibandingkan remote config adalah granularitas. Anda dapat menargetkan segmen pengguna tertentu, wilayah, atau versi aplikasi. Anda dapat meningkatkan secara bertahap. Anda dapat melakukan pengujian A/B. Dan ketika sesuatu salah, Anda dapat mematikan fitur secara instan tanpa rilis baru.

Feature flags sangat berharga untuk mobile karena mereka memisahkan deployment dari rilis. Anda dapat mengirimkan kode yang tersembunyi di balik flag, mengaktifkannya saat Anda siap, dan menonaktifkannya jika keadaan memburuk. Aplikasi tidak perlu berubah. Server flag menangani semuanya.

Daftar Periksa Praktis untuk Manajemen Versi

  • Kirimkan versi aplikasi dengan setiap permintaan melalui header kustom
  • Bangun dasbor yang menunjukkan distribusi versi secara real-time
  • Pertahankan endpoint API lama tetap berjalan hingga versi lama turun di bawah 5% penggunaan
  • Tetapkan versi minimum yang didukung dan tegakkan dengan mekanisme paksa-perbarui
  • Terapkan remote config untuk perubahan perilaku sisi server tanpa pembaruan aplikasi
  • Gunakan feature flags untuk menonaktifkan fitur bermasalah secara instan
  • Umumkan tenggat waktu penghentian melalui pemberitahuan dalam aplikasi sebelum memotong dukungan

Intisari

Pengiriman aplikasi mobile tidak berakhir ketika build ditandatangani dan diunggah ke toko. Itu berakhir ketika setiap pengguna berada pada versi yang berfungsi dengan backend Anda saat ini. Itu bisa memakan waktu berminggu-minggu atau berbulan-bulan. Selama waktu itu, backend Anda akan berubah, dan setiap perubahan berpotensi merusak bagi seseorang yang masih menjalankan versi lama.

Pantau distribusi versi Anda. Pertahankan kompatibilitas mundur selama itu praktis. Gunakan remote config dan feature flags untuk mengulur waktu ketika sesuatu salah. Dan ketika Anda harus memaksa pembaruan, lakukan dengan sengaja, bukan reaktif.

Tujuannya bukan untuk menghilangkan fragmentasi versi—itu tidak mungkin. Tujuannya adalah untuk mengetahui apa yang beredar, menjaganya tetap berfungsi, dan memiliki rencana ketika tidak berfungsi.