Ketika aplikasi Anda dibangun pada arsitektur multi-modul, Anda harus mencurahkan banyak waktu untuk memastikan bahwa semua komunikasi antara modul ditulis dengan benar dalam kode. Setengah dari pekerjaan ini dapat dipercayakan pada kerangka Dagger 2. Kepala grup Yandex.Map untuk Android Vladimir Tagakov
Noxa berbicara tentang pro dan kontra multi-
modularitas dan pengaturan modul DI yang nyaman dengan menggunakan Dagger 2.
- Nama saya Vladimir, saya sedang mengembangkan Yandex.Maps dan hari ini saya akan memberi tahu Anda tentang modularitas dan belati kedua.
Saya mengerti bagian terpanjang ketika saya mempelajarinya sendiri, yang tercepat. Bagian kedua, di mana saya duduk selama beberapa minggu, saya akan memberitahu Anda dengan sangat cepat dan ringkas.

Mengapa kami memulai proses yang sulit untuk membagi ke dalam modul di Maps? Kami hanya ingin meningkatkan kecepatan build, semua orang tahu tentang itu.
Poin kedua dari tujuannya adalah untuk mengurangi kait kode. Saya mengambil perlengkapan dari Wikipedia. Ini berarti bahwa kami ingin mengurangi interkoneksi antar modul sehingga modul terpisah dan dapat digunakan di luar aplikasi. Pernyataan awal masalah: proyek Yandex lainnya harus dapat menggunakan bagian dari fungsi Maps persis seperti yang kita lakukan. Dan untuk mengembangkan fungsi ini, kami terlibat dalam pengembangan proyek.
Saya ingin melempar sandal yang terbakar ke arah [k] apt, yang memperlambat kecepatan perakitan. Aku tidak membencinya, tapi aku sangat mencintainya. Dia mengizinkan saya menggunakan Belati.

Kelemahan utama dari proses pemisahan modul adalah, secara paradoksal, penurunan kecepatan perakitan. Terutama di awal, ketika Anda mengeluarkan dua modul pertama, Umum dan beberapa fitur Anda, kecepatan build keseluruhan proyek menurun, tidak peduli bagaimana Anda mencoba. Pada akhirnya, karena semakin sedikit kode yang tersisa di modul utama Anda, kecepatan build akan meningkat. Dan tetap saja, ini tidak berarti bahwa semuanya sangat buruk, ada cara untuk menyiasatinya dan bahkan mendapat untung dari modul pertama.
Kelemahan kedua adalah sulitnya memisahkan kode menjadi modul. Siapa yang mencoba, tahu bahwa Anda mulai menarik semacam dependensi, beberapa klasik, dan semuanya berakhir ketika Anda menyalin seluruh modul utama Anda ke modul lain dan memulai dari awal. Karena itu, Anda perlu memahami dengan jelas saat ketika Anda perlu menghentikan dan memutuskan koneksi menggunakan semacam abstraksi. Kerugiannya adalah lebih banyak abstraksi. Lebih banyak abstraksi - desain yang lebih kompleks - lebih banyak abstraksi.
Sulit untuk menambahkan modul Gradle baru. Mengapa Misalnya, seorang pengembang datang, mengambil fitur baru dalam pengembangan, segera melakukannya dengan baik, membuat modul terpisah. Apa masalahnya? Dia harus mengingat semua kode yang tersedia, yang ada di modul utama, sehingga, jika ada, menggunakannya kembali dan memasukkannya ke Common. Karena proses mengeluarkan beberapa modul di Common adalah konstan sampai modul App utama Anda berubah menjadi lapisan tipis.
Modul, modul, modul ... Modul gradasi, modul belati, modul antarmuka mengerikan.

Laporan ini akan terdiri dari tiga bagian: kecil, besar dan kompleks. Pertama, perbedaan antara Implementasi dan API di AGP. Android Gradle Plugin 3.0 telah muncul relatif baru-baru ini. Bagaimana segalanya sebelum dia?

