Pluton.id
Catatan

Mengapa Kami Menghapus Lapisan Sinkronisasi Offline dari Aplikasi Point-of-Sale

Kami menghapus sinkronisasi offline dari aplikasi point-of-sale Flutter karena penanganan konfliknya dan risiko operasionalnya lebih besar daripada nilai yang diberikannya.


Keputusan ini tampak seperti langkah mundur pada awalnya

Kami menghapus lapisan sinkronisasi offline dari aplikasi point-of-sale Flutter yang digunakan oleh sekitar seribu orang.

Keputusan itu dapat terdengar seperti menjauh dari ketahanan sistem. Dalam perangkat lunak ritel, naluri yang umum adalah offline-first: tetap berjualan saat koneksi hilang, simpan setiap perubahan secara lokal, lalu sinkronkan kemudian.

Kami setuju dengan tujuannya. Seorang kasir tidak seharusnya kehilangan pekerjaan hanya karena koneksi tidak stabil. Namun, setelah meninjau cara aplikasi benar-benar menangani penjualan, stok, pembayaran, retur, dan pemulihan perangkat, kami menyimpulkan bahwa sistem sinkronisasi offline untuk tujuan umum menciptakan risiko yang lebih besar daripada risiko yang dihilangkannya.

Keputusan kami bukan untuk membuat aplikasi mengabaikan kegagalan jaringan. Kami mengganti sinkronisasi yang luas dan tertunda dengan serangkaian aturan penanganan kegagalan yang lebih kecil dan lebih eksplisit.

Tanggung jawab lapisan sinkronisasi

Aplikasi menyimpan data operasional secara lokal dan kemudian merekonsiliasikannya dengan server. Secara teori, hal ini memungkinkan point of sale terus bekerja saat terputus dari jaringan.

Dalam praktiknya, lapisan tersebut harus menjawab pertanyaan yang sulit:

  • Versi stok mana yang benar ketika beberapa toko menjual barang yang sama?
  • Apa yang harus terjadi ketika retur dicatat pada satu perangkat sebelum penjualan asalnya tersinkronkan dari perangkat lain?
  • Apakah pembayaran yang selesai secara lokal dapat dicoba ulang dengan aman setelah aplikasi dimulai ulang?
  • Rekaman mana yang boleh diubah setelah dikirim?
  • Bagaimana aplikasi pulih ketika data lokal, data server, dan penyedia pembayaran masing-masing melaporkan status yang berbeda?

Ini bukan hanya pertanyaan tentang sinkronisasi mobile. Ini adalah pertanyaan tentang aturan bisnis, dan aturannya berbeda untuk setiap operasi.

Memperlakukan semua rekaman sebagai dokumen yang dapat disinkronkan membuat implementasinya tampak seragam. Namun, pendekatan itu juga menyembunyikan fakta bahwa penjualan, penyesuaian stok, percobaan pembayaran, dan pembaruan produk memiliki kebutuhan konsistensi yang sangat berbeda.

Offline-first tidak otomatis lebih aman

Offline-first berguna ketika perangkat lokal dapat mengambil keputusan yang valid secara mandiri selama jangka waktu yang berarti.

Pada alur point-of-sale ini, kemandirian tersebut terbatas. Server tetap menjadi otoritas untuk informasi penting seperti data produk aktif, aturan harga, pergerakan stok, status transaksi, dan izin. Perangkat dapat menyimpan interaksi pelanggan secara lokal, tetapi tidak selalu dapat menentukan apakah menyelesaikan interaksi tersebut masih valid.

Kasus sulitnya bukan periode offline yang bersih. Kasus sulitnya adalah kegagalan parsial:

  • Permintaan mencapai server, tetapi perangkat tidak menerima respons.
  • Terminal pembayaran melaporkan keberhasilan ketika aplikasi sedang dimulai ulang.
  • Penjualan masuk ke antrean lokal ketika informasi produk atau stok berubah di tempat lain.
  • Operator mencoba ulang tindakan karena layar tampak macet.
  • Dua perangkat melanjutkan pekerjaan dengan asumsi berbeda lalu terhubung kembali di kemudian hari.

Lapisan sinkronisasi di latar belakang dapat membuat kasus-kasus ini tidak terlalu terlihat oleh pengguna, tetapi tidak dapat menghilangkan ambiguitasnya. Pada akhirnya, sistem tetap harus memutuskan apakah akan mencoba ulang, menggabungkan, menolak, atau meminta peninjauan.

Kami memilih untuk membuat keputusan tersebut eksplisit saat operasi dilakukan, alih-alih menundanya ke dalam antrean sinkronisasi yang dapat gagal jauh setelahnya.

Biaya terbesar ada pada kasus tepi

Aplikasi Flutter itu sendiri bukan alasan kami menghapus sinkronisasi. Flutter memberi kami cara yang praktis untuk membangun antarmuka perangkat dan pengelolaan state lokal. Masalahnya adalah besarnya state bisnis yang kami letakkan di balik abstraksi offline.

Setiap fitur baru harus dievaluasi setidaknya dalam dua lini masa:

  1. Apa yang terjadi ketika perangkat online dan menerima respons server secara langsung?
  2. Apa yang terjadi ketika perangkat membuat perubahan lokal, menunggu, mencoba ulang, dimulai ulang, dan tersinkronkan kemudian?

Lini masa kedua memengaruhi lebih dari aplikasi. Ini memengaruhi desain API, prosedur dukungan, jejak audit, pelaporan, dan pemahaman operator terhadap status transaksi.

Hasilnya adalah sistem tempat sebuah fitur tampak selesai pada jalur normal, tetapi masih memerlukan pekerjaan besar untuk write yang mengantre, penyelesaian konflik, urutan pemutaran ulang, migrasi rekaman yang disimpan secara lokal, dan pemulihan setelah sinkronisasi terputus.

Kami menghabiskan kompleksitas untuk mempertahankan kemampuan offline yang luas tanpa dapat menjamin bahwa semua tindakan offline aman untuk diselesaikan.

Pembayaran memperjelas batasnya

Pembayaran adalah contoh paling jelas mengapa sinkronisasi generik tidak cocok.

Operasi pembayaran tidak boleh diduplikasi hanya karena klien mencoba ulang setelah waktu habis. Pada saat yang sama, pengguna membutuhkan jawaban yang jelas tentang apakah pembayaran berhasil.

Solusinya bukan sekadar "sinkronkan rekaman pembayaran nanti." Server dan integrasi pembayaran memerlukan strategi idempotensi.

Kami memberikan kunci idempotensi pada sebuah operasi sebelum mengirimkannya. Jika klien perlu mencoba ulang, klien mencoba ulang dengan kunci yang sama, bukan membuat operasi baru.

POST /sales
Idempotency-Key: 4e58c8a2-transaction-attempt

Server kemudian dapat mengenali bahwa permintaan yang berulang mewakili maksud yang sama. Server dapat mengembalikan hasil yang ada, melanjutkan pemrosesan dengan aman, atau melaporkan status akhir yang telah diketahui.

Idempotensi tidak menyelesaikan semua masalah pembayaran. Idempotensi tidak mengungkap hasil yang tidak diterima oleh sistem mana pun, dan tidak menggantikan rekonsiliasi dengan penyedia pembayaran. Namun, idempotensi memberi arti yang jelas pada percobaan ulang, yang lebih berguna daripada berharap antrean lokal akan diputar ulang tepat satu kali.

Yang tetap kami pertahankan

Menghapus lapisan sinkronisasi offline tidak berarti menghapus semua penyimpanan lokal atau membuat setiap kegagalan jaringan sementara menjadi fatal.

Kami mempertahankan state lokal ketika tujuannya jelas dan terbatas:

  • Data keranjang dan state formulir yang sedang dikerjakan
  • Data baca yang di-cache dengan ekspektasi kesegaran yang jelas
  • Catatan percobaan operasi yang diperlukan untuk mencoba ulang dan pemulihan
  • Diagnostik lokal yang membantu kami memahami permintaan yang gagal

Kami tidak memperlakukan state lokal tersebut sebagai replika mandiri dari basis data bisnis.

Untuk operasi write, kami beralih ke model permintaan dengan penanganan status yang eksplisit. Aplikasi mengirim operasi, menyimpan informasi yang cukup untuk pulih dari gangguan, dan menanyakan status operasi kepada server ketika hasilnya tidak pasti.

Hal ini mengubah pengalaman pengguna secara penting. Alih-alih diam-diam menampilkan penjualan sebagai selesai dan berharap sinkronisasi berhasil kemudian, aplikasi dapat menampilkan bahwa penjualan sedang diproses, selesai, ditolak, atau perlu ditinjau.

Terkadang ini kurang nyaman daripada konfirmasi offline yang optimistis. Namun, ini juga lebih jujur ketika sistem tidak dapat mengonfirmasi hasilnya dengan aman.

Trade-off yang kami terima

Kami menerima bahwa beberapa alur kerja memerlukan konektivitas agar dapat diselesaikan dengan yakin.

Ketika perangkat tidak dapat mencapai layanan, aplikasi dapat menyimpan pekerjaan operator jika sesuai, menjelaskan batasan saat ini, dan mengizinkan percobaan ulang yang terkendali. Aplikasi tidak seharusnya menyiratkan bahwa transaksi penting bagi bisnis sudah final ketika finalitasnya tidak dapat diverifikasi.

Ini adalah trade-off produk dan operasional, bukan sekadar preferensi arsitektur. Point of sale yang sepenuhnya offline dapat menjadi pilihan tepat ketika toko memiliki koneksi yang tidak andal dan bisnis dapat menerima otoritas lokal, rekonsiliasi belakangan, serta pengecualian sesekali.

Dalam kasus kami, aplikasi memerlukan koordinasi pusat yang lebih kuat daripada yang dapat disediakan secara aman oleh model sinkronisasi. Kami memilih untuk memusatkan koordinasi itu di server dan membuat ketidakpastian jaringan terlihat di klien.

Yang akan kami putuskan lebih awal lain kali

Jika kami memulai kembali, kami akan mengajukan pertanyaan ini sebelum memilih arsitektur offline-first:

  • Tindakan apa yang valid tanpa data server terkini?
  • Tindakan apa yang menimbulkan konsekuensi keuangan, inventaris, atau kepatuhan?
  • Apakah perubahan lokal yang bertentangan dapat digabungkan dengan aturan, atau memerlukan peninjauan manusia?
  • Apa sumber kebenaran untuk setiap operasi?
  • Apakah setiap write dapat dicoba ulang dengan aman melalui idempotensi?
  • Apa yang harus dilihat operator ketika hasil akhirnya tidak diketahui?

Kami juga akan menghindari penyebutan dukungan offline sebagai satu fitur tunggal. Membaca data cache, menyimpan keranjang, mencoba ulang permintaan, beroperasi saat gangguan, dan menyinkronkan basis data lokal adalah kemampuan yang berbeda. Masing-masing memerlukan desain dan model kegagalannya sendiri.

Pelajarannya

Kami tidak menghapus sinkronisasi karena penggunaan offline tidak penting. Kami menghapusnya karena lapisan sinkronisasi mengambil tanggung jawab atas keputusan bisnis yang tidak dapat dibuatnya secara andal.

Untuk aplikasi point-of-sale Flutter ini, model yang lebih kecil lebih mudah kami pahami: state UI lokal, operasi server yang eksplisit, informasi percobaan ulang yang tahan gangguan, dan idempotensi untuk permintaan yang mungkin diulang.

Model tersebut tetap membutuhkan desain backend yang cermat dan status operasional yang jelas. Namun, model ini memberi kami konflik tersembunyi yang lebih sedikit, jalur kegagalan yang lebih mudah dipahami, dan batas yang lebih jelas antara apa yang diketahui perangkat dengan apa yang hanya dapat dikonfirmasi oleh sistem pusat.