Skip to content

Commit 14c2eca

Browse files
committed
Update "What is What is dependency injection?" documentation page
1 parent 2cf5efa commit 14c2eca

File tree

4 files changed

+211
-121
lines changed

4 files changed

+211
-121
lines changed

README.rst

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,25 @@ It helps implementing the dependency injection principle.
5757
What is dependency injection?
5858
-----------------------------
5959

60-
Dependency injection is a principle that helps to decrease coupling and increase cohesion. Your
61-
code becomes more flexible, clear and it is easier to test it.
60+
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
61+
62+
What is coupling and cohesion?
63+
64+
Coupling and cohesion are about how tough the components are tied.
65+
66+
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
67+
to disassemble.
68+
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
69+
assemble back or assemble a different way. It is an alternative to high coupling.
70+
71+
When the cohesion is high the coupling is low.
72+
73+
High cohesion brings the flexibility. Your code becomes easier to change and to test.
6274

6375
How to implement dependency injection?
6476
--------------------------------------
6577

66-
Objects do not create each other anymore. They provide a way to inject the needed dependencies
67-
instead.
78+
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
6879

6980
Before:
7081

@@ -114,14 +125,22 @@ After:
114125
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
115126
116127
117-
Flexibility comes with a price: now you need to assemble your objects like this
128+
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
129+
timeout from a configuration file or even get them from a database.
130+
131+
``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a
132+
stub or other compatible object.
133+
134+
Flexibility comes with a price.
135+
136+
Now you need to assemble your objects like this
118137
``Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))``. The assembly code might get
119138
duplicated and it'll become harder to change the application structure.
120139

121140
What does Dependency Injector do?
122141
---------------------------------
123142

124-
``Dependency Injector`` helps you assemble the objects.
143+
``Dependency Injector`` helps to assemble the objects.
125144

126145
It provides you the container and the providers that help you describe objects assembly. When you
127146
need an object you get it from the container. The rest of the assembly work is done by the
@@ -170,8 +189,11 @@ framework:
170189
171190
Retrieving of the ``Service`` instance now is done like this ``container.service()``.
172191

173-
Also ``Dependency Injector`` provides a bonus in overriding any of the providers with the
174-
``.override()`` method:
192+
The responsibility of assembling the object is consolidated in the container. When you need to
193+
make a change you do it in one place.
194+
195+
When doing the testing you call the ``container.api_client.override()`` to replace the real API
196+
client with a mock:
175197

176198
.. code-block:: python
177199
@@ -180,7 +202,6 @@ Also ``Dependency Injector`` provides a bonus in overriding any of the providers
180202
181203
with container.api_client.override(mock.Mock()):
182204
service = container.service()
183-
assert isinstance(service.api_client, mock.Mock)
184205
185206
It helps in a testing. Also you can use it for configuring project for the different environments:
186207
replace an API client with a stub on the dev or stage.
8.16 KB
Loading

docs/introduction/what_is_di.rst

Lines changed: 177 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,190 @@
1-
What is dependency injection and inversion of control?
2-
------------------------------------------------------
1+
What is dependency injection?
2+
-----------------------------
33

