Skip to main content

How to implement DI in a layered architecture?

DI (Dependency Injection) is a way to inject dependencies between layers so that each layer does not create objects itself, but instead receives them "from the outside": through a constructor, parameters, or a dependency-injection container.

In a layered architecture, DI is needed to loosen the coupling between layers and make the system flexible, testable, and manageable.


1. The problem without DI

Without dependency injection, the code looks like this:

python
class UserService: def __init__(self): self.repository = UserRepository() # dependency is created inside

Now UserService is tightly coupled to the specific UserRepository implementation. If you want to swap the database or test the service, you will have to rewrite the code.


2. The DI principle in a layered architecture

The idea is that each layer does not create its dependency, but receives it "ready-made" from the upper level or a container:

python
class UserService: def __init__(self, repository): self.repository = repository

Now you can pass any implementation:

python
repo = PostgresUserRepository() service = UserService(repo)

UserService no longer depends on which database is used. → Tests can substitute a FakeRepository.


3. Where to inject dependencies per layer

LayerWhat is injectedWhere
Presentation layerBusiness-logic servicesInto controllers / views
Business-logic layerRepositories, providersInto services / managers
Data layerConnections, driversInto repositories
Infrastructure layerCache, loggers, API clientsInto services or event handlers

4. What this looks like in practice

Example in Python (no frameworks):

python
class UserRepository: def get_user(self, id): ... class UserService: def __init__(self, repository): self.repository = repository def get_user_info(self, id): user = self.repository.get_user(id) return {"name": user.name, "email": user.email} class UserController: def __init__(self, service): self.service = service def handle_request(self, id): return self.service.get_user_info(id) # Dependency injection at the top level: repository = UserRepository() service = UserService(repository) controller = UserController(service)

5. DI containers (automation)

Large projects use a DI container: a component that creates and passes dependencies to the right places by itself:

  • Python: dependency-injector, injector
  • Java: Spring Framework (@Autowired)
  • C#: .NET Core Dependency Injection
  • JavaScript/TS: NestJS, InversifyJS

The container manages the whole dependency tree:

python
container = Container() container.wire(modules=[controllers, services, repositories])

6. The main rule

Dependencies always point downward: the upper layer knows about the lower one, but not the other way around.

DI lets you change the lower layer's implementation without touching the upper one.


Summary: DI in a layered architecture is a mechanism that wires connections between layers through interfaces rather than hard object creation. It makes the code flexible, testable, and ready to scale without a "domino effect."

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.