← Back to Articles         
Dependency Injection in Microservices

Dependency Injection in Microservices

Published on 28 Sep 2026     7 min read Microservices
Microservice Design

How Does Dependency Injection Help in Microservices?

1. What is Dependency Injection?

Dependency Injection (DI) is a design technique where a class receives the objects it depends on from an external source instead of creating those objects itself.

ASP.NET Core has a built-in Dependency Injection container.

Without DI

public class OrderService
{
    private readonly EmailService _emailService;

    public OrderService()
    {
        _emailService = new EmailService();
    }
}

OrderService is tightly coupled to EmailService.

With DI

public class OrderService
{
    private readonly IEmailService _emailService;

    public OrderService(IEmailService emailService)
    {
        _emailService = emailService;
    }
}

The dependency is provided from outside.

OrderService
     ↓
IEmailService
     ↓
EmailService

2. Why Do We Need DI in Microservices?

A microservice commonly depends on many components:

Order Service
     |
     ├── Repository
     ├── Database
     ├── Payment Client
     ├── Message Publisher
     ├── Cache
     └── Logger

If classes create these dependencies directly using new, the application becomes tightly coupled and difficult to test or modify.

DI separates:

What the class needs
        ↓
    Interface
        ↓
How it is implemented

This promotes loose coupling inside each microservice.

DI does not itself decouple one microservice from another at the network level. It mainly helps organize dependencies within the service.


3. Simple ASP.NET Core Example

Suppose an Order microservice needs a repository.

Interface

public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(int id);
}

Implementation

public class OrderRepository : IOrderRepository
{
    public async Task<Order?> GetByIdAsync(int id)
    {
        // Database operation
        return await Task.FromResult<Order?>(null);
    }
}

Service

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository)
    {
        _repository = repository;
    }

    public async Task<Order?> GetOrderAsync(int id)
    {
        return await _repository.GetByIdAsync(id);
    }
}

The OrderService doesn't need to know how OrderRepository connects to SQL Server.


4. Register Dependencies

Register them in Program.cs:

builder.Services.AddScoped<
    IOrderRepository,
    OrderRepository>();

builder.Services.AddScoped<OrderService>();

Now ASP.NET Core's DI container knows:

IOrderRepository
       ↓
OrderRepository

When OrderService is created, ASP.NET Core automatically supplies the required IOrderRepository.

You don't manually pass it.


5. Controller Injection

The controller can also receive the service through constructor injection.

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly OrderService _orderService;

    public OrdersController(OrderService orderService)
    {
        _orderService = orderService;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        var order =
            await _orderService.GetOrderAsync(id);

        if (order == null)
            return NotFound();

        return Ok(order);
    }
}

Complete flow:

HTTP Request
     ↓
OrdersController
     ↓
OrderService
     ↓
IOrderRepository
     ↓
OrderRepository
     ↓
Database

ASP.NET Core DI creates and connects these objects according to their registrations.


6. DI Helps with Loose Coupling

Suppose today we use:

IOrderRepository
       ↓
SqlOrderRepository

Later, requirements change.

We could provide another implementation:

IOrderRepository
       ↓
CosmosOrderRepository

The business service can continue depending on:

IOrderRepository

instead of knowing the database implementation.

This follows the Dependency Inversion Principle (DIP).


7. DI Helps Service-to-Service Communication

Suppose Order Service communicates with Payment Service.

Instead of directly creating an HttpClient everywhere:

var client = new HttpClient();

define a client abstraction:

public interface IPaymentClient
{
    Task<bool> ProcessPaymentAsync(
        int orderId,
        decimal amount);
}

Implementation:

public class PaymentClient : IPaymentClient
{
    private readonly HttpClient _httpClient;

    public PaymentClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    // Implementation...
}

Register:

builder.Services.AddHttpClient<
    IPaymentClient,
    PaymentClient>();

Then:

OrderService
     ↓
IPaymentClient
     ↓
PaymentClient
     ↓
HttpClient
     ↓
Payment Microservice

This makes external service communication easier to configure, test, and replace.


8. DI Helps with Testing

Suppose OrderService depends on:

IOrderRepository

During production:

IOrderRepository
       ↓
SQL Repository

During unit testing:

IOrderRepository
       ↓
Fake / Mock Repository

The business logic can be tested without connecting to the actual database.

For example:

var mockRepository =
    new Mock<IOrderRepository>();

var service =
    new OrderService(mockRepository.Object);

If using Moq, install:

dotnet add package Moq

Moq is optional; you can also create your own fake implementation.


9. DI Helps Manage Object Lifetimes

ASP.NET Core DI provides three major service lifetimes.

Transient

builder.Services.AddTransient<
    IEmailService,
    EmailService>();

A new instance is created whenever requested.