44
.. meta::
5-
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
6-
:description: This article provides definition of dependency injection,
7-
inversion of control and dependency inversion. It contains
8-
example code in Python that is refactored to be following
9-
inversion of control principle.
10-
11-
Definition
12-
~~~~~~~~~~
13-
14-
Wikipedia provides quite good definitions of dependency injection pattern
15-
and related principles:
16-
17-
.. glossary::
18-
19-
`Dependency injection`_
20-
In software engineering, dependency injection is a software design
21-
pattern that implements inversion of control for resolving
22-
dependencies. A dependency is an object that can be used (a service).
23-
An injection is the passing of a dependency to a dependent object (a
24-
client) that would use it. The service is made part of the client's
25-
state. Passing the service to the client, rather than allowing a
26-
client to build or find the service, is the fundamental requirement of
27-
the pattern.
28-
29-
Dependency injection allows a program design to follow the dependency
30-
inversion principle. The client delegates to external code (the
31-
injector) the responsibility of providing its dependencies. The client
32-
is not allowed to call the injector code. It is the injecting code
33-
that constructs the services and calls the client to inject them. This
34-
means the client code does not need to know about the injecting code.
35-
The client does not need to know how to construct the services. The
36-
client does not need to know which actual services it is using. The
37-
client only needs to know about the intrinsic interfaces of the
38-
services because these define how the client may use the services.
39-
This separates the responsibilities of use and construction.
40-
41-
`Inversion of control`_
42-
In software engineering, inversion of control (IoC) describes a design
43-
in which custom-written portions of a computer program receive the
44-
flow of control from a generic, reusable library. A software
45-
architecture with this design inverts control as compared to
46-
traditional procedural programming: in traditional programming, the
47-
custom code that expresses the purpose of the program calls into
48-
reusable libraries to take care of generic tasks, but with inversion
49-
of control, it is the reusable code that calls into the custom, or
50-
task-specific, code.
51-
52-
Inversion of control is used to increase modularity of the program and
53-
make it extensible, and has applications in object-oriented
54-
programming and other programming paradigms. The term was popularized
55-
by Robert C. Martin and Martin Fowler.
56-
57-
The term is related to, but different from, the dependency inversion
58-
principle, which concerns itself with decoupling dependencies between
59-
high-level and low-level layers through shared abstractions.
60-
61-
`Dependency inversion`_
62-
In object-oriented programming, the dependency inversion principle
63-
refers to a specific form of decoupling software modules. When
64-
following this principle, the conventional dependency relationships
65-
established from high-level, policy-setting modules to low-level,
66-
dependency modules are reversed, thus rendering high-level modules
67-
independent of the low-level module implementation details. The
68-
principle states:
69-
70-
+ High-level modules should not depend on low-level modules.
71-
Both should depend on abstractions.
72-
+ Abstractions should not depend on details.
73-
Details should depend on abstractions.
74-
75-
The principle inverts the way some people may think about
76-
object-oriented design, dictating that both high- and low-level
77-
objects must depend on the same abstraction.
78-
79-
Example
80-
~~~~~~~
81-
82-
Let's go through the code of ``example.py``:
83-
84-
.. literalinclude:: ../../examples/di_demo/example.py
85-
:language: python
86-
87-
At some point, things defined above mean, that the code from ``example.py``,
88-
could look different, like in ``example_di.py``:
89-
90-
.. literalinclude:: ../../examples/di_demo/example_di.py
91-
:language: python
92-
93-
Best explanation, ever
94-
~~~~~~~~~~~~~~~~~~~~~~
95-
96-
Some times ago `user198313`_ posted awesome `question`_ about dependency
97-
injection on `StackOverflow`_:
98-
99-
.. note::
100-
101-
How to explain dependency injection to a 5-year-old?
102-
103-
And `John Munsch`_ provided absolutely Great answer:
104-
105-
.. note::
106-
107-
When you go and get things out of the refrigerator for yourself, you can
5+
:keywords: Python,DI,Dependency injection,Low coupling,High cohesion
6+
:description: This page provides a Python example of what is dependency injection. It tells
7+
about benefits of coupling and high cohesion.
8+
9+
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
10+
11+
.. image:: images/coupling-cohesion.png
12+
13+
What is coupling and cohesion?
14+
15+
Coupling and cohesion are about how tough the components are tied.
16+
17+
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
18+
to disassemble.
19+
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
20+
assemble back or assemble a different way. It is an alternative to high coupling.
21+
22+
When the cohesion is high the coupling is low.
23+
24+
High cohesion brings the flexibility. Your code becomes easier to change and to test.
25+
26+
The example
27+
~~~~~~~~~~~
28+
29+
How does dependency injection helps to achieve high cohesion?
30+
31+
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
32+
33+
Before:
34+
35+
.. code-block:: python
36+
37+
import os
38+
39+
40+
class ApiClient:
41+
42+
def __init__(self):
43+
self.api_key = os.getenv('API_KEY') # <-- the dependency
44+
self.timeout = os.getenv('TIMEOUT') # <-- the dependency
45+
46+
47+
class Service:
48+
49+
def __init__(self):
50+
self.api_client = ApiClient() # <-- the dependency
51+
52+
53+
if __name__ == '__main__':
54+
service = Service()
55+
56+
57+
After:
58+
59+
.. code-block:: python
60+
61+
import os
62+
63+
64+
class ApiClient:
65+
66+
def __init__(self, api_key: str, timeout: int):
67+
self.api_key = api_key # <-- the dependency is injected
68+
self.timeout = timeout # <-- the dependency is injected
69+
70+
71+
class Service:
72+
73+
def __init__(self, api_client: ApiClient):
74+
self.api_client = api_client # <-- the dependency is injected
75+
76+
77+
if __name__ == '__main__':
78+
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
79+
80+
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
81+
timeout from a configuration file or even get them from a database.
82+
83+
``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a
84+
stub or other compatible object.
85+
86+
Flexibility comes with a price.
87+
88+
Now you need to assemble your objects like this
89+
``Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))``. The assembly code might get
90+
duplicated and it'll become harder to change the application structure.
91+
92+
Here comes the ``Dependency Injector``.
93+
94+
``Dependency Injector`` helps to assemble the objects.
95+
96+
It provides you the container and the providers that help you describe objects assembly. When you
97+
need an object you get it from the container. The rest of the assembly work is done by the
98+
framework:
99+
100+
.. code-block:: python
101+
102+
from dependency_injector import containers, providers
103+
104+
105+
class ApiClient:
106+
107+
def __init__(self, api_key: str, timeout: int):
108+
self.api_key = api_key
109+
self.timeout = timeout
110+
111+
112+
class Service:
113+
114+
def __init__(self, api_client: ApiClient):
115+
self.api_client = api_client
116+
117+
118+
class Container(containers.DeclarativeContainer):
119+
120+
config = providers.Configuration()
121+
122+
api_client = providers.Singleton(
123+
ApiClient,
124+
api_key=config.api_key,
125+
timeout=config.timeout.as_int(),
126+
)
127+
128+
service = providers.Factory(
129+
Service,
130+
api_client=api_client,
131+
)
132+
133+
134+
if __name__ == '__main__':
135+
container = Container()
136+
container.config.api_key.from_env('API_KEY')
137+
container.config.timeout.from_env('TIMEOUT')
138+
139+
service = container.service()
140+
141+
Retrieving of the ``Service`` instance now is done like this ``container.service()``.
142+
143+
Objects assembling is consolidated in the container. When you need to make a change you do it in
144+
one place.
145+
146+
When doing the testing you call the ``container.api_client.override()`` to replace the real API
147+
client with a mock:
148+
149+
.. code-block:: python
150+
151+
from unittest import mock
152+
153+
154+
with container.api_client.override(mock.Mock()):
155+
service = container.service()
156+
157+
How to explain dependency injection to a 5-year-old?
158+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159+
160+
Some time ago `user198313`_ posted this `question`_ on the `StackOverflow`_.
161+
162+
`John Munsch`_ provided a great answer:
163+
164+
*When you go and get things out of the refrigerator for yourself, you can
108165
cause problems. You might leave the door open, you might get something
109166
Mommy or Daddy doesn't want you to have. You might even be looking for
110-
something we don't even have or which has expired.
167+
something we don't even have or which has expired.*
111168

