Aplikasi CLI + Injector Ketergantungan - panduan injeksi ketergantungan + FAQ

Hai,



saya pencipta Dependency Injector . Ini adalah kerangka kerja injeksi ketergantungan untuk Python.



Ini adalah panduan definitif untuk membangun aplikasi dengan Dependency Injector. Tutorial sebelumnya membahas bagaimana membangun aplikasi web dengan Flask , REST API dengan Aiohttp, dan memantau daemon dengan Asyncio menggunakan injeksi ketergantungan.



Hari ini saya ingin menunjukkan bagaimana Anda dapat membangun aplikasi konsol (CLI).



Selain itu, saya telah menyiapkan jawaban atas pertanyaan yang sering diajukan dan akan menerbitkan catatan tambahannya.



Manual terdiri dari bagian-bagian berikut:



  1. Apa yang akan kita bangun?
  2. Mempersiapkan lingkungan
  3. Struktur proyek
  4. Menginstal dependensi
  5. Perlengkapan
  6. Wadah
  7. Bekerja dengan csv
  8. Bekerja dengan sqlite
  9. Pemilih Penyedia
  10. Tes
  11. Kesimpulan
  12. PS: tanya jawab


Proyek yang telah selesai dapat ditemukan di Github .



Untuk memulai, Anda harus memiliki:



  • Python 3.5+
  • Lingkungan virtual


Dan diinginkan untuk memiliki pemahaman umum tentang prinsip injeksi ketergantungan.



Apa yang akan kita bangun?



Kami akan membangun aplikasi CLI (konsol) yang mencari film. Sebut saja Movie Lister.



Bagaimana cara kerja Movie Lister?



  • Kami memiliki database film
  • Informasi berikut ini diketahui tentang setiap film:

    • Nama
    • Tahun penerbitan
    • Nama sutradara
  • Basis data didistribusikan dalam dua format:

    • File csv
    • Database persegi
  • Aplikasi mencari database menggunakan kriteria berikut:

    • Nama sutradara
    • Tahun penerbitan
  • Format database lain mungkin ditambahkan di masa mendatang


Movie Lister adalah contoh aplikasi yang digunakan dalam artikel Martin Fowler tentang injeksi ketergantungan dan kontrol inversi.



Seperti inilah diagram kelas dari aplikasi Movie Lister:





Tanggung jawab antar kelas didistribusikan sebagai berikut:



  • MovieLister - bertanggung jawab atas pencarian
  • MovieFinder - Bertanggung jawab untuk mengekstraksi data dari database
  • Movie - "film" kelas entitas


Mempersiapkan lingkungan



Mari kita mulai dengan mempersiapkan lingkungan.



Pertama-tama, kita perlu membuat folder proyek dan lingkungan virtual:



mkdir movie-lister-tutorial
cd movie-lister-tutorial
python3 -m venv venv


Sekarang mari aktifkan lingkungan virtual:



. venv/bin/activate


Lingkungan sudah siap. Sekarang mari masuk ke dalam struktur proyek.



Struktur proyek



Pada bagian ini, kami akan mengatur struktur proyek.



Mari buat struktur berikut di folder saat ini. Biarkan semua file kosong untuk saat ini.



Struktur awal:



./
├── movies/
│   ├── __init__.py
│   ├── __main__.py
│   └── containers.py
├── venv/
├── config.yml
└── requirements.txt


Menginstal dependensi



Saatnya menginstal dependensi. Kami akan menggunakan paket seperti ini:



  • dependency-injector - kerangka injeksi ketergantungan
  • pyyaml - perpustakaan untuk mem-parsing file YAML, digunakan untuk membaca konfigurasi
  • pytest - kerangka pengujian
  • pytest-cov - Perpustakaan pembantu untuk mengukur cakupan kode dengan tes


Mari tambahkan baris berikut ke file requirements.txt:



dependency-injector
pyyaml
pytest
pytest-cov


Dan jalankan di terminal:



pip install -r requirements.txt


Instalasi dependensi selesai. Pindah ke perlengkapan.



Perlengkapan



