Aplicativo CLI + Dependency Injector - guia de injeção de dependĂȘncia + FAQ

OlĂĄ,



sou o criador do injetor de dependĂȘncia . Esta Ă© uma estrutura de injeção de dependĂȘncia para Python.



Este Ă© o guia definitivo para criar aplicativos com o injetor de dependĂȘncia. Tutoriais anteriores cobrem como construir um aplicativo da web com Flask , API REST com Aiohttp e monitorar um daemon com Asyncio usando injeção de dependĂȘncia.



Hoje eu quero mostrar como vocĂȘ pode construir um aplicativo de console (CLI).



Além disso, preparei respostas para as perguntas mais frequentes e publicarei seu pós-escrito.



O manual consiste nas seguintes partes:



  1. O que vamos construir?
  2. Preparando o ambiente
  3. Estrutura do projeto
  4. Instalando dependĂȘncias
  5. LuminĂĄrias
  6. Recipiente
  7. Trabalhando com csv
  8. Trabalhando com sqlite
  9. Provider Selector
  10. Testes
  11. ConclusĂŁo
  12. PS: perguntas e respostas


O projeto concluĂ­do pode ser encontrado no Github .



Para começar vocĂȘ deve ter:



  • Python 3.5+
  • Ambiente virtual


E Ă© desejĂĄvel ter uma compreensĂŁo geral do princĂ­pio da injeção de dependĂȘncia.



O que vamos construir?



Estaremos construindo um aplicativo CLI (console) que procura filmes. Vamos chamĂĄ-lo de Movie Lister.



Como funciona o Movie Lister?



  • Temos um banco de dados de filmes
  • As seguintes informaçÔes sĂŁo conhecidas sobre cada filme:

    • Nome
    • Ano de emissĂŁo
    • Nome do diretor
  • O banco de dados Ă© distribuĂ­do em dois formatos:

    • Arquivo csv
    • Banco de dados Sqlite
  • O aplicativo pesquisa o banco de dados usando os seguintes critĂ©rios:

    • Nome do diretor
    • Ano de emissĂŁo
  • Outros formatos de banco de dados podem ser adicionados no futuro


Movie Lister Ă© um aplicativo de amostra usado no artigo de Martin Fowler sobre injeção de dependĂȘncia e inversĂŁo de controle.



Esta Ă© a aparĂȘncia do diagrama de classes do aplicativo Movie Lister:





As responsabilidades entre as classes sĂŁo distribuĂ­das da seguinte forma:



  • MovieLister - responsĂĄvel pela busca
  • MovieFinder - responsĂĄvel por extrair dados do banco de dados
  • Movie - classe de entidade "filme"


Preparando o ambiente



Vamos começar preparando o ambiente.



Em primeiro lugar, precisamos criar uma pasta de projeto e um ambiente virtual:



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


Agora vamos ativar o ambiente virtual:



. venv/bin/activate


O ambiente estĂĄ pronto. Agora vamos entrar na estrutura do projeto.



Estrutura do projeto



Nesta seção, vamos organizar a estrutura do projeto.



Vamos criar a seguinte estrutura na pasta atual. Deixe todos os arquivos vazios por enquanto.



Estrutura inicial:



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


Instalando dependĂȘncias



É hora de instalar as dependĂȘncias. Usaremos pacotes como este:



  • dependency-injector - framework de injeção de dependĂȘncia
  • pyyaml - biblioteca para analisar arquivos YAML, usada para ler a configuração
  • pytest - estrutura de teste
  • pytest-cov - biblioteca auxiliar para medir a cobertura de cĂłdigo por testes


Vamos adicionar as seguintes linhas ao arquivo requirements.txt:



dependency-injector
pyyaml
pytest
pytest-cov


E execute no terminal:



pip install -r requirements.txt


A instalação das dependĂȘncias foi concluĂ­da. Passando para os acessĂłrios.



LuminĂĄrias



Nesta seção, adicionaremos acessórios. Os dados de teste são chamados de acessórios.



Criaremos um script que criarĂĄ bancos de dados de teste.



Adicione um diretĂłrio data/Ă  raiz do projeto e adicione um arquivo dentro de fixtures.py:



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


A seguir, edite 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()


Agora vamos executar no terminal:



python data/fixtures.py


O script deve sair OKcom sucesso.