Berikut ini adalah proyek khas pengembang yang sehat, yang terdiri dari tiga modul: modul Aplikasi, yang merupakan modul utama, dirakit dan diinstal dalam aplikasi, dan dua modul Fitur.
Segera bicarakan panah. Ini adalah rasa sakit yang besar, semua orang menarik ke arah yang nyaman baginya untuk menggambar. Bagi saya maksud mereka bahwa dari Core ada panah ke Fitur. Jadi, Fitur tahu tentang Core, bisa menggunakan kelas dari Core. Seperti yang Anda lihat, tidak ada panah antara Core dan App, yang berarti App tampaknya tidak menggunakan Core. Core bukan modul umum, itu adalah, semua orang bergantung padanya, itu terpisah, ada sedikit kode di dalamnya. Meskipun kami tidak akan mempertimbangkannya.
Modul inti kami telah berubah, kami harus mengulanginya entah bagaimana. Kami mengubah kode di dalamnya. Warna kuning - perubahan kode.

Setelah memasang kembali proyek. Jelas bahwa setelah mengubah modul, modul harus dibangun kembali, dikompilasi ulang. Baiklah

Setelah modul Fitur juga dirakit, yang tergantung padanya. Jelas juga, ketergantungannya telah dipasang kembali, dan Anda perlu memperbarui diri. Siapa tahu apa yang berubah di sana.
Dan di sini hal yang paling tidak menyenangkan terjadi. Modul Aplikasi berjalan, meskipun tidak jelas mengapa. Saya tahu pasti bahwa saya tidak menggunakan Core dengan cara apa pun, dan mengapa Aplikasi sedang dibangun tidak jelas. Dan dia sangat besar, karena pada awal jalan, dan ini adalah rasa sakit yang sangat besar.

Selain itu, jika beberapa fitur, banyak modul bergantung pada Core, maka seluruh dunia akan dipasang kembali, itu membutuhkan waktu yang sangat lama.
Mari tingkatkan ke AGP versi baru dan ganti, seperti yang dikatakan manual, semua kompilasi dengan API, dan bukan dengan Implementasi, seperti yang Anda pikirkan. Tidak ada yang berubah. Skema itu identik. Apa cara baru dalam menentukan dependensi Implementasi? Bayangkan skema yang sama hanya menggunakan kata kunci ini, tanpa API? Ini akan terlihat seperti ini.

Di sini dalam implementasinya, terlihat jelas bahwa ada koneksi antara Core dan App. Di sini kita dapat dengan jelas memahami bahwa kita tidak membutuhkannya, kita ingin menyingkirkannya, jadi hapus saja. Segalanya menjadi lebih mudah.

Sekarang hampir semuanya baik, bahkan lebih dari itu. Jika kami mengubah beberapa API di Core, tambahkan kelas baru, metode publik atau paket pribadi baru, maka Core dan Fitur akan dibangun kembali. Jika Anda mengubah implementasi di dalam metode atau menambahkan metode pribadi, maka secara teoritis membangun kembali Fitur tidak boleh terjadi sama sekali, karena tidak ada yang berubah.

Mari kita melangkah lebih jauh. Kebetulan banyak yang bergantung pada Core kami. Core mungkin semacam jaringan atau pemrosesan data pengguna. Karena ini adalah Network, semuanya berubah cukup sering, semuanya dibangun kembali, dan kami mendapat rasa sakit yang sama dari mana kami dengan hati-hati melarikan diri.
Mari kita lihat dua cara untuk mengatasi ini.

Kami hanya dapat mentransfer API dari modul Core kami ke modul terpisah, API-nya, yang kami gunakan. Dan dalam modul terpisah kita dapat mengambil implementasi antarmuka ini.