Di bagian ini, kami akan menambahkan perlengkapan. Data uji disebut perlengkapan.



Kami akan membuat skrip yang akan membuat database uji.



Tambahkan direktori data/ke root proyek dan tambahkan file di dalamnya fixtures.py:



./
├── data/
│   └── fixtures.py
├── movies/
│   ├── __init__.py
│   ├── __main__.py
│   └── containers.py
├── venv/
├── config.yml
└── requirements.txt


Selanjutnya, edit fixtures.py:



"""Fixtures module."""

import csv
import sqlite3
import pathlib


SAMPLE_DATA = [
    ('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'),
    ('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'),
    ('The Jungle Book', 2016, 'Jon Favreau'),
]

FILE = pathlib.Path(__file__)
DIR = FILE.parent
CSV_FILE = DIR / 'movies.csv'
SQLITE_FILE = DIR / 'movies.db'


def create_csv(movies_data, path):
    with open(path, 'w') as opened_file:
        writer = csv.writer(opened_file)
        for row in movies_data:
            writer.writerow(row)


def create_sqlite(movies_data, path):
    with sqlite3.connect(path) as db:
        db.execute(
            'CREATE TABLE IF NOT EXISTS movies '
            '(title text, year int, director text)'
        )
        db.execute('DELETE FROM movies')
        db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data)


def main():
    create_csv(SAMPLE_DATA, CSV_FILE)
    create_sqlite(SAMPLE_DATA, SQLITE_FILE)
    print('OK')


if __name__ == '__main__':
    main()


Sekarang mari kita jalankan di terminal:



python data/fixtures.py


Skrip harus menghasilkan OKkesuksesan.



Kami memverifikasi bahwa file movies.csvdan movies.dbmuncul di direktori data/:



./
├── data/
│   ├── fixtures.py
│   ├── movies.csv
│   └── movies.db
├── movies/
│   ├── __init__.py
│   ├── __main__.py
│   └── containers.py
├── venv/
├── config.yml
└── requirements.txt


Jadwal dibuat. Ayo lanjutkan.



Wadah



Di bagian ini, kami akan menambahkan bagian utama dari aplikasi kami - wadah.



Penampung memungkinkan Anda mendeskripsikan struktur aplikasi dalam gaya deklaratif. Ini akan berisi semua komponen aplikasi dan dependensinya. Semua dependensi akan ditentukan secara eksplisit. Penyedia digunakan untuk menambahkan komponen aplikasi ke wadah. Penyedia mengontrol masa pakai komponen. Saat membuat penyedia, tidak ada komponen yang dibuat. Kami memberi tahu penyedia cara membuat objek, dan itu akan membuatnya segera setelah diperlukan. Jika ketergantungan satu penyedia adalah penyedia lain, maka itu akan dipanggil dan seterusnya di sepanjang rantai ketergantungan.



Mari edit containers.py:



"""Containers module."""

from dependency_injector import containers


class ApplicationContainer(containers.DeclarativeContainer):
    ...


Wadahnya masih kosong. Kami akan menambahkan penyedia di bagian selanjutnya.



Mari tambahkan fungsi lain main(). Tanggung jawabnya adalah menjalankan aplikasi. Untuk saat ini, dia hanya akan membuat wadah.



Mari edit __main__.py:



"""Main module."""

from .containers import ApplicationContainer


def main():
    container = ApplicationContainer()


if __name__ == '__main__':
    main()


Kontainer adalah objek pertama dalam aplikasi. Ini digunakan untuk mendapatkan semua objek lainnya.


Bekerja dengan csv



Sekarang mari tambahkan semua yang kita butuhkan untuk bekerja dengan file csv.



Kita butuh:



  • Intinya Movie
  • Kelas dasar MovieFinder
  • Implementasinya CsvMovieFinder
  • Kelas MovieLister


Setelah menambahkan setiap komponen, kami akan menambahkannya ke wadah.







Buat file entities.pydalam sebuah paket movies:



