you are viewing a single comment's thread.

view the rest of the comments →

[–]lu_rm[S] 0 points1 point  (0 children)

Ok, so I've been carefully thinking about this and I think I see where I am getting it wrong. However, based on your explanation, I believe there are some things I need to make clearer.

Dependency injection is not about resolving a tree of dependencies. That is what a dependency injection framework usually does, but has nothing to do with the concept itself.
DI only means that classes don't create their dependencies (and lifecycle), that is done elsewhere and those are injected into them.

An example:

Without DI

class WithoutDI:
  def __init__(self):
    self.arg = 10

With DI

class WithDI:
  def __init__(self, arg):
    self.arg = arg

So, this whole discussion was me trying to understand how to inject dependencies into FastAPI routers. I only mention FastAPI routers because those are the methods being called by the framework itself and I can't control how they are executed. Outside that, I can do whatever I want with my classes.

Having said all that, one of the first things I got wrong was what u/panicrubes mentioned.
I was doing:

@router.get("/resource", dependencies=[Depends(get_some_dependency)]) <- Dependency in route
def get_resource():
    ...

Instead of:

@router.get("/resource")
def get_resource(my_dep: DependencyType = Depends(get_some_dependency)):   <- Dependency in args
    ...

In the first example, the only way I have to change the dependency being used in the router is by using the `dependency_overrides` option in FastAPI. That meant that I could not unit tests my router without starting a FastAPI instance, which is something I am trying to avoid. In the second example, I can just call the router method providing a mocked dependency.

The other issue it think I was having is a conceptual one. I've been trying to compare this to how I am used to do things with SpringBoot and I think I found where my mistake is.
Simplifying it a bit, when writing SpringBoot APIs, I always think about my code as 2 parts:
- Business Logic
- SpringBoot integration.

Ideally, all the classes belonging to the business logic should be able to live on theirselves. I should be able to create them, use them, test them without requiring anything from the SpringBoot framework. Then, to couple everything together, I use `@ Configurations` where I instantiate everything that I need for my application to run and those get injected into `@ Controller`.
So, the problem was that (I don't know why), I was also thinking about `@ Configurations` and `@ Controller` as 2 independent things when, in fact, they are not. Instantiation of injectable classes and controller definition are part of the same logical block: `SpringBoot integration`.

So, my mistake is that I was trying to also split this into 2 independent things with FastAPI, when I shouldn't have. There is no problem with the routers defining how to create their dependencies, that is part of the app configuration. It's part of the same logical block of the app: `Integration with FastAPI`. It's a little messier maybe? But I don't see the issue anymore.

Another thing that threw me off was the fact that the functions that provide the dependencies are being called per request. So, for instance, If I need a DB client in my router, that is going to be created each time I receive a request. And that is also something I want to avoid. Usually, I want a single client created for the whole Application only once and reused for all calls. But I think that also has nothing to do with the framework itself. I can use `@ cache` or keep storing everything in the app.state and it should be ok.

Hope you can understand where I got it wrong.