Add FAQ and good/bad practices to the docs

This commit is contained in:
Jakub Stasiak 2014-10-28 00:24:30 +00:00
parent c4a1354dc0
commit 6730306841
3 changed files with 197 additions and 0 deletions

29
docs/faq.rst Normal file
View File

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

View File

@ -30,3 +30,5 @@ Contents:
scopes scopes
logging logging
api api
faq
practices

166
docs/practices.rst Normal file
View File

@ -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...
(...)