Short Story

So, a few days ago i got a code review regarding my codes that will be merged. And somehow, i got an interesting lesson about it. After reviewing, my senior coworker said to me “i’ve seen you had some DI (dependency injection) in your code”. At some point, i actually don’t really understand how dependency injection actually works in python. Its very reasonable if im being honest, in python which is very dynamic typing, the utility of using DI itself is honestly not subtle from developer perspective (or at least from python developer itself), because most of the time we always implement mixins and simple object-oriented concept such as singleton, decorator, inheritance.

From there i asked, “what kind of DI that what you mean?” and he asked back “seriously? you don’t know from the code that you wrote yourself?” at the time, in between confusing or embarassing because i still don’t know what he means about DI in my code. And then he showed me the DI line that was in my program, and i frowned, “is that kind of DI? i think its just a normal mixins or i just simply call the instances?” and then he said try to find out for yourself how DI or IoC works. So, with some curiosity that is still hanging in my side, i tried to understand it again and the “Oh Moment” showing up at that time. That heavily mindblowing lesson learned from me.

What’s Dependency Injection?

Tl;dr; i’ll explain this as simple and short as possible based on what i understand. So its just some kind of my re-phrase as per my understanding of the concept. Dependency injection is a principle in the object-oriented paradigm, to reduce high coupling and increase low cohesion. Basically, cohesion and coupling are how closely the components are joined together. So, i ended up read various sources including Uncle Martin himself, but i’m still confused conceptually. But, i can abbreviate it like this:

  • If the coupling between components (classes, modules) is very tight = difficult to separate. it means, high coupling
  • If the coupling between components is not tight = easy to separate. it means, low coupling

By all means, the lower the coupling between components, the easier our code will be, it’s also flexible to test and separate. It also makes it easier to reduce the number of constructors that will be used later in other objects

What’s the fuss about Dependency Injection on Python?

As i just said before, most of the time i don’t know that i’ve been using DI in python entirely because there is a valid reason:

  • There is an opinion that dependency injection doesn’t work for it as well as it does for Java (or static typing language). and also there is an opinion that a dependency injection framework is something that Python developer rarely needs. And i always believe that
  • In python itself, the utility will not appear at all because python itself is a dynamic programming language. You can actually easily to inject an object into another object with simple inheritance
  • In python, most of the time or at least for me, i always use MRO (method resolution order) instead of using DI. I love this, since when i’m using MRO, i can easily to override the methods of the parent class. Many big frameworks like Django, Django REST Framework use this concept
  • Dependency Injection in python is a little different, because we don’t have the container and providers themselves built-in (unlike in Java which extensively uses the concept), although you can use DI without the help of libraries, you will still have trouble trying to do the assembly of objects that have been tied or you most likely will get the trouble when injecting some dependency. The simple thing is, natively from python itself it doesn’t provide DI

How to use dependency injection in Python?

Based on the story above, i will use it as an example:

  1. Lets say, we have a codebase which functions as an HTTP client (make a request to the expected URL based on the request or the arguments that we’ev passed)
class HttpClient:
    def __init__(self, base_url: str, timeout: int) -> None:
        self.base_url = base_url # this one is depedency
        self.timeout = timeout # this one too
        self.is_valid_url = invalid_url(self.base_url) # and this
        ...
 
class ApiServiceA:
    def __init__(self, client: HttpClient) -> None:
        self.client = client # dependency is injected
        ...
  1. Based from the code above, the dependency injection implementation itself is relatively easy to implement but the problem is “it looks like ordinary inheritance and looks like vanishing utility of DI itself” and yes, i’m very certain this also 100 % to be true. It’s like ordinary inheritance or just some single instantiation where the attributes of HttpClient() can be accessed in ApiServiceA()
  2. With such an implementation, you can easily and flexibly use the client attribute in ApiServiceA() to instantiate the attributes in the HttpClient() so that all of them can be accessed in other classes or modules. For example:
class RunApiService(ApiServiceA):
    # this expected to run ApiServiceA
    def run_program(self):
        run = self.client.base_url
        if not run:
            timeout += self.client.timeout
  1. However, “with greater flexibility, comes with price” according to the DI concept in general, how do we do the split for the base_url object, timeout or in sometime later we want to change the value? This is where the DI problem in python comes in, because the assembly code might get duplicated and it’ll become harder to change the application structure. Apparently, the answer for duplicate in question here is that you will create a new instance for the RunApiService() class
# to order assembly the "run_program" object in RunApiService class
run_api_instances = RunApiService(...)
run_api_instances.run_program.update(...) # after assembly

Another lesson that i’ve learn from the hard way, when using DI is: somehow i become “greedy” and abusing all injection into only one class. This is obviously and cleary against of SRP, but to be frank, i didn’t know about this either until my senior engineer said that, “once we know that the benefits of DI are enormous and eliminate complexity so quickly and increase flexibility of use, naturally we will be happy with that and we will inject everything into one component”. For example that my previous code related to abusing the DI is something like this:

class HttpClient:
    def __init__(
        client: httpx.Client = Any,
        response: httpx.Response = Any,
        request: httpx.Request = Any,
        is_valid: is_valid_url = bool,
        session: httpx.Session = Any,
    ): # injecting all httpx dependency into single constructor in one class
        self.client = client
        ...

Ending

So the next question is, when will you use dependency injection in python? it depends on the case you are working on. Most of the time, if you are building a large web application or system that is heavily on web framework, you don’t need to use DI because they all already implement DI in the built-in API. You can simply use multiple inheritance or mixins to reduce the coupling between components with each other. If we talk about maintainability and testable system (this is based on my own case), i would like to say which i unconsciously use DI concept, then personally i highly recommend you to implement it too. One or two things that i noticed after i applied DI was:

  • Loosely coupled, somehow every component or new class that i’ll create is very easy to combine both in terms of doing the injecting test (API, database, UI, etc) and its functionality
  • Clearness, as zen of python said “Explicit better than implicit” because all components or depdencies that you have made are explicitly placed in separate containers (it means, you can directly call the HttpClient() class into single attribute and call it again for those attributes in other classes)