Refactoring for Dependency Injection (Circular Dependecies)

TLDR: I want to refactor my application for dependency injection and I am not sure how to. I started creating Classes for basically every resource / table in DB. They all contain functions related to the resource like this:
interface UserRepository {
getByID(id: number): Promise<User>
}
class PrismaUserRepository implements UserRepository {
getByID(id: number) => prisma.user.findUnique({ where: { id }})
}
interface UserRepository {
getByID(id: number): Promise<User>
}
class PrismaUserRepository implements UserRepository {
getByID(id: number) => prisma.user.findUnique({ where: { id }})
}
My Problem now is that my Auth Service depends on another Service which also depends on the AuthService partially. This creates circular dependencies. Should I create more granular Services i.e. a service for every function? Set of functions based on the dependencies? Is my Repository per Resource approach wrong? Context: I've been working on a project using the T3 Stack and until now everything was basically entirely written inside the tRPC routers. This has become a problem now that other people besides me might start working on the project, increasing complexity, code coupling / duplication, testing purposes and all the other reasons for refactoring for dependency injection. I started today and am still learning how to apply the SOLID principles and I figured that starting with DI would be the solution to my problem. So if you think I am approaching my problem in a wrong way or if i should focus on other principles first, please let me know your opinion as well! If you want more info and more detailed code examples I will be gladly provide them.
2 Replies
Ian
Ian7mo ago
First, as a side-note, the easiest way to handle circular dependency is to not have any. This is of course not always possible, and I don't have enough detail, but I would ask myself if the other service you talk about really needs to handle auth at this layer, and if so, which part of auth specifically? That said, one way to handle circular dependencies in this context is by having lazy dependencies. In this case the dependency would only be created when it is used. This allows the both dependencies to depend on each-other in the same way as if you created a dependency per function, without having to create a class for every function (which is also a possible solution, but one you already thought of, so I'm providing an alternative). From experience, I've noticed that focusing on design principles often brings worse and less maintainable code. Instead, I would urge you to look for problems and difficulties that you're having, and how to fix those specifically. Look at your project, and which problems you want to solve, and only then, look for solutions. Not the other way around. PS: Do you need an interface for the UserRepository here?
Gnommon
GnommonOP7mo ago
Thank you! You bring up very good points. The problem i had above is kinda solved now (haven't tested it yet). I realized the Auth Service does not really need the StructureService but rather the underlying repository so that should fix my problem of circular dependecy. The reason why i want to add DI (and refactor everything to use DI not just parts, to have the code somewhat structured) is mainly for. For example: I have a function for deleting structures. A user can delete a structure and the call could happen in multiple parts of the app. But only some of those calls also should send WebSocket notifications to other users to update their UI. There are other additional steps that could happen around the deletion of a structure. For this kind of thing i envisioned using DI to simplify the code a bit but again, i just started this refactoring so i am not 100% sure on the approach yet. Another example: One very big and complex function makes a bunch of DB calls. We have to test the entire functionality manually every time a slight change happens and therefore i wanted to make this function specifically more testable by injecting the DB calls with mocks instead of directly calling prisma. Also: No i probably dont need a UserRepository but i wanted to keep the rest of the backend code clean and not have too many custom solutions for every single problem
Want results from more Discord servers?
Add your server