Panduan arsitektur aplikasi

Arsitektur aplikasi adalah fondasi aplikasi Android berkualitas tinggi. Arsitektur yang ditentukan dengan baik memungkinkan Anda membuat aplikasi yang skalabel dan mudah dikelola yang dapat beradaptasi dengan ekosistem perangkat Android yang terus berkembang, termasuk ponsel, tablet, perangkat foldable, perangkat ChromeOS, layar mobil, dan XR.

Komposisi aplikasi

Aplikasi Android standar terdiri dari beberapa komponen aplikasi, seperti layanan, penyedia konten, dan penerima siaran. Anda mendeklarasikan komponen ini di manifes aplikasi.

Antarmuka pengguna aplikasi juga merupakan komponen. Sebelumnya, UI dibuat menggunakan beberapa aktivitas. Namun, aplikasi modern menggunakan arsitektur satu aktivitas. Activity tunggal berfungsi sebagai penampung untuk layar yang diterapkan sebagai fragmen atau tujuan Jetpack Compose.

Beberapa faktor bentuk

Aplikasi dapat berjalan di berbagai faktor bentuk, termasuk tidak hanya ponsel, tetapi juga tablet, perangkat foldable, perangkat ChromeOS, dan lainnya. Aplikasi tidak dapat mengasumsikan orientasi potret atau lanskap. Perubahan konfigurasi, seperti rotasi perangkat atau melipat dan membentangkan perangkat foldable, memaksa aplikasi Anda untuk merekomposisi UI-nya, yang memengaruhi data dan status aplikasi.

Kendala resource

Perangkat seluler—bahkan perangkat layar besar—memiliki keterbatasan resource, jadi sewaktu-waktu, sistem operasi mungkin menghentikan beberapa proses aplikasi agar aplikasi lain dapat berjalan.

Kondisi peluncuran variabel

Dalam lingkungan dengan keterbatasan resource, komponen aplikasi Anda dapat diluncurkan satu per satu dan tidak berurutan; terlebih lagi, sistem operasi atau pengguna dapat merusak proses ini kapan saja. Oleh karena itu, jangan simpan data atau status aplikasi apa pun di komponen aplikasi Anda. Komponen aplikasi Anda harus bersifat mandiri, terpisah satu sama lain.

Prinsip arsitektur umum

Jika Anda tidak dapat menggunakan komponen aplikasi untuk menyimpan data dan status aplikasi, bagaimana sebaiknya Anda mendesain aplikasi?

Seiring bertambahnya ukuran aplikasi Android, penting untuk menentukan arsitektur yang memungkinkan aplikasi melakukan penskalaan. Arsitektur aplikasi yang didesain dengan baik menentukan batas antara bagian aplikasi dan tanggung jawab yang harus dimiliki setiap bagian.

Pemisahan fokus

Desain arsitektur aplikasi Anda untuk mengikuti beberapa prinsip khusus.

Prinsip yang paling penting adalah pemisahan fokus. Menulis semua kode Anda dalam sebuah Activity atau Fragment adalah sebuah kesalahan umum.

Peran utama Activity atau Fragment adalah menghosting UI aplikasi Anda. Sistem operasi Android mengontrol siklus prosesnya, sering kali menghancurkan dan membuatnya ulang sebagai respons terhadap tindakan pengguna seperti rotasi layar atau peristiwa sistem seperti memori rendah.

Sifat sementara ini membuatnya tidak cocok untuk menyimpan data atau status aplikasi. Jika Anda menyimpan data di Activity atau Fragment, data tersebut akan hilang saat komponen dibuat ulang. Untuk memastikan persistensi data dan memberikan pengalaman pengguna yang stabil, jangan mempercayakan status ke komponen UI ini.

Tata letak adaptif

Aplikasi Anda harus menangani perubahan konfigurasi dengan baik, seperti perubahan orientasi perangkat atau perubahan ukuran jendela aplikasi. Terapkan tata letak kanonis adaptif untuk memberikan pengalaman pengguna yang optimal di berbagai faktor bentuk.