Suitable for lightweight stateless services.


Scoped

builder.Services.AddScoped<
    IOrderRepository,
    OrderRepository>();

One instance is generally created per HTTP request.

Common for:

  • EF Core DbContext
  • Repositories
  • Request-oriented business services

Singleton

builder.Services.AddSingleton<
    ICacheService,
    CacheService>();

One instance is shared for the lifetime of the application process.

Singleton services must be designed to be thread-safe, and you must be careful not to inject shorter-lived scoped dependencies directly into them.


10. DI in Clean Architecture Microservices

DI fits naturally with Clean Architecture.

API
 |
 ↓
Application
 |
 ↓
Domain

Infrastructure
 |
 └── Implements Interfaces

For example:

Application/Domain abstraction

public interface IOrderRepository
{
    Task<Order?> GetAsync(int id);
}

Infrastructure

public class OrderRepository
    : IOrderRepository
{
    // EF Core implementation
}

API – Program.cs

builder.Services.AddScoped<
    IOrderRepository,
    OrderRepository>();

The inner business layers depend on an abstraction, while Infrastructure supplies the implementation.


11. DI Helps Replace Infrastructure

A microservice may depend on external technologies:

Application Logic
       |
       ├── IOrderRepository
       ├── ICacheService
       ├── IMessagePublisher
       └── IPaymentClient

Implementations could be:

IOrderRepository
       ↓
EF Core + SQL Server

ICacheService
       ↓
Redis

IMessagePublisher
       ↓
Azure Service Bus

IPaymentClient
       ↓
HTTP Client

The application layer doesn't need to be tightly coupled to those technologies.


12. Advantages

Dependency Injection provides:

  • Loose coupling
  • Better separation of concerns
  • Easier unit testing
  • Easier implementation replacement
  • Centralized dependency configuration
  • Proper object lifetime management
  • Cleaner architecture
  • Better maintainability
  • Supports Dependency Inversion Principle
  • Easier integration with repositories, APIs, caches and messaging

13. Disadvantages / Challenges

DI can also introduce some complexity:

  • Too many dependencies can make constructors large.
  • Incorrect lifetimes can cause bugs.
  • Dependency chains can become difficult to understand.
  • Excessive interfaces can over-engineer simple code.
  • Runtime DI configuration errors may occur if services are not registered.

For example, this is often a design warning:

public OrderService(
    IService1 service1,
    IService2 service2,
    IService3 service3,
    IService4 service4,
    IService5 service5,
    IService6 service6,
    IService7 service7)

A very large constructor can indicate that the class has too many responsibilities.


14. Key Points

  • ASP.NET Core has built-in Dependency Injection.
  • DI means dependencies are provided rather than created inside the class.
  • Constructor injection is the most common approach.
  • DI promotes loose coupling within a microservice.
  • Depend on abstractions where they provide meaningful boundaries.
  • DI makes unit testing easier.
  • AddTransient → new instance when requested.
  • AddScoped → typically one instance per HTTP request.
  • AddSingleton → one instance per application process.
  • Repositories and EF Core contexts are commonly scoped.
  • AddHttpClient integrates service-to-service HTTP clients with DI.
  • DI works well with Clean Architecture and SOLID principles.

15. Interview Questions & Answers

Q1. How does Dependency Injection help in Microservices?

DI reduces coupling between components inside a microservice by injecting dependencies such as repositories, API clients, caches and message publishers rather than creating them directly.

Q2. Does ASP.NET Core have built-in DI?

Yes. ASP.NET Core includes a built-in dependency injection container.

Q3. What is Constructor Injection?

Dependencies are provided through the class constructor:

public OrderService(
    IOrderRepository repository)
{
    _repository = repository;
}

ASP.NET Core resolves the registered implementation automatically.

Q4. What are the DI lifetimes?

The three main lifetimes are:

Transient → New instance when resolved
Scoped    → Usually one per HTTP request
Singleton → One per application process

Q5. Why is Scoped commonly used for repositories?

Repositories commonly depend on EF Core DbContext, which is normally registered as Scoped, allowing related database operations within an HTTP request to use the same context scope.

Q6. How does DI improve unit testing?

Production dependencies can be replaced with mock or fake implementations, allowing business logic to be tested without real databases or external services.

Q7. Does DI make microservices loosely coupled from each other?

Not by itself. DI primarily reduces code-level coupling inside a service. Inter-service coupling is controlled through API contracts, messaging, bounded contexts, and service design.

Interview One-Line Answer

Dependency Injection helps ASP.NET Core microservices by reducing internal coupling, managing dependency lifetimes, improving testability, and allowing infrastructure implementations such as databases, caches, messaging, and external API clients to be replaced without tightly coupling them to business logic.

← Back to Articles