./
├── data/
│   ├── fixtures.py
│   ├── movies.csv
│   └── movies.db
├── movies/
│   ├── __init__.py
│   ├── __main__.py
│   ├── containers.py
│   └── entities.py
├── venv/
├── config.yml
└── requirements.txt


dan tambahkan baris berikut di dalamnya:



"""Movie entities module."""


class Movie:

    def __init__(self, title: str, year: int, director: str):
        self.title = str(title)
        self.year = int(year)
        self.director = str(director)

    def __repr__(self):
        return '{0}(title={1}, year={2}, director={3})'.format(
            self.__class__.__name__,
            repr(self.title),
            repr(self.year),
            repr(self.director),
        )


Sekarang kita perlu menambahkan pabrik Movieke wadah. Untuk ini kita membutuhkan modul providersdari dependency_injector.



Mari edit containers.py:



"""Containers module."""

from dependency_injector import containers, providers

from . import entities

class ApplicationContainer(containers.DeclarativeContainer):

    movie = providers.Factory(entities.Movie)


Jangan lupa untuk menghapus elipsis ( ...). Penampung sudah memiliki penyedia dan tidak lagi diperlukan.


Mari lanjutkan ke pembuatan finders.



Buat file finders.pydalam sebuah paket movies:



./
├── data/
│   ├── fixtures.py
│   ├── movies.csv
│   └── movies.db
├── movies/
│   ├── __init__.py
│   ├── __main__.py
│   ├── containers.py
│   ├── entities.py
│   └── finders.py
├── venv/
├── config.yml
└── requirements.txt


dan tambahkan baris berikut di dalamnya:



"""Movie finders module."""

import csv
from typing import Callable, List

from .entities import Movie


class MovieFinder:

    def __init__(self, movie_factory: Callable[..., Movie]) -> None:
        self._movie_factory = movie_factory

    def find_all(self) -> List[Movie]:
        raise NotImplementedError()


class CsvMovieFinder(MovieFinder):

    def __init__(
            self,
            movie_factory: Callable[..., Movie],
            path: str,
            delimiter: str,
    ) -> None:
        self._csv_file_path = path
        self._delimiter = delimiter
        super().__init__(movie_factory)

    def find_all(self) -> List[Movie]:
        with open(self._csv_file_path) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
            return [self._movie_factory(*row) for row in csv_reader]


Sekarang mari tambahkan CsvMovieFinderke wadah.



Mari edit containers.py:



"""Containers module."""

from dependency_injector import containers, providers

from . import finders, entities

class ApplicationContainer(containers.DeclarativeContainer):

    config = providers.Configuration()

    movie = providers.Factory(entities.Movie)

    csv_finder = providers.Singleton(
        finders.CsvMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.csv.path,
        delimiter=config.finder.csv.delimiter,
    )


Anda CsvMovieFindermemiliki ketergantungan pada pabrik Movie. CsvMovieFindermembutuhkan pabrik karena akan membuat objek Moviesaat membaca data dari file. Untuk melewati pabrik, kami menggunakan atribut .provider. Ini disebut delegasi penyedia. Jika kita menetapkan pabrik moviesebagai ketergantungan, itu akan dipanggil saat csv_finderdibuat CsvMovieFinderdan objek akan diteruskan sebagai injeksi Movie. Menggunakan atribut .providersebagai injeksi akan diteruskan oleh penyedia itu sendiri.



Ini csv_finderjuga memiliki ketergantungan pada beberapa opsi konfigurasi. Kami telah menambahkan penyedia onfigurationuntuk meneruskan dependensi ini.



Kami menggunakan parameter konfigurasi sebelum menetapkan nilainya. Ini adalah prinsip yang digunakan oleh penyedia Configuration.



Pertama kita gunakan, lalu kita atur nilainya.



Sekarang mari tambahkan nilai konfigurasi.



Mari edit config.yml:



finder:

  csv:
    path: "data/movies.csv"
    delimiter: ","


Nilai ditetapkan ke file konfigurasi. Mari perbarui fungsi main()untuk menunjukkan lokasinya.



Mari edit __main__.py:



"""Main module."""

from .containers import ApplicationContainer