112-
What you should be doing is stating a need, "I need something to drink
169+
*What you should be doing is stating a need, "I need something to drink
113170
with lunch," and then we will make sure you have something when you sit
114-
down to eat.
171+
down to eat.*
115172

173+
What's next?
174+
~~~~~~~~~~~~
116175

117-
.. disqus::
176+
Choose one of the following as a next step:
118177

178+
+ Pass one of the tutorials:
179+
+ :ref:`cli-tutorial`
180+
+ :ref:`flask-tutorial`
181+
+ :ref:`aiohttp-tutorial`
182+
+ :ref:`asyncio-daemon-tutorial`
183+
+ Know more about the :ref:`providers`
184+
+ Go to the :ref:`contents`
185+
186+
.. disqus::
119187

120-
.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection
121-
.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
122-
.. _Dependency inversion: https://en.wikipedia.org/wiki/Dependency_inversion_principle
123188
.. _StackOverflow: http://stackoverflow.com/
124189
.. _question: http://stackoverflow.com/questions/1638919/how-to-explain-dependency-injection-to-a-5-year-old/1639186
125190
.. _user198313: http://stackoverflow.com/users/198313/user198313

docs/main/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ that were made in every particular version.
77
From version 0.7.6 *Dependency Injector* framework strictly
88
follows `Semantic versioning`_
99

10+
Development version
11+
-------------------
12+
- Update "What is What is dependency injection?" documentation page.
13+
1014
3.37.0
1115
------
1216
- Update index documentation page.

0 commit comments

Comments
 (0)