Jadikan injeksi ketergantungan antar paket sebagai tugas sepele dengan pengaturan ini
TL; DR: Dengan menggunakan
injectable
,injectable_generator
, danget_it
, kita membuat instanceget_it
dan meneruskannya melalui paket yang berbeda dan meminta setiap paket menerapkan konfigurasinya ke instanceget_it
ini.
Pendahuluan
Biasanya, menyiapkan injeksi ketergantungan melibatkan pengaturan yang terlihat seperti berikut:
Kami mengambil instance get_it
dan menggunakan keajaiban pembuatan kode yang bagus untuk membuat sesuatu terjadi di latar belakang, dan kami memberi anotasi pada kelas kami dengan hal-hal seperti @Injectable
atau @LazySingleton
untuk membuatnya tersedia melalui di.
Semua itu adalah cara praktis dan mudah untuk melakukan di kami. Ini jauh lebih baik daripada harus mendaftarkan semuanya secara manual setiap saat.
Tapi kita hanya berbicara tentang satu paket. Biasanya, itu adalah aplikasi Flutter Anda, misalnya.
Bagaimana jika Anda memiliki proyek di mana Anda mengendalikan beberapa paket, dan Anda masih ingin menggunakan solusi mudah ini?
Anda mungkin ingin struktur paket seperti itu melonggarkan ketergantungan dalam proyek Anda atau menerapkan arsitektur tertentu (bayangan;)).
Baiklah, saya punya solusinya untuk Anda!
Menyiapkan Aliran Ketergantungan
Sebelum kita mulai, saya ingin memperjelas seperti apa produk akhirnya dengan diagram alur berikut:
Jika dipikir-pikir, ini sangat masuk akal karena App
bergantung pada semua paket lainnya.
Sesederhana itu.
Bagaimana Menerapkannya
Catatan: Untuk contoh di atas, keseluruhan penyiapan ini sangat berlebihan, namun ini untuk membantu agar ide dapat tersampaikan.
Mari kita mulai dengan mendefinisikan aplikasi sederhana. Contoh utama saya biasanya adalah penghitung, jadi mari kita mulai saja.
Aplikasi kita akan menjadi penghitung dasar yang dapat ditambah, yang nilainya disimpan dalam memori. Untungnya, Flutter sudah menyediakan porsi yang signifikan saat kita membuat proyek baru.
Aplikasi akan dibagi menjadi empat paket:
- paket
counter
yang mengekspor antarmukaCounter
dan antarmukaCounterStorage
, dan implementasi untuk antarmukaCounter
bernamaCounterImpl
- paket
counter_storage
yang mengimplementasikan antarmuka penyimpanan dari paketcounter
. - paket
logger
yang mengekspor antarmukaLogger
dan mendefinisikan implementasiLoggerImpl
. - paket
app
yang dihasilkan oleh Flutter.
Berikut adalah grafik yang menunjukkan bagaimana paket-paket ini bergantung satu sama lain:
Lapisan app
mengambil penghitung dan implementasinya dari counter
dan juga menggunakan paket logger
Logger
.
Alasan counter_storage
bergantung pada counter
adalah karena memerlukan antarmuka CounterStorage
agar counter_storage
dapat melakukan implementasi CounterStorageImpl
.
Dalam praktiknya, eksekusi kode akan berjalan seperti ini:
Injeksi ketergantungan adalah cara kami melakukan ini. Jadi, ini langkah demi langkahnya:
Langkah 1: Siapkan paket
Agar konsisten, kami akan melakukan implementasi DI persis seperti diagram di atas. Anda dapat menemukan implementasi akhir dari langkah ini di sini.
Untuk membuat paket, Anda dapat menggunakan perintah berikut (secara berurutan):
mkdir dart-easy-di cd dart-easy-di # 1st package dart create counter # 2nd package dart create counter_storage # 3rd package dart create logger # Finally, the app package flutter create app
(Catatan: Anda dapat menyalin-menempelkan seluruh blok perintah ini dan menempelkannya di terminal Anda dan menjalankannya)
Kami akan menambahkan kelas yang diperlukan ke paket kami untuk menyelesaikan langkah ini.
Ke paket counter
, tambahkan kelas berikut:
CounterImpl
adalah implementasi dasar Counter
yang menggunakan CounterStorage
sebagai ketergantungan.
Kita perlu mengekspor antarmuka Counter
dan CounterStorage
, jadi kita melakukannya di sini:
Konfigurasi paket pertama selesai! Ke berikutnya. Mari konfigurasikan paket CounterStorage
.
Tambahkan file berikut:
Kita perlu menambahkan ketergantungan pada counter
agar kita dapat mengimpornya.
Itu membuat paket counter_storage
kami selesai untuk saat ini. Anda akan melihat kami tidak mengekspor apa pun, jadi bagaimana cara kami mengaksesnya pada akhirnya? Tentu saja dengan sihir DI!
Kita akan membahasnya dalam langkah-langkah berikut. Mari kita urus logger
selanjutnya. Tambahkan file berikut:
Terakhir, kami mengekspor antarmuka Logger
:
Baiklah! Sekarang yang tersisa hanyalah paket app
. Untuk langkah ini, kami akan menambahkan dependensi di pubspec
:
Langkah 2: Menyiapkan DI dengan GetIt dan Injectable
Anda dapat menemukan implementasi akhir dari langkah ini di sini.
Idenya di sini adalah mengekspor metode yang menggunakan instance get_it
dan menyiapkan semua yang diperlukan.
Pertama, semua paket kami memerlukan beberapa dependensi. Mari tambahkan dengan skrip mewah di bawah ini:
# foreach dir for d in */; do cd "$d" echo "$d" dart pub add get_it injectable dart pub add --dev injectable_generator build_runner echo "\n" cd .. done
Jangan khawatir, ini tidak akan meretas komputer Anda (mungkin). Ini akan menginstal beberapa dependensi yang diperlukan untuk solusi DI kami ke setiap paket kami, yaitu get_it
, injectable
, injectable_generator
, dan build_runner
.
Kemudian, untuk masing-masing paket counter
, counter_storage
, dan logger
, kami akan menambahkan yang berikut ini (abaikan kesalahan apa pun dari editor Anda untuk saat ini):
Lalu, kita ekspor fungsi configureDependencies
ini di masing-masing counter/lib/counter.dart
, counter_storage/lib/counter_storage.dart
, dan logger/lib/logger.dart
:
Akhirnya, kami menyatukan semuanya di app
:
Pada baris 21 - 23, Anda dapat melihat kita memanggil setiap fungsi configureDependencies
paket yang baru saja kita ekspor. Kami melakukan aliasing impor dengan kata kunci as
untuk menghindari konflik penamaan.
Satu langkah terakhir adalah kita memanggil fungsi app
kita configureDependencies
di app/lib/main.dart
:
Kami telah melakukan 95% pengaturan yang kami perlukan, namun kami masih mengalami kesalahan yang mengganggu pada file di.dart
. Untuk menghilangkannya, jalankan skrip berikut:
# foreach dir for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done
Anda harus menjalankan skrip ini setiap kali Anda memperbarui dependensi Anda dengan cara apa pun. Contohnya adalah menambahkan tag @Injectable
, menambahkan ketergantungan baru ke kelas, dll.
Saya akan menunjukkan kepada Anda bagaimana Anda dapat menambahkan skrip ini sebagai fungsi yang mudah dijalankan di konfigurasi bash atau zsh Anda di sini.
Menjalankan skrip ini akan menyebabkan file baru muncul di direktori di/
setiap paket bernama di.config.dart
. File ini berisi asosiasi aktual antar kelas yang memungkinkan DI. Kesalahan seharusnya sudah hilang sekarang, tapi kita belum selesai.
Anda harus mengabaikan file ini di .gitignore
Anda, jadi pastikan untuk menambahkan **/di/*.config.dart
atau sesuatu yang serupa dengan itu. Saya pribadi membuat satu file .gitignore
di bawah dart-easy-di/
(yang berisi semua paket kami) dan menambahkan pernyataan abaikan saja di sana.
Jadi, satu-satunya yang tersisa saat ini adalah memberi anotasi pada file kita.
Langkah 3: Membuat Anotasi dengan Suntikan
Anda dapat menemukan implementasi yang telah selesai dari ini di sini.
Kami membubuhi keterangan penerapan kami dengan tag yang relevan. Ini termasuk CounterImpl
, CounterStorageImpl
, dan LoggerImpl
:
Dengan ini, kami telah mengaitkan implementasi kami dengan antarmukanya. Kami bahkan tidak perlu mengekspor implementasinya!
Kita perlu menjalankan skrip build_runner
lagi karena kita mengubah dependensinya:
# foreach dir for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done
Catatan: Anda mungkin mendapatkan peringatan saat menjalankan skrip ini tentang dependensi tertentu yang tidak terdaftar. Anda dapat mengabaikannya dengan aman.
Kami sekarang secara resmi selesai dengan persiapannya. Kita bisa menggunakan semua hal ini di app
.
Menggunakan paket kami di aplikasi Flutter
Anda dapat menemukan implementasi yang telah selesai di sini.
Di app/lib/main.dart
, kami melakukan hal berikut:
Baris 3 — 11 adalah tempat kita menambahkan impor untuk paket kita. Anda juga akan melihat impor file di.dart
diberi alias sebagai app
. Hal ini untuk menghindari konflik namespace.
Pada Baris 16 -› 18, kami menggunakan instance DI kami untuk mendapatkan counter dan logger. Kemudian kita gunakan yang ada di baris 22, 23, dan 41. Cobalah dan lihat apakah logger benar-benar berfungsi!
Jika Anda menemukan kesalahan apa pun dengan instance get_it
, menjalankan kembali skrip build_runner
mungkin dapat menyelesaikannya.
Skenario yang lebih realistis
Anda dapat menemukan implementasi akhir dari langkah ini di sini.
Meski berhasil, namun tidak sepenuhnya benar. Kami telah menyebabkan paket app
bergantung langsung pada counter_storage
, meskipun app
tidak berinteraksi sama sekali (selain memanggilnya configureDependencies
.
Bagaimana cara kita membuatnya agar grafik ketergantungan kita tidak rusak namun DI tetap seperti apa adanya?
Jawabannya ada pada layer di
. Ini adalah paket dengan tujuan menerima instance get_it
dan meneruskannya ke semua paket yang membutuhkannya.
Dengan cara ini, lapisan app
Anda hanya bergantung pada di
, dan di
bergantung pada lapisan lainnya dan mendistribusikan solusi DI Anda.
app
tentu saja masih bergantung pada counter
dan logger
karena memerlukan antarmuka yang diekspor.
Untuk mengimplementasikan di seperti diagram di atas, lakukan hal berikut (buka terminal di dart-easy-di
:
# Create the package dart create di # Install its dependencies cd di dart pub add get_it injectable dart pub add --dev injectable_generator build_runner
Tambahkan dependensi berikut ke pubspec.yaml
-nya
dan sekarang kita dapat menghapus ketergantungan counter_storage
dari app/pubspec.yaml
.
Kemudian, kita menyiapkan paket di
dengan file di.dart
miliknya sendiri, yang terlihat seperti berikut:
Ini terlihat mirip dengan apa yang kita lakukan di app/lib/di/di.dart
, bukan? Langkah selanjutnya adalah mengekspor fungsi configureDependencies
ini dari di
, mengimpornya ke app
lalu memanggilnya. Berikut langkah-langkah untuk melakukannya:
Ekspor dari paket di
:
Impor ke app
(Anda mungkin perlu menjalankan dart pub get
sesudahnya)
Perbarui app/lib/di/di.dart
Dan terakhir, jalankan kembali script build_runner
.
# foreach dir for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done
Jika Anda telah melakukan semuanya dengan benar (dan tentu saja Anda melakukannya, Anda adalah individu yang sangat cerdas), maka Anda telah menerapkan injeksi ketergantungan antarpaket.
Bonusnya
Berikut perintah untuk mengatur fungsi build_runner
di konfigurasi shell Anda. Ini akan membuat menjalankan skrip build_runner
lebih mudah.
echo " function build_all { for d in */; do cd "$d" echo "$d" dart run build_runner build echo "\n" cd .. done } " >> ~/.zshrc
Ganti .zshrc
dengan file konfigurasi shell Anda sendiri. Setelah Anda menjalankan ini di terminal Anda, restart terminal Anda atau jalankan source ~/.zshrc
.
Sekarang Anda dapat mengetik build_all
di terminal Anda ketika Anda berada di direktori dart-easy-di
, dan terminal akan menjalankan langkah pembuatan semua paket untuk Anda.
Apa pendapat Anda tentang solusi ini?
Lihat repo saya untuk implementasi lengkap.
Referensi
- Referensi Paket Dart Resmi
- paket GetIt
- Paket “Injectable” dan “Injectable Generator”.