def main():
    container = ApplicationContainer()

    container.config.from_yaml('config.yml')


if __name__ == '__main__':
    main()


Ayo pergi ke listers.



Buat file listers.pydalam sebuah paket movies:



./
├── data/
│   ├── fixtures.py
│   ├── movies.csv
│   └── movies.db
├── movies/
│   ├── __init__.py
│   ├── __main__.py
│   ├── containers.py
│   ├── entities.py
│   ├── finders.py
│   └── listers.py
├── venv/
├── config.yml
└── requirements.txt


dan tambahkan baris berikut di dalamnya:



"""Movie listers module."""

from .finders import MovieFinder


class MovieLister:

    def __init__(self, movie_finder: MovieFinder):
        self._movie_finder = movie_finder

    def movies_directed_by(self, director):
        return [
            movie for movie in self._movie_finder.find_all()
            if movie.director == director
        ]

    def movies_released_in(self, year):
        return [
            movie for movie in self._movie_finder.find_all()
            if movie.year == year
        ]


Kami memperbarui containers.py:



"""Containers module."""

from dependency_injector import containers, providers

from . import finders, listers, entities

class ApplicationContainer(containers.DeclarativeContainer):

    config = providers.Configuration()

    movie = providers.Factory(entities.Movie)

    csv_finder = providers.Singleton(
        finders.CsvMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.csv.path,
        delimiter=config.finder.csv.delimiter,
    )

    lister = providers.Factory(
        listers.MovieLister,
        movie_finder=csv_finder,
    )


Semua komponen dibuat dan ditambahkan ke penampung.



Terakhir, kami memperbarui fungsinya main().



Mari edit __main__.py:



"""Main module."""

from .containers import ApplicationContainer


def main():
    container = ApplicationContainer()

    container.config.from_yaml('config.yml')

    lister = container.lister()

    print(
        'Francis Lawrence movies:',
        lister.movies_directed_by('Francis Lawrence'),
    )
    print(
        '2016 movies:',
        lister.movies_released_in(2016),
    )


if __name__ == '__main__':
    main()


Semuanya sudah siap. Sekarang mari luncurkan aplikasinya.



Mari kita jalankan di terminal:



python -m movies


Kamu akan lihat:



Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]


Aplikasi kami bekerja dengan database film di csv. Kami juga perlu menambahkan dukungan format sqlite. Kami akan membahas ini di bagian selanjutnya.



Bekerja dengan sqlite



Di bagian ini, kami akan menambahkan tipe lain MovieFinder- SqliteMovieFinder.



Mari edit finders.py:



"""Movie finders module."""

import csv
import sqlite3
from typing import Callable, List

from .entities import Movie


class MovieFinder:

    def __init__(self, movie_factory: Callable[..., Movie]) -> None:
        self._movie_factory = movie_factory

    def find_all(self) -> List[Movie]:
        raise NotImplementedError()


class CsvMovieFinder(MovieFinder):

    def __init__(
            self,
            movie_factory: Callable[..., Movie],
            path: str,
            delimiter: str,
    ) -> None:
        self._csv_file_path = path
        self._delimiter = delimiter
        super().__init__(movie_factory)

    def find_all(self) -> List[Movie]:
        with open(self._csv_file_path) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
            return [self._movie_factory(*row) for row in csv_reader]


class SqliteMovieFinder(MovieFinder):

    def __init__(
            self,
            movie_factory: Callable[..., Movie],
            path: str,
    ) -> None:
        self._database = sqlite3.connect(path)
        super().__init__(movie_factory)

    def find_all(self) -> List[Movie]:
        with self._database as db:
            rows = db.execute('SELECT title, year, director FROM movies')
            return [self._movie_factory(*row) for row in rows]


Tambahkan penyedia sqlite_finderke container dan tentukan sebagai dependensi untuk penyedia lister.



Mari edit containers.py:



"""Containers module."""

from dependency_injector import containers, providers

from . import finders, listers, entities