Anda dapat melihat koneksi di layar. Implan Inti tidak akan tersedia untuk fitur. Artinya, tidak akan ada koneksi antara fitur dan implementasi Core. Dan modul, yang disorot dengan warna kuning, hanya akan menyediakan pabrik yang akan menyediakan semacam implementasi antarmuka Anda yang tidak diketahui siapa pun.
Setelah konversi seperti itu, saya ingin menarik perhatian pada fakta bahwa API Inti, karena fakta bahwa kata kunci API berdiri, akan tersedia untuk semua fitur secara transitif.

Setelah transformasi ini, kami mengubah sesuatu dalam implementasi yang paling sering Anda lakukan, dan hanya modul dengan pabrik yang akan dibangun kembali, ini sangat ringan, kecil, Anda bahkan tidak perlu mempertimbangkan berapa lama.

Pilihan lain tidak selalu berhasil. Sebagai contoh, jika ini semacam Jaringan, maka saya hampir tidak bisa membayangkan bagaimana ini bisa terjadi, tetapi jika ini adalah semacam layar login pengguna, maka mungkin saja.

Kita dapat membuat Sampel, modul root lengkap yang sama dengan App, dan hanya mengumpulkan satu fitur di dalamnya, itu akan sangat cepat, dan dapat dikembangkan dengan cepat secara iteratif. Di akhir presentasi, saya akan menunjukkan kepada Anda berapa lama untuk membangun dan membangun sampel.
Dengan bagian pertama selesai. Modul apa yang ada?

Ada tiga jenis modul. Biasa, tentu saja, harus seringan mungkin, dan seharusnya tidak mengandung fitur apa pun, tetapi hanya fungsionalitas yang digunakan oleh semua orang. Bagi kami di tim kami ini sangat penting. Jika kami menyediakan modul Fitur kami ke aplikasi lain, kami akan memaksa mereka untuk menyeret Common dalam keadaan apa pun. Jika dia sangat gemuk, maka tidak ada yang akan mencintai kita.

Jika Anda memiliki proyek yang lebih kecil, maka dengan Common Anda dapat merasa lebih santai, maka Anda juga tidak harus sangat bersemangat.

Jenis modul berikutnya adalah Standalone. Modul paling umum dan intuitif yang berisi fitur tertentu: semacam layar, semacam skrip pengguna, dan sebagainya. Itu harus independen mungkin, dan untuk itu paling sering Anda dapat membuat Aplikasi sampel dan mengembangkannya di dalamnya. Aplikasi Sampel sangat penting pada awal proses pemisahan, karena semuanya masih berjalan lambat, dan Anda ingin mendapatkan keuntungan secepat mungkin. Pada akhirnya, ketika semuanya dipukuli menjadi modul, Anda dapat membangun kembali semuanya, itu akan cepat. Karena itu tidak akan dibangun kembali.

Modul selebriti. Saya sendiri datang dengan kata itu. Intinya dia sangat terkenal untuk semua orang, dan banyak yang bergantung padanya. Jaringan yang sama. Saya sudah mengatakan, jika Anda sering memasang kembali, bagaimana Anda bisa menghindari kenyataan bahwa semuanya dipasang kembali dari Anda. Ada cara lain yang dapat digunakan untuk proyek-proyek kecil yang tidak sepadan dengan tujuan memberikan semuanya sebagai kecanduan yang terpisah, artefak yang terpisah.

Seperti apa bentuknya? Kami ulangi yang mengeluarkan API dari Selebriti, menghapus implementasinya, dan sekarang perhatikan tangan Anda, perhatikan panah dari Fitur hingga Selebriti. Ini sedang terjadi API modul Anda jatuh ke Common, implementasinya tetap di dalamnya, dan pabrik yang menyediakan implementasi API ini muncul di modul utama Anda. Jika seseorang menyaksikan Mobius, maka Denis Neklyudov membicarakannya. Skema yang sangat mirip.
Kami menggunakan Dagger dalam proyek ini, kami menyukainya, dan kami ingin mendapatkan hasil maksimal dari manfaat ini dalam konteks modul yang berbeda.