Verificamos se os arquivos movies.csve movies.dbapareceram no diretĂłrio data/:



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


Jogos sĂŁo criados. Vamos continuar.



Recipiente



Nesta seção, adicionaremos a parte principal de nosso aplicativo - o contĂȘiner.



O contĂȘiner permite que vocĂȘ descreva a estrutura do aplicativo em um estilo declarativo. Ele conterĂĄ todos os componentes do aplicativo e suas dependĂȘncias. Todas as dependĂȘncias serĂŁo especificadas explicitamente. Os provedores sĂŁo usados ​​para adicionar componentes do aplicativo ao contĂȘiner. Os provedores controlam a vida Ăștil dos componentes. Ao criar um provedor, nenhum componente Ă© criado. Dizemos ao provedor como criar o objeto e ele o criarĂĄ assim que necessĂĄrio. Se a dependĂȘncia de um provedor for de outro provedor, ele serĂĄ chamado e assim por diante ao longo da cadeia de dependĂȘncias.



Vamos editar containers.py:



"""Containers module."""

from dependency_injector import containers


class ApplicationContainer(containers.DeclarativeContainer):
    ...


O recipiente ainda estå vazio. Adicionaremos provedores nas próximas seçÔes.



Vamos adicionar outra função main(). Sua responsabilidade é executar o aplicativo. Por enquanto, ela criarå apenas um container.



Vamos editar __main__.py:



"""Main module."""

from .containers import ApplicationContainer


def main():
    container = ApplicationContainer()


if __name__ == '__main__':
    main()


O contĂȘiner Ă© o primeiro objeto do aplicativo. É usado para obter todos os outros objetos.


Trabalhando com csv



Agora vamos adicionar tudo o que precisamos para trabalhar com arquivos csv.



NĂłs precisamos:



  • A essĂȘncia Movie
  • Classe base MovieFinder
  • Sua implementação CsvMovieFinder
  • Classe MovieLister


ApĂłs adicionar cada componente, iremos adicionĂĄ-lo ao contĂȘiner.







Crie um arquivo entities.pyem um pacote movies:



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


e adicione as seguintes linhas dentro:



"""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),
        )


Agora precisamos adicionar uma fĂĄbrica Movieao contĂȘiner. Para isso, precisamos de um mĂłdulo providersde dependency_injector.



Vamos editar containers.py:



"""Containers module."""

from dependency_injector import containers, providers

from . import entities

class ApplicationContainer(containers.DeclarativeContainer):

    movie = providers.Factory(entities.Movie)


NĂŁo se esqueça de remover as reticĂȘncias ( ...). O container jĂĄ tem provedores e nĂŁo Ă© mais necessĂĄrio.


Vamos prosseguir para a criação finders.



Crie um arquivo finders.pyem um pacote movies:



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


e adicione as seguintes linhas dentro:



"""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]


Agora vamos adicionar CsvMovieFinderao contĂȘiner.



Vamos editar 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,
    )


VocĂȘ CsvMovieFindertem uma dependĂȘncia da fĂĄbrica Movie. CsvMovieFinderprecisa de uma fĂĄbrica, pois criarĂĄ objetos Movieao ler os dados de um arquivo. Para passar de fĂĄbrica, usamos o atributo .provider. Isso Ă© chamado de delegação do provedor. Se especificarmos uma fĂĄbrica moviecomo dependĂȘncia, ela serĂĄ chamada quando csv_finderfor criada CsvMovieFindere um objeto serĂĄ passado como uma injeção Movie. O uso do atributo .providercomo uma injeção serĂĄ passado pelo prĂłprio provedor.



Ele csv_findertambĂ©m depende de vĂĄrias opçÔes de configuração. Adicionamos um provedor onfigurationpara passar essas dependĂȘncias.



Usamos os parùmetros de configuração antes de definir seus valores. Este é o princípio pelo qual o provedor trabalha Configuration.



Primeiro usamos, depois definimos os valores.



Agora vamos adicionar os valores de configuração.



Vamos editar config.yml:



finder:

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


Os valores são definidos para o arquivo de configuração. Vamos atualizar a função main()para indicar sua localização.



Vamos editar __main__.py:



"""Main module."""

from .containers import ApplicationContainer


def main():
    container = ApplicationContainer()

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


if __name__ == '__main__':
    main()


Vamos para listers.