class ApplicationContainer(containers.DeclarativeContainer):

    config = providers.Configuration()

    movie = providers.Factory(entities.Movie)

    csv_finder = providers.Singleton(
        finders.CsvMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.csv.path,
        delimiter=config.finder.csv.delimiter,
    )

    sqlite_finder = providers.Singleton(
        finders.SqliteMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.sqlite.path,
    )

    lister = providers.Factory(
        listers.MovieLister,
        movie_finder=sqlite_finder,
    )


Penyedia sqlite_findermemiliki ketergantungan pada opsi konfigurasi yang belum kami tentukan. Mari perbarui file konfigurasi:



Edit config.yml:



finder:

  csv:
    path: "data/movies.csv"
    delimiter: ","

  sqlite:
    path: "data/movies.db"


Selesai. Mari kita periksa.



Kami mengeksekusi di terminal:



python -m movies


Kamu akan lihat:



Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]


Aplikasi kami mendukung kedua format database: csvdan sqlite. Setiap kali kami perlu mengubah format, kami harus mengubah kode di wadah. Kami akan meningkatkan ini di bagian selanjutnya.



Pemilih Penyedia



Di bagian ini, kami akan membuat aplikasi kami lebih fleksibel.



Anda tidak perlu lagi mengubah kode untuk beralih di antara csvdan sqliteformat. Kami akan menerapkan sakelar berdasarkan variabel lingkungan MOVIE_FINDER_TYPE:



  • Saat MOVIE_FINDER_TYPE=csvaplikasi menggunakan csv.
  • Saat MOVIE_FINDER_TYPE=sqliteaplikasi menggunakan sqlite.


Penyedia akan membantu kami dalam hal ini Selector. Ini memilih penyedia berdasarkan opsi konfigurasi ( dokumentasi ).



Mari edit containers.py:



"""Containers module."""

from dependency_injector import containers, providers

from . import finders, listers, entities


class ApplicationContainer(containers.DeclarativeContainer):

    config = providers.Configuration()

    movie = providers.Factory(entities.Movie)

    csv_finder = providers.Singleton(
        finders.CsvMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.csv.path,
        delimiter=config.finder.csv.delimiter,
    )

    sqlite_finder = providers.Singleton(
        finders.SqliteMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.sqlite.path,
    )

    finder = providers.Selector(
        config.finder.type,
        csv=csv_finder,
        sqlite=sqlite_finder,
    )

    lister = providers.Factory(
        listers.MovieLister,
        movie_finder=finder,
    )


Kami membuat penyedia finderdan menetapkannya sebagai dependensi untuk penyedia lister. Penyedia findermemilih antara penyedia csv_finderdan sqlite_findersaat waktu proses. Pilihannya tergantung pada nilai sakelar.



Sakelar adalah opsi konfigurasi config.finder.type. Saat nilainya csvdigunakan oleh penyedia dari kunci csv. Demikian juga untuk sqlite.



Sekarang kita perlu membaca nilai config.finder.typedari variabel lingkungan MOVIE_FINDER_TYPE.



Mari edit __main__.py:



"""Main module."""

from .containers import ApplicationContainer


def main():
    container = ApplicationContainer()

    container.config.from_yaml('config.yml')
    container.config.finder.type.from_env('MOVIE_FINDER_TYPE')

    lister = container.lister()

    print(
        'Francis Lawrence movies:',
        lister.movies_directed_by('Francis Lawrence'),
    )
    print(
        '2016 movies:',
        lister.movies_released_in(2016),
    )


if __name__ == '__main__':
    main()


Selesai.



Jalankan perintah berikut di terminal:



MOVIE_FINDER_TYPE=csv python -m movies
MOVIE_FINDER_TYPE=sqlite python -m movies


Output untuk setiap perintah akan terlihat seperti ini:



Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]


Di bagian ini, kami berkenalan dengan penyedia Selector. Dengan penyedia ini, Anda dapat membuat aplikasi Anda lebih fleksibel. Nilai sakelar dapat disetel dari sumber apa pun: file konfigurasi, kamus, penyedia lain.



Petunjuk:

Mengganti nilai konfigurasi dari penyedia lain memungkinkan Anda mengimplementasikan overloading konfigurasi dalam aplikasi Anda tanpa hot restart.