Kami ingin setiap modul memiliki grafik dependensi independen, untuk memiliki komponen root spesifik dari mana Anda dapat melakukan apa saja, kami ingin memiliki kode yang kami buat sendiri untuk setiap modul Gradle. Kami tidak ingin kode yang dihasilkan masuk ke kode utama. Kami ingin sebanyak mungkin validasi waktu kompilasi. Kami menderita [k] apt, setidaknya kami harus mendapatkan keuntungan dari apa yang diberikan belati. Dan dengan semua ini, kami tidak ingin memaksa siapa pun untuk menggunakan Belati. Baik orang yang mengimplementasikan modul fitur baru secara terpisah, maupun orang yang kemudian mengkonsumsinya, adalah kolega kami yang meminta beberapa fitur untuk diri mereka sendiri.
Bagaimana cara mengatur grafik ketergantungan yang terpisah di dalam modul fitur kami?

Anda dapat mencoba menggunakan Subkomponen, dan itu bahkan akan berfungsi. Tetapi ini memiliki beberapa kekurangan. Anda dapat melihat bahwa dalam Subkomponen tidak jelas dependensi mana yang digunakannya dari Komponen. Untuk memahami ini, Anda harus merakit ulang proyek dengan panjang dan menyakitkan, lihat apa yang Dagger sumpah dan tambahkan.
Selain itu, subkomponen diatur sedemikian rupa sehingga mereka memaksa orang lain untuk menggunakan Belati, dan itu tidak akan berhasil dengan mudah untuk klien Anda dan diri Anda sendiri jika Anda memutuskan untuk menolak dalam beberapa modul.

Salah satu hal yang paling menjijikkan adalah ketika menggunakan Subkomponen, semua dependensi ditarik ke modul utama. Belati dirancang sedemikian rupa sehingga subkomponen dihasilkan oleh kelas tertanam dari komponen pembingkaiannya, induknya. Mungkin seseorang melihat kode yang dihasilkan dan ukurannya pada komponen yang dihasilkan? Kami memiliki 20 ribu baris di dalamnya. Karena subkomponen selalu bersarang kelas untuk komponen, ternyata subkomponen dari subkomponen juga bersarang, dan semua kode yang dihasilkan jatuh ke modul utama, file dua puluh baris ini yang perlu dikompilasi dan perlu di refactored, Studio mulai melambat - sakit.
Tapi ada solusinya. Anda dapat menggunakan hanya Komponen.

Dalam belati, komponen dapat menentukan dependensi. Ini ditunjukkan dalam kode, dan ditunjukkan dalam gambar. Ketergantungan tempat Anda menentukan metode Penyediaan, metode pabrik yang menunjukkan entitas mana yang menjadi tempat bergantung komponen Anda. Dia menginginkan mereka pada saat penciptaan.
Sebelumnya, saya selalu berpikir bahwa hanya komponen lain yang dapat ditentukan dalam dependensi ini, dan itulah sebabnya - dokumentasi mengatakan demikian.

Sekarang saya mengerti apa artinya menggunakan antarmuka komponen, tetapi sebelum saya pikir itu hanya komponen. Bahkan, Anda perlu menggunakan antarmuka yang disusun sesuai dengan aturan untuk membuat antarmuka untuk komponen. Singkatnya, cukup metode Penyediaan, ketika Anda hanya memiliki getter untuk beberapa jenis dependensi. Anda juga dapat menemukan kode sampel dalam dokumentasi Belati.

Komponen OtherComponent juga ditulis di sana, dan ini membingungkan, karena pada kenyataannya, Anda tidak bisa hanya memasukkan komponen di sana.
Bagaimana kita ingin menggunakan bisnis ini dalam kenyataan?