Menjalankan UI dari model data

Prinsip penting lainnya adalah sebaiknya Anda menjalankan UI dari model data, terutama model persisten. Model data mewakili data aplikasi dan tidak bergantung pada elemen UI dan komponen lainnya dalam aplikasi Anda. Artinya, modul tersebut tidak terkait dengan UI dan siklus proses komponen aplikasi, tetapi akan tetap dihancurkan saat OS menghapus proses aplikasi dari memori.

Model persisten ideal karena alasan berikut:

  • Pengguna tidak kehilangan data jika OS Android menghancurkan aplikasi Anda untuk mengosongkan resource.

  • Aplikasi Anda tetap berfungsi saat koneksi jaringan terputus-putus atau tidak tersedia.

Dasarkan arsitektur aplikasi Anda pada class model data untuk membuat aplikasi Anda andal dan mudah diuji.

Sumber ketepatan tunggal

Saat jenis data baru ditentukan di aplikasi, Anda harus menetapkan sumber kebenaran tunggal (SSOT) ke jenis data tersebut. SST adalah pemilik data tersebut, dan hanya SST yang dapat mengubah atau memutasikannya. Untuk melakukannya, SST mengekspos data menggunakan jenis yang tidak dapat diubah. Jika perlu memodifikasi data tersebut, SST mengekspos fungsi atau menerima peristiwa yang dapat dipanggil oleh jenis lain.

Pola ini memiliki beberapa manfaat:

  • Memusatkan semua perubahan pada jenis data tertentu di satu tempat
  • Melindungi data sehingga jenis lain tidak dapat mengutak-atiknya
  • Membuat perubahan pada data lebih mudah dilacak, sehingga bug lebih mudah ditemukan

Dalam aplikasi yang mengutamakan versi offline, sumber tepercaya untuk data aplikasi biasanya adalah database. Dalam beberapa kasus lain, sumber tepercaya dapat berupa ViewModel.

Aliran data searah

Prinsip satu sumber tepercaya sering digunakan dengan pola aliran data searah (UDF). Di UDF, status hanya mengalir dalam satu arah, biasanya dari komponen induk ke komponen turunan. Peristiwa yang mengubah aliran data ke arah yang berlawanan.

Di Android, status atau data biasanya mengalir dari jenis hierarki yang cakupannya lebih tinggi ke hierarki dengan cakupan yang lebih rendah. Peristiwa biasanya dipicu dari jenis cakupan yang lebih rendah hingga mencapai SST untuk jenis data yang sesuai. Misalnya, data aplikasi biasanya mengalir dari sumber data ke UI. Peristiwa pengguna seperti penekanan tombol mengalir dari UI ke SST tempat data aplikasi diubah dan diekspos dalam jenis yang tidak dapat diubah.

Pola ini mempertahankan konsistensi data dengan lebih baik, tidak terlalu rentan terhadap error, lebih mudah di-debug, dan memberikan semua manfaat pola SSOT.

Dengan mempertimbangkan prinsip arsitektur umum, setiap aplikasi harus memiliki setidaknya dua lapisan:

  • Lapisan UI: Menampilkan data aplikasi di layar
  • Lapisan data: Berisi logika bisnis aplikasi Anda dan mengekspos data aplikasi

Anda dapat menambahkan lapisan tambahan yang disebut lapisan domain untuk menyederhanakan dan menggunakan kembali interaksi antara lapisan UI dan data.

Dalam arsitektur aplikasi standar, lapisan UI mengambil data aplikasi     dari lapisan data atau dari lapisan domain opsional, yang berada di antara     lapisan UI dan lapisan data.
Gambar 1. Diagram arsitektur aplikasi standar.

Arsitektur aplikasi modern

Arsitektur aplikasi Android modern menggunakan teknik berikut (antara lain):

  • Arsitektur adaptif dan berlapis
  • Aliran data searah (UDF) di semua lapisan aplikasi
  • Lapisan UI dengan holder status untuk mengelola kompleksitas UI
  • Coroutine dan flow
  • Praktik terbaik injeksi dependensi