Crie um arquivo listers.pyem um pacote 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


e adicione as seguintes linhas dentro:



"""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
        ]


NĂłs atualizamos 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,
    )


Todos os componentes sĂŁo criados e adicionados ao contĂȘiner.



Finalmente, atualizamos a função main().



Vamos editar __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()


Tudo estĂĄ pronto. Agora vamos iniciar o aplicativo.



Vamos executar no terminal:



python -m movies


VocĂȘ verĂĄ:



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')]


Nosso aplicativo funciona com um banco de dados de filmes em csv. Também precisamos adicionar suporte de formato sqlite. Trataremos disso na próxima seção.



Trabalhando com sqlite



Nesta seção, adicionaremos outro tipo MovieFinder- SqliteMovieFinder.



Vamos editar 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]


Adicione o provedor sqlite_finderao contĂȘiner e especifique-o como uma dependĂȘncia para o provedor lister.



Vamos editar 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,
    )


O provedor sqlite_finderdepende de opçÔes de configuração que ainda não definimos. Vamos atualizar o arquivo de configuração:



Editar config.yml:



finder:

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

  sqlite:
    path: "data/movies.db"


Feito. Vamos checar.



Executamos no terminal:



python -m movies


VocĂȘ verĂĄ:



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')]


Nosso aplicativo suporta os dois formatos de banco de dados: csve sqlite. Cada vez que precisamos mudar o formato, temos que mudar o código no container. Vamos melhorar isso na próxima seção.



Provider Selector



Nesta seção, tornaremos nosso aplicativo mais flexível.



VocĂȘ nĂŁo precisarĂĄ mais fazer alteraçÔes no cĂłdigo para alternar entre os formatos csve sqlite. Implementaremos um switch com base em uma variĂĄvel de ambiente MOVIE_FINDER_TYPE:



  • Quando um MOVIE_FINDER_TYPE=csvaplicativo usa o csv.
  • Quando um MOVIE_FINDER_TYPE=sqliteaplicativo usa o sqlite.


O provedor vai nos ajudar com isso Selector. Ele escolhe um provedor com base na opção de configuração ( documentação ).



Vamos editar 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,
    )


Criamos um provedor findere o especificamos como uma dependĂȘncia para o provedor lister. O provedor finderescolhe entre provedores csv_findere sqlite_finderem tempo de execução. A escolha depende do valor do switch.



O switch é a opção de configuração config.finder.type. Quando seu valor é csvusado pelo provedor da chave csv. Da mesma forma para sqlite.



Agora precisamos ler o valor config.finder.typeda variĂĄvel de ambiente MOVIE_FINDER_TYPE.



Vamos editar __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()


Feito.



Execute os seguintes comandos no terminal:



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


A saĂ­da de cada comando serĂĄ assim:



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')]


Nesta seção, conhecemos o provedor Selector. Com esse provedor, vocĂȘ pode tornar seu aplicativo mais flexĂ­vel. O valor da chave pode ser definido a partir de qualquer fonte: arquivo de configuração, dicionĂĄrio, outro provedor.



Dica:

Substituir um valor de configuração de outro provedor permite que vocĂȘ implemente a sobrecarga de configuração em seu aplicativo sem uma reinicialização a quente.

Para fazer isso, vocĂȘ precisa usar a delegação de provedor e o .override().



Na próxima seção, adicionaremos alguns testes.



Testes



Finalmente, vamos adicionar alguns testes.



Crie um arquivo tests.pyem um pacote 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


e adicione as seguintes linhas a ele:



"""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'


Agora vamos começar a testar e verificar a cobertura:



pytest movies/tests.py --cov=movies


VocĂȘ verĂĄ:



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%


Usamos o método do .override()provedor finder. O provedor é substituído por mock. Ao entrar em contato com o provedor, a findersimulação de substituição agora serå retornada.



O trabalho estĂĄ feito. Agora vamos resumir.



ConclusĂŁo



ConstruĂ­mos um aplicativo CLI usando o princĂ­pio de injeção de dependĂȘncia. Usamos o Dependency Injector como uma estrutura de injeção de dependĂȘncia.



A vantagem que vocĂȘ obtĂ©m com o Dependency Injector Ă© o contĂȘiner.



