Jadikan injeksi ketergantungan antar paket sebagai tugas sepele dengan pengaturan ini

TL; DR: Dengan menggunakan injectable, injectable_generator, dan get_it , kita membuat instance get_it dan meneruskannya melalui paket yang berbeda dan meminta setiap paket menerapkan konfigurasinya ke instance get_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 antarmuka Counter dan antarmuka CounterStorage, dan implementasi untuk antarmuka Counter bernama CounterImpl
  • paket counter_storage yang mengimplementasikan antarmuka penyimpanan dari paket counter.
  • paket logger yang mengekspor antarmuka Logger dan mendefinisikan implementasi LoggerImpl.
  • 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