Легковесный Dependency Injection

Легковесный Dependency Injection

Алексей Пирогов (@alex_pir, "БАРС Груп")

"PiterPy-2015"

qr

Presenter Notes

DI

DI - реализация IoC для библиотек

  • берет на себя поиск и импорт модулей/сервисов
  • обеспечивает Dependency Inversion Principle - слабую связанность
  • обеспечивает прозрачную взаимозаменяемость модулей

Presenter Notes

Концепция

  • Service -> Client
  • сервис создается, как часть состояния клиента
  • сервис предоставляется клиенту извне!
  • создание зависимостей отделено от создания объекта
  • в отличие от service locator клиенту не нужно знать о существовании и устройстве системы

Presenter Notes

Преимущества

  • DI можно сравнительно легко применить к существующему коду
  • высокая степень изоляции увеличивает
    • тестируемость
    • сопровождаемость
    • пригодность к переиспользованию
  • вынос конфигурации из кода в конфигурационные файлы
  • упрощение коллективной разработки

Presenter Notes

Недостатки

  • потенциальное усложнение понимания кода - нужно просматривать больше файлов
  • уменьшение степени инкапсуляции - зависимости становятся явными

Presenter Notes

Виды DI

  • constructor injection
  • setter injection
  • interface injection

Presenter Notes

Примеры

Presenter Notes

Код без использования DI

class Dependency:
    ...
class Client:
    def __init__(self):
        self._dep = Dependency(...)
        ...
    def do_thing(self):
        self._dep.populate()

Presenter Notes

Constructor injection

class Dependency:
    ...
class Client:
    def __init__(self, dep):
        self._dep = dep
        ...
# где-то в инжекторе(контейнере)
def get_client(...):
    ...
    dep = Dependency(...)
    return Client(dep=dep)

Presenter Notes

Setter injection

class Client:
    def set_dep(self, dep):
        self._dep = dep
    ...
class Dependency:
    ...
# где-то в инжекторе(контейнере)
def get_client(...):
    ...
    dep = Dependency(...)
    client = Client()
    client.set_dep(dep)
    return client

Presenter Notes

Interface injection

class Client:
    def set_dep(self, dep):
        self._dep = dep
    ...
class Dependency:
    def inject(self, client):
        client.set_dep(self)
    ...
# где-то в инжекторе(контейнере)
def get_client(...):
    ...
    dep = Dependency(...)
    client = Client()
    dep.inject(client)
    return client

Presenter Notes

Существующие реализации

Presenter Notes

dependency_injection

def foo(bar, baz):
    return bar + baz

def inject_dependencies(func):
    my_state = {
        'bar': 1, 'baz': 2, 'bloo': 'blee'
    }
    dependencies = resolve_dependencies(
        func, my_state
    )
    return func(*dependencies.as_args)

Presenter Notes

zuice

import zuice

class BlogPostLister(zuice.Base):
    _fetcher = zuice.dependency(
        BlogPostFetcher)

bindings = zuice.Bindings()
bindings.bind(
    BlogPostFetcher
).to_instance(
    BlogPostFetcher())
...
injector = zuice.Injector(bindings)
injector.get(BlogPostLister)

Presenter Notes

Injector

from injector import Injector, inject, Key
GreetingType = Key('GreetingType')

@inject(greeting_type=GreetingType)
def greet(greeting_type, who):
    print('%s, %s'
          % (greeting_type, who))

def configure(binder):
    binder.bind(GreetingType, to='Hello')

>>> injector = Injector(configure)
>>> greet_wrapper = injector.get(greet)
>>> greet_wrapper(who='John')
Hello, John

Presenter Notes

pinject

>>> class OuterClass(object):
...     def __init__(self, inner_class):
...         self.inner_class = inner_class
...
>>> class InnerClass(object):
...     def __init__(self):
...         self.forty_two = 42
...
>>> obj_graph = pinject.new_object_graph()
>>> outer_class = obj_graph.provide(OuterClass)
>>> print outer_class.inner_class.forty_two
42

Presenter Notes