O contĂȘiner começa a pagar quando vocĂȘ precisa entender ou alterar a estrutura do seu aplicativo. Com um contĂȘiner, isso Ă© fĂĄcil, porque todos os componentes do aplicativo e suas dependĂȘncias sĂŁo explicitamente definidos em um sĂł lugar:



"""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,
    )




Um contĂȘiner como um mapa de seu aplicativo. VocĂȘ sempre sabe o que depende do quĂȘ.



PS: perguntas e respostas



Nos comentårios do tutorial anterior, perguntas interessantes foram feitas: "por que isso é necessårio?", "Por que precisamos de um framework?", "Como o framework ajuda na implementação?"



Preparei as respostas:



O que Ă© injeção de dependĂȘncia?



  • Ă© o princĂ­pio que reduz o acoplamento e aumenta a coesĂŁo


Por que devo usar injeção de dependĂȘncia?



  • seu cĂłdigo se torna mais flexĂ­vel, compreensĂ­vel e melhor testĂĄvel
  • vocĂȘ tem menos problemas quando precisa entender como funciona ou alterĂĄ-lo


Como começo a aplicar injeção de dependĂȘncia?



  • vocĂȘ começa a escrever cĂłdigo seguindo o princĂ­pio de injeção de dependĂȘncia
  • vocĂȘ registra todos os componentes e suas dependĂȘncias no contĂȘiner
  • quando vocĂȘ precisa de um componente, vocĂȘ o obtĂ©m do contĂȘiner


Por que preciso de uma estrutura para isso?



  • vocĂȘ precisa de uma estrutura para nĂŁo criar a sua prĂłpria. O cĂłdigo de criação do objeto serĂĄ duplicado e difĂ­cil de alterar. Para evitar isso, vocĂȘ precisa de um contĂȘiner.
  • a estrutura fornece um contĂȘiner e provedores
  • os provedores controlam o tempo de vida dos objetos. VocĂȘ precisarĂĄ de fĂĄbricas, singletons e objetos de configuração
  • o contĂȘiner serve como uma coleção de provedores


Que preço estou pagando?



  • vocĂȘ precisa especificar explicitamente as dependĂȘncias no contĂȘiner
  • este Ă© um trabalho adicional
  • começarĂĄ a pagar dividendos quando o projeto começar a crescer
  • ou 2 semanas apĂłs sua conclusĂŁo (quando vocĂȘ esquecer quais decisĂ”es vocĂȘ tomou e qual Ă© a estrutura do projeto)


Conceito de injetor de dependĂȘncia



AlĂ©m disso, descreverei o conceito de injetor de dependĂȘncia como uma estrutura.



O injetor de dependĂȘncia Ă© baseado em dois princĂ­pios:



  • ExplĂ­cito Ă© melhor do que implĂ­cito (PEP20).
  • NĂŁo faça mĂĄgica com seu cĂłdigo.


Como o Dependency Injector Ă© diferente de outras estruturas?



  • Sem vinculação automĂĄtica. A estrutura nĂŁo vincula dependĂȘncias automaticamente. A introspecção, vinculação por nomes de argumento e / ou tipos nĂŁo Ă© usada. Porque "explĂ­cito Ă© melhor do que implĂ­cito (PEP20)".
  • NĂŁo polui o cĂłdigo do seu aplicativo. Seu aplicativo desconhece e Ă© independente do Injetor de DependĂȘncias. Sem @injectdecoradores, anotaçÔes, remendos ou outros truques de mĂĄgica.


O Dependency Injector oferece um contrato simples:



  • VocĂȘ mostra ao framework como coletar objetos
  • A estrutura os coleta


A força do Injetor de DependĂȘncia estĂĄ em sua simplicidade e objetividade. É uma ferramenta simples para implementar um princĂ­pio poderoso.



Qual Ă© o prĂłximo?



Se vocĂȘ estiver interessado, mas hesite, minha recomendação Ă© esta:



tente esta abordagem por 2 meses. Ele nĂŁo Ă© intuitivo. Leva tempo para se acostumar e sentir. Os benefĂ­cios se tornam tangĂ­veis quando o projeto cresce para mais de 30 componentes em um contĂȘiner. Se vocĂȘ nĂŁo gosta, nĂŁo perca muito. Se vocĂȘ gostar, obtenha uma vantagem significativa.





Eu ficaria feliz em receber feedback e responder perguntas nos comentĂĄrios.



All Articles