Untuk mengetahui informasi selengkapnya, lihat Rekomendasi untuk arsitektur Android.

Lapisan UI

Peran lapisan UI (atau lapisan presentasi) adalah menampilkan data aplikasi di layar. Setiap kali data berubah, baik karena interaksi pengguna (seperti menekan tombol) atau input eksternal (seperti respons jaringan), UI harus diupdate untuk mencerminkan perubahan tersebut.

Lapisan UI terdiri dari dua jenis konstruksi:

  • Elemen UI yang merender data di layar. Anda membuat elemen ini menggunakan fungsi Jetpack Compose untuk mendukung tata letak adaptif.
  • Holder status (seperti ViewModel) yang menyimpan data, mengeksposnya ke UI, dan menangani logika
Dalam arsitektur standar, elemen UI lapisan UI bergantung pada holder status, yang kemudian bergantung pada class dari lapisan data atau lapisan domain opsional.
Gambar 2. Peran lapisan UI dalam arsitektur aplikasi.

Untuk UI adaptif, holder status seperti objek ViewModel mengekspos status UI yang beradaptasi dengan class ukuran jendela yang berbeda. Anda dapat menggunakan currentWindowAdaptiveInfo() untuk mendapatkan status UI ini. Komponen seperti NavigationSuiteScaffold kemudian dapat menggunakan informasi ini untuk otomatis beralih di antara pola navigasi yang berbeda (misalnya, NavigationBar, NavigationRail, atau NavigationDrawer) berdasarkan ruang layar yang tersedia.

Untuk mempelajari lebih lanjut, lihat halaman lapisan UI.

Lapisan data

Lapisan data aplikasi berisi logika bisnis. Logika bisnis adalah yang memberikan nilai bagi aplikasi Anda—logika bisnis terdiri dari aturan yang menentukan cara aplikasi Anda membuat, menyimpan, dan mengubah data.

Lapisan data terdiri dari repositori yang masing-masing dapat berisi nol hingga banyak sumber data. Anda harus membuat class repositori untuk setiap jenis data yang ditangani di aplikasi Anda. Misalnya, Anda dapat membuat class MoviesRepository untuk data yang terkait dengan film atau class PaymentsRepository untuk data yang terkait dengan pembayaran.

Dalam arsitektur standar, repositori lapisan data menyediakan data      ke seluruh aplikasi dan bergantung pada sumber data.
Gambar 3. Peran lapisan data dalam arsitektur aplikasi.

Class repositori bertanggung jawab atas hal berikut:

  • Mengekspos data ke seluruh aplikasi
  • Memusatkan perubahan pada data
  • Menyelesaikan konflik antara beberapa sumber data
  • Mengabstraksi sumber data dari bagian aplikasi lainnya
  • Berisi logika bisnis

Setiap class sumber data harus memiliki tanggung jawab untuk menangani hanya satu sumber data, yang dapat berupa file, sumber jaringan, atau database lokal. Class sumber data adalah jembatan antara aplikasi dan sistem untuk operasi data.

Untuk mempelajari lebih lanjut, lihat halaman lapisan data.

Lapisan domain

Lapisan domain adalah lapisan opsional di antara lapisan UI dan data.

Lapisan domain bertanggung jawab untuk mengenkapsulasi logika bisnis yang kompleks atau logika bisnis yang lebih sederhana yang digunakan kembali oleh beberapa model tampilan. Lapisan domain bersifat opsional karena tidak semua aplikasi memiliki persyaratan ini. Gunakan hanya jika diperlukan, misalnya, untuk menangani kompleksitas atau mendukung penggunaan kembali.

Jika disertakan, lapisan domain opsional memberikan dependensi ke     lapisan UI dan bergantung pada lapisan data.
Gambar 4. Peran lapisan domain dalam arsitektur aplikasi.