Pada kenyataannya, ada modul Fitur, ia memiliki paket API yang terlihat, terletak dekat dengan akar semua paket, dan dikatakan ada titik masuk - FeatureActivity. Tidak perlu menggunakan typealias, hanya untuk membuatnya jelas. Ini bisa berupa fragmen, bisa berupa ViewController - tidak masalah. Dan ada dependensinya, FeatureDeps, di mana diindikasikan bahwa ia membutuhkan konteks, beberapa layanan Jaringan, dari Common beberapa hal yang ingin Anda dapatkan dari App, dan setiap klien berkewajiban untuk memenuhi ini. Ketika dia melakukannya, semuanya akan bekerja.

Bagaimana kita menggunakan semua ini dalam modul Fitur? Di sini saya menggunakan Activity, ini opsional. Seperti biasa, kami membuat komponen Root Dagger kami sendiri dan menggunakan metode magic findComponentDependencies, sangat mirip dengan Dagger untuk Android, tetapi kami tidak dapat menggunakannya terutama karena kami tidak ingin menyeret subkomponen. Kalau tidak, kita bisa mengambil semua logika dari mereka.
Pada awalnya saya mencoba untuk mengetahui cara kerjanya, tetapi Anda dapat melihatnya di proyek sampel pada hari Jumat. Bagaimana ini harus digunakan oleh klien perpustakaan Anda di modul utama Anda?

Pertama-tama, itu hanya typealias. Sebenarnya, ia memiliki nama yang berbeda, tetapi untuk singkatnya. Kelas antarmuka MapOfDepth by Dependency memberi Anda implementasinya. Di App, kami mengatakan bahwa kami dapat membuat dependensi dengan cara yang sama seperti di Dagger untuk Android, dan sangat penting bahwa komponen mewarisi antarmuka ini dan secara otomatis menerima metode Provision. Belati mulai saat ini mulai memaksa kita untuk memberikan ketergantungan ini. Sampai Anda memberikannya, itu tidak akan dikompilasi. Ini adalah kenyamanan utama: Anda memutuskan untuk mengatur fitur, memperluas komponen Anda dengan antarmuka ini - semuanya sampai Anda melakukan sisanya, tidak hanya mengkompilasi, tetapi akan menghasilkan pesan kesalahan yang jelas. Modul ini sederhana, intinya adalah bahwa ia mengikat komponen Anda ke implementasi antarmuka. Kira-kira sama dengan di Dagger untuk Android.
Mari beralih ke hasil.

Saya memeriksa mainframe kami dan laptop lokal saya, sebelum itu saya mematikan semua yang mungkin terjadi. Jika kita menambahkan metode publik ke Fitur, maka waktu pembuatannya sangat berbeda. Di sini saya menunjukkan perbedaan ketika saya sedang membangun proyek sampel. Ini 16 detik. Atau ketika saya mengumpulkan semua kartu - itu berarti dua menit untuk duduk dan menunggu setiap, bahkan perubahan minimal. Oleh karena itu, banyak fitur yang kami kembangkan dan akan kami kembangkan dalam proyek sampel. Pada mainframe, waktunya sebanding.

Hasil penting lainnya. Sebelum menyoroti modul Fitur, tampilannya seperti ini: pada mainframe itu 28 detik, sekarang 49 detik. Kami telah mengalokasikan modul pertama, dan sudah menerima perlambatan perakitan hampir dua kali.

Dan opsi lain adalah perakitan tambahan sederhana dari modul kami, bukan fitur, seperti pada yang sebelumnya. 28 detik sampai modul dialokasikan. Ketika kami mengalokasikan kode yang tidak perlu dibangun kembali setiap kali, dan [k] apt, yang tidak perlu dilakukan setiap kali, kami memenangkan tiga detik. Tuhan tahu apa, tetapi saya berharap bahwa dengan setiap modul baru, waktu hanya akan berkurang.
Berikut ini tautan yang bermanfaat untuk artikel:
API versus implementasi ,
artikel dengan pengukuran waktu pembuatan ,
modul sampel . Presentasi akan
tersedia . Terima kasih