From 6730306841059f05e31cd8abf6819c520717b554 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Tue, 28 Oct 2014 00:24:30 +0000 Subject: [PATCH] Add FAQ and good/bad practices to the docs --- docs/faq.rst | 29 ++++++++ docs/index.rst | 2 + docs/practices.rst | 166 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 docs/faq.rst create mode 100644 docs/practices.rst diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 0000000..fcc640d --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,29 @@ +.. _faq: + +Frequently Asked Questions +========================== + +* If I use :func:`~injector.inject` or scope decorators on my classess will + I be able to create instances of them without using Injector? + + Yes. Scope decorators don't change the way you can construct your class + instances without Injector interaction. + + :func:`~injector.inject` changes the constructor semantics slightly + if you use it to decorate your class - in this case you need to use + keyword arguments to pass values to the constructor. + + For example: + + .. code-block:: python + + @inject(s=str) + class X(object): + pass + + + # will fail + X('a') + + # will work + X(s='a') diff --git a/docs/index.rst b/docs/index.rst index 6a2459e..7c5f54a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,3 +30,5 @@ Contents: scopes logging api + faq + practices diff --git a/docs/practices.rst b/docs/practices.rst new file mode 100644 index 0000000..4353156 --- /dev/null +++ b/docs/practices.rst @@ -0,0 +1,166 @@ +.. _practices: + +Good and bad practices +====================== + +Side effects +```````````` + +You should avoid creating side effects in your modules for two reasons: + +* Side effects will make it more difficult to test a module if you want to do it +* Modules expose a way to acquire some resource but they don't provide any way + to release it. If, for example, your module connects to a remote server while + creating a service you have no way of closing that connection unless the + service exposes it. + + +Injecting into constructors vs injecting into other methods +``````````````````````````````````````````````````````````` + +In general you should prefer injecting into constructors to injecting into +other methods because: + +* it can expose potential issues earlier (at object construction time rather + than at the method call) +* it exposes class' dependencies more openly. Constructor injection: + + .. code-block:: python + + class Service1(object): + @inject(http_client=HTTP) + def __init__(self, http_client): + self.http_client = http_client + # some other code + + # tens or hundreds lines of code + + def method(self): + # do something + pass + + Regular method injection: + + .. code-block:: python + + class Service2(object): + def __init__(self): + # some other code + + # tens or hundreds lines of code + + @inject(http_client=HTTP) + def method(self, http_client): + # do something + pass + + + In first case you know all the dependencies by looking at the class' + constructor, in the second you don't know about ``HTTP`` dependency until + you see the method definition. + + Slightly different approach is suggested when it comes to Injector modules - + in this case injecting into their constructors (or ``configure`` methods) + would make the injection process dependent on the order of passing modules + to Injector and therefore quite fragile. See this code sample: + + .. code-block:: python + + A = Key('A') + B = Key('B') + + class ModuleA(Module): + @inject(a=A) + def configure(self, binder, a): + pass + + class ModuleB(Module): + @inject(b=B) + def __init__(self, b): + pass + + class ModuleC(Module): + def configure(self, binder): + binder.bind(A, to='a') + binder.bind(B, to='b') + + + # error, at the time of ModuleA processing A is unbound + Injector([ModuleA, ModuleC]) + + # error, at the time of ModuleB processing B is unbound + Injector([ModuleB, ModuleC]) + + # no error this time + Injector([ModuleC, ModuleA, ModuleB]) + + +Doing too much in modules and/or providers +`````````````````````````````````````````` + +An implementation detail of Injector: Injector and accompanying classes are +protected by a lock to make them thread safe. This has a downside though: +in general only one thread can use dependency injection at any given moment. + +In best case scenario you "only" slow other threads' dependency injection +down. In worst case scenario (performing blocking calls without timeouts) you +can **deadlock** whole application. + +**It is advised to avoid performing any IO, particularly without a timeout +set, inside modules code.** + +As an illustration: + +.. code-block:: python + + from threading import Thread + from time import sleep + + from injector import inject, Injector, Key, Module, provides + + SubA = Key('SubA') + A = Key('A') + B = Key('B') + + + class BadModule(Module): + @provides(A) + @inject(suba=SubA) + def provide_a(self, suba): + return suba + + @provides(SubA) + def provide_suba(self): + print('Providing SubA...') + while True: + print('Sleeping...') + sleep(1) + + # This never executes + return 'suba' + + @provides(B) + def provide_b(self): + return 'b' + + + injector = Injector([BadModule]) + + thread = Thread(target=lambda: injector.get(A)) + + # to make sure the thread doesn't keep the application alive + thread.daemon = True + thread.start() + + # This will never finish + injector.get(B) + print('Got B') + + +Here's the output of the application:: + + Providing SubA... + Sleeping... + Sleeping... + Sleeping... + (...)