Class di lapisan domain biasanya disebut kasus penggunaan atau pemicu interaksi. Setiap kasus penggunaan harus memiliki tanggung jawab atas satu fungsi. Misalnya, aplikasi Anda dapat memiliki class GetTimeZoneUseCase jika beberapa model tampilan bergantung pada zona waktu untuk menampilkan pesan yang tepat di layar.

Untuk mempelajari lebih lanjut, lihat halaman lapisan domain.

Mengelola dependensi antarkomponen

Class di aplikasi Anda bergantung pada class lain agar berfungsi dengan baik. Anda dapat menggunakan salah satu pola desain berikut untuk mengumpulkan dependensi class tertentu:

  • Injeksi dependensi (DI): Injeksi dependensi memungkinkan class untuk menentukan dependensi tanpa perlu menyusunnya. Saat runtime, class lain bertanggung jawab menyediakan dependensi ini.
  • Pencari lokasi: Pola pencari lokasi menyediakan registry tempat class dapat memperoleh dependensinya, bukan menyusunnya.

Kedua pola ini memungkinkan Anda menskalakan kode karena keduanya memberikan pola yang jelas untuk mengelola dependensi tanpa menduplikasi kode atau menambahkan kompleksitas. Pola ini juga memungkinkan Anda beralih dengan cepat antara implementasi pengujian dan produksi.

Praktik terbaik umum

Pemrograman adalah bidang kreatif, begitu juga pembuatan aplikasi Android. Ada banyak cara untuk menyelesaikan masalah; Anda dapat mengomunikasikan data antara beberapa aktivitas atau fragmen, mengambil data jarak jauh dan mempertahankannya secara lokal untuk mode offline, atau menangani sejumlah skenario umum lainnya yang ditemukan oleh aplikasi yang tidak umum.

Meskipun rekomendasi berikut tidak wajib diikuti, dalam sebagian besar kasus, mengikuti rekomendasi berikut akan membuat codebase Anda lebih tangguh, mudah diuji, dan mudah dipelihara.

Jangan simpan data di komponen aplikasi.

Hindari menetapkan titik masuk aplikasi Anda—seperti aktivitas, layanan, dan penerima siaran—sebagai sumber data. Titik entri hanya boleh berkoordinasi dengan komponen lain untuk mengambil subkumpulan data yang relevan dengan titik entri tersebut. Setiap komponen aplikasi memiliki masa aktif yang singkat, bergantung pada interaksi pengguna dengan perangkatnya dan kapasitas sistem.

Mengurangi dependensi di class Android.

Komponen aplikasi Anda harus menjadi satu-satunya class yang mengandalkan API SDK framework Android seperti Context atau Toast. Mengabstraksi class lain di aplikasi Anda dari komponen aplikasi membantu memudahkan pengujian dan mengurangi penggabungan dalam aplikasi Anda.

Tentukan batasan tanggung jawab yang jelas antara modul di aplikasi Anda.

Jangan menyebarkan kode yang memuat data dari jaringan di beberapa class atau paket dalam codebase Anda. Demikian pula, jangan menetapkan beberapa tanggung jawab yang tidak terkait, seperti data caching dan data binding, dalam class yang sama. Mengikuti arsitektur aplikasi yang direkomendasikan akan membantu.

Ekspos sesedikit mungkin dari setiap modul.

Jangan membuat pintasan yang memperlihatkan detail implementasi internal. Ini mungkin menghemat waktu dalam jangka pendek, tetapi Anda mungkin akan menanggung utang teknis berkali-kali lipat seiring berkembangnya codebase Anda.

Berfokus pada inti unik aplikasi Anda agar lebih menarik dari aplikasi lain.

Jangan memulai dari awal dengan menuliskan kode boilerplate yang sama berulang-ulang. Sebaliknya, fokuskan waktu dan energi Anda pada hal yang membuat aplikasi Anda unik. Biarkan library Jetpack dan library rekomendasi lainnya menangani boilerplate berulang.

Gunakan tata letak kanonis dan pola desain aplikasi.