pinject

>>> class SomeClass(object):
...     def __init__(self, long_name):
...         self.long_name = long_name
>>> class LongName(object):
...     def __init__(self):
...         self.foo = 'foo'
>>> class MyBindingSpec(pinject.BindingSpec):
...     def configure(self, bind):
...         bind('long_name', to_class=LongName)
...
>>> obj_graph = pinject.new_object_graph(
...     binding_specs=[MyBindingSpec()])
>>> some_class = obj_graph.provide(SomeClass)
>>> print some_class.long_name.foo
'foo'

Presenter Notes

YADIC

Presenter Notes

yadic

Yet Another Dependency Injection Container

  • constructor injection
  • конфигурация - словарь (JSON, YAML...)
  • можно писать, не пользуясь декораторами, метаклассами и проч

Presenter Notes

Примеры

Presenter Notes

Конфигурируемый код

class Vehicle:   # boat, truck...
    def __init__(self, engine, actuator):
...
class Engine:    # steam, diesel...
    def __init__(self, fuel):
...
class Fuel:      # coal, gasoil...
...
class Actuator:  # rotor, wheels...
...

Presenter Notes

Типичная конфигурация

vehicle:
  __default__:
    __realization__: 'domain.Vehicle'
  Boat:
    engine: SteamEngine
    actuator: Rotor

engine:
  SteamEngine:
    __realization__: 'domain.engines.SteamEngine'
    fuel: Coal

fuel:
  Coal:
    __realization__: 'domain.fuel.Coal'

Presenter Notes

Использование

from yadic.container import Container

container = Container(
    json.load(open('config.json')))

controller = container.get('vehicle', 'Boat')

Presenter Notes

Вариации

const:
  pi:
    __realization__: 'constants.PI'
    __type__: static

  someSum:
    __realization__: 'funcs.add'
    $x: 10
    $y: 32

model:
  User:
    'profile:model': Profile
    'relations:model':
      - Avatar
      - History

Presenter Notes

Вариации

model:
  User:
    db_connection: Main

db_connection:
  Main:
    __type__: singleton
    $connection_string: "http://localhost:1234"

Presenter Notes

Визуализация

Presenter Notes

Пример

vehicles.json:

{"vehicle": {"Boat": {"engine": "SteamEngine",
                      "actuator": "Rotor"},
             "Truck": {"engine": "Diesel",
                       "actuator": "Wheel"}},

"engine": {"SteamEngine": {"fuel": "Gasoil"},
           "Diesel": {"fuel": "Gasoil"}},

"actuator": {"Wheel": {},
             "Rotor": {}},

"fuel": {"Gasoil": {}}}

Presenter Notes

dot-файл

$ python -m yadic.dot vehicles.json
digraph container {
    "engine:SteamEngine" -> "fuel:Gasoil";
    "engine:Diesel" -> "fuel:Gasoil";
    "vehicle:Truck" -> "engine:Diesel";
    "vehicle:Truck" -> "actuator:Wheel";
    "vehicle:Boat" -> "engine:SteamEngine";
    "vehicle:Boat" -> "actuator:Rotor";
}

Presenter Notes

Рендеринг (GraphViz)

$ python -m yadic.dot vehicles.json \
| dot -Tpng > vehicles.png

vehicles

Presenter Notes

Выборочное отображение

$ python -m yadic.dot vehicles.json \
--include="vehicle:Boat"            \
| dot -Tpng > vehicles.png

vehicles

Presenter Notes

Выборочное отображение

$ python -m yadic.dot vehicles.json \
--exclude="actuator"                \
| dot -Tpng > vehicles.png

vehicles

Presenter Notes

Выборочное отображение

$ python -m yadic.dot vehicles.json \
--include="vehicle;fuel"            \
--exclude="engine"                  \
| dot -Tpng > vehicles.png

vehicles

Presenter Notes

Ссылки

yadic:

Упомянутые в презентации библиотеки для DI

Presenter Notes

Вопросы

Presenter Notes

Спасибо за внимание!

Presenter Notes