Untuk melakukan ini, Anda perlu menggunakan delegasi penyedia dan .override().



Di bagian selanjutnya, kami akan menambahkan beberapa tes.



Tes



Terakhir, mari tambahkan beberapa tes.



Buat file tests.pydalam sebuah paket movies:



./
├── data/
│   ├── fixtures.py
│   ├── movies.csv
│   └── movies.db
├── movies/
│   ├── __init__.py
│   ├── __main__.py
│   ├── containers.py
│   ├── entities.py
│   ├── finders.py
│   ├── listers.py
│   └── tests.py
├── venv/
├── config.yml
└── requirements.txt


dan tambahkan baris berikut ke dalamnya:



"""Tests module."""

from unittest import mock

import pytest

from .containers import ApplicationContainer


@pytest.fixture
def container():
    container = ApplicationContainer()
    container.config.from_dict({
        'finder': {
            'type': 'csv',
            'csv': {
                'path': '/fake-movies.csv',
                'delimiter': ',',
            },
            'sqlite': {
                'path': '/fake-movies.db',
            },
        },
    })
    return container


def test_movies_directed_by(container):
    finder_mock = mock.Mock()
    finder_mock.find_all.return_value = [
        container.movie('The 33', 2015, 'Patricia Riggen'),
        container.movie('The Jungle Book', 2016, 'Jon Favreau'),
    ]

    with container.finder.override(finder_mock):
        lister = container.lister()
        movies = lister.movies_directed_by('Jon Favreau')

    assert len(movies) == 1
    assert movies[0].title == 'The Jungle Book'


def test_movies_released_in(container):
    finder_mock = mock.Mock()
    finder_mock.find_all.return_value = [
        container.movie('The 33', 2015, 'Patricia Riggen'),
        container.movie('The Jungle Book', 2016, 'Jon Favreau'),
    ]

    with container.finder.override(finder_mock):
        lister = container.lister()
        movies = lister.movies_released_in(2015)

    assert len(movies) == 1
    assert movies[0].title == 'The 33'


Sekarang mari kita mulai menguji dan memeriksa cakupannya:



pytest movies/tests.py --cov=movies


Kamu akan lihat:



platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: cov-2.10.0
collected 2 items

movies/tests.py ..                                              [100%]

---------- coverage: platform darwin, python 3.8.3-final-0 -----------
Name                   Stmts   Miss  Cover
------------------------------------------
movies/__init__.py         0      0   100%
movies/__main__.py        10     10     0%
movies/containers.py       9      0   100%
movies/entities.py         7      1    86%
movies/finders.py         26     13    50%
movies/listers.py          8      0   100%
movies/tests.py           24      0   100%
------------------------------------------
TOTAL                     84     24    71%


Kami menggunakan metode .override()penyedia finder. Penyedia diganti dengan tiruan. Saat menghubungi penyedia, findertiruan yang menimpa sekarang akan dikembalikan.



Pekerjaan sudah selesai. Sekarang mari kita rangkum.



Kesimpulan



Kami membangun aplikasi CLI menggunakan prinsip injeksi ketergantungan. Kami menggunakan Dependency Injector sebagai kerangka kerja injeksi ketergantungan.



Keuntungan yang Anda dapatkan dengan Dependency Injector adalah wadah.



Penampung mulai terbayar saat Anda perlu memahami atau mengubah struktur aplikasi Anda. Dengan container, ini mudah karena semua komponen aplikasi dan dependensinya ditentukan secara eksplisit di satu tempat:



"""Containers module."""

from dependency_injector import containers, providers

from . import finders, listers, entities


class ApplicationContainer(containers.DeclarativeContainer):

    config = providers.Configuration()

    movie = providers.Factory(entities.Movie)

    csv_finder = providers.Singleton(
        finders.CsvMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.csv.path,
        delimiter=config.finder.csv.delimiter,
    )

    sqlite_finder = providers.Singleton(
        finders.SqliteMovieFinder,
        movie_factory=movie.provider,
        path=config.finder.sqlite.path,
    )

    finder = providers.Selector(
        config.finder.type,
        csv=csv_finder,
        sqlite=sqlite_finder,
    )

    lister = providers.Factory(
        listers.MovieLister,
        movie_finder=finder,
    )




Sebuah wadah sebagai peta aplikasi Anda. Anda selalu tahu apa yang tergantung pada apa.



PS: tanya jawab



Dalam komentar pada tutorial sebelumnya, pertanyaan keren telah diajukan: "mengapa ini perlu?", "Mengapa kita membutuhkan kerangka kerja?", "Bagaimana kerangka kerja membantu dalam implementasi?"



Saya sudah menyiapkan jawaban:



Apa itu injeksi ketergantungan?



  • Ini adalah prinsip yang mengurangi kopling dan meningkatkan kohesi


Mengapa saya harus menggunakan injeksi ketergantungan?



  • kode Anda menjadi lebih fleksibel, mudah dipahami, dan lebih dapat diuji
  • Anda memiliki lebih sedikit masalah saat Anda perlu memahami cara kerjanya atau mengubahnya


Bagaimana cara mulai menerapkan injeksi ketergantungan?



  • Anda mulai menulis kode mengikuti prinsip injeksi ketergantungan
  • Anda mendaftarkan semua komponen dan dependensinya di container
  • saat Anda membutuhkan sebuah komponen, Anda mendapatkannya dari penampung


Mengapa saya membutuhkan kerangka kerja untuk ini?



  • Anda membutuhkan kerangka kerja agar tidak membuatnya sendiri. Kode pembuatan objek akan digandakan dan sulit diubah. Untuk menghindarinya, Anda membutuhkan wadah.
  • kerangka kerja memberi Anda wadah dan penyedia
  • penyedia mengontrol masa benda. Anda akan membutuhkan objek pabrik, lajang, dan konfigurasi
  • penampung berfungsi sebagai kumpulan penyedia


Berapa harga yang saya bayar?



  • Anda perlu menentukan dependensi di container secara eksplisit
  • ini adalah pekerjaan tambahan
  • itu akan mulai membayar dividen saat proyek mulai tumbuh
  • atau 2 minggu setelah penyelesaian (ketika Anda lupa keputusan apa yang Anda buat dan bagaimana struktur proyek)


Konsep Dependency Injector



Selain itu, saya akan menjelaskan konsep Dependency Injector sebagai kerangka kerja.



Dependency Injector didasarkan pada dua prinsip:



  • Eksplisit lebih baik daripada implisit (PEP20).
  • Jangan lakukan sihir apa pun dengan kode Anda.


Apa yang membedakan Dependency Injector dari framework lain?



  • Tidak ada tautan otomatis. Kerangka tidak secara otomatis menghubungkan dependensi. Introspeksi, menghubungkan dengan nama argumen dan / atau tipe tidak digunakan. Karena "eksplisit lebih baik daripada implisit (PEP20)".
  • Tidak mencemari kode aplikasi Anda. Aplikasi Anda tidak menyadari dan tidak bergantung pada Dependency Injector. Tidak ada @injectdekorator, anotasi, tambalan atau trik sulap lainnya.


Dependency Injector menawarkan kontrak sederhana:



  • Anda menunjukkan kerangka bagaimana mengumpulkan objek
  • Kerangka mengumpulkan mereka


Kekuatan dari Dependency Injector terletak pada kesederhanaan dan keterusterangannya. Ini adalah alat sederhana untuk menerapkan prinsip yang ampuh.



Apa berikutnya?



Jika Anda tertarik, tetapi ragu, rekomendasi saya adalah ini:



Coba pendekatan ini selama 2 bulan. Dia tidak intuitif. Perlu waktu untuk terbiasa dan merasa. Manfaatnya menjadi nyata saat proyek berkembang hingga 30+ komponen dalam satu wadah. Jika Anda tidak menyukainya, jangan rugi banyak. Jika Anda suka, dapatkan keuntungan yang signifikan.





Saya akan senang menerima umpan balik dan menjawab pertanyaan di komentar.



All Articles