Library Jetpack Compose menyediakan API yang andal untuk membuat antarmuka pengguna adaptif. Gunakan tata letak kanonis di aplikasi Anda untuk mengoptimalkan pengalaman pengguna di berbagai faktor bentuk dan ukuran layar. Tinjau galeri pola desain aplikasi untuk memilih tata letak yang paling sesuai dengan kasus penggunaan Anda.

Mempertahankan status UI saat terjadi perubahan konfigurasi.

Saat mendesain tata letak adaptif, pertahankan status UI di seluruh perubahan konfigurasi seperti pengubahan ukuran tampilan, pelipatan, dan perubahan orientasi. Arsitektur Anda harus memverifikasi bahwa status pengguna saat ini dipertahankan, sehingga memberikan pengalaman yang lancar.

Mendesain komponen UI yang dapat digunakan kembali dan dapat dikomposisikan.

Buat komponen UI yang dapat digunakan kembali dan dapat dikomposisikan untuk mendukung desain adaptif. Hal ini memungkinkan Anda menggabungkan dan mengatur ulang komponen agar sesuai dengan berbagai ukuran dan posisi layar tanpa melakukan refaktor yang signifikan.

Pertimbangkan cara untuk menjadikan setiap bagian aplikasi mudah diuji secara terpisah.

API yang didefinisikan dengan baik untuk mengambil data dari jaringan akan mempermudah pengujian modul yang mempertahankan data tersebut di database lokal. Sebaliknya, jika Anda mencampur logika dari kedua fungsi ini di satu tempat, atau mendistribusikan kode jaringan Anda di seluruh codebase, pengujian akan menjadi jauh lebih sulit, bahkan mustahil, dilakukan.

Jenis bertanggung jawab atas kebijakan serentaknya.

Jika suatu jenis menjalankan tugas pemblokiran yang berjalan lama, jenis tersebut harus bertanggung jawab untuk memindahkan komputasi tersebut ke thread yang tepat. Jenis mengetahui jenis komputasi yang dilakukannya dan di thread mana komputasi tersebut harus dieksekusi. Jenis harus main‑safe, yang berarti jenis tersebut aman untuk dipanggil dari thread utama tanpa memblokirnya.

Pertahankan sebanyak mungkin data yang relevan dan baru.

Dengan demikian, pengguna dapat menikmati fungsionalitas aplikasi Anda meski perangkat mereka berada dalam mode offline. Ingat, tidak semua pengguna Anda menyukai konektivitas berkecepatan tinggi dan konstan, dan meskipun mereka menyukainya, mereka bisa mendapatkan penerimaan sinyal yang buruk di tempat yang ramai.

Manfaat arsitektur

Menerapkan arsitektur yang baik di aplikasi Anda akan memberikan banyak manfaat bagi tim project dan tim engineering:

  • Meningkatkan pemeliharaan, kualitas, dan keandalan aplikasi secara keseluruhan.
  • Memungkinkan aplikasi melakukan penskalaan. Lebih banyak orang dan lebih banyak tim dapat berkontribusi pada codebase yang sama dengan sedikit konflik kode.
  • Membantu proses orientasi. Saat arsitektur memberikan konsistensi pada project Anda, anggota tim baru dapat langsung bekerja dengan lebih cepat dan lebih efisien dalam waktu singkat.
  • Lebih mudah diuji. Arsitektur yang baik mendorong jenis yang lebih sederhana yang umumnya lebih mudah diuji.
  • Bug dapat diselidiki secara metodis dengan proses yang didefinisikan dengan baik.

Berinvestasi pada arsitektur juga berdampak langsung pada pengguna. Mereka mendapatkan manfaat dari aplikasi yang lebih stabil dan lebih banyak fitur karena tim engineering yang lebih produktif. Namun, arsitektur juga memerlukan investasi waktu di awal. Untuk membantu Anda menentukan waktu ini bagi organisasi, lihat studi kasus yang berisi kisah sukses perusahaan lain saat memiliki arsitektur yang baik di aplikasinya.

Contoh

Contoh berikut menunjukkan arsitektur aplikasi yang baik: