The Secret of Breath — Rediscover the sacred rhythm of your breath. Cultivate inner silence that brings clarity, balance, and resilience in daily life.


Dependency Injection as a Practice of Surrender

The Illusion of Control in Software

In the early stages of a developer’s journey, there is a natural inclination toward control. We construct our dependencies explicitly, wire objects together manually, and take comfort in knowing exactly how each piece comes into existence.

public class OrderService
{
    private readonly PaymentGateway _paymentGateway;

    public OrderService()
    {
        _paymentGateway = new PaymentGateway();
    }
}

This feels clean and deliberate. The class appears self-sufficient, fully in charge of its behavior. But this control comes at a cost. The service is tightly bound to a specific implementation. Any change in the dependency ripples through the system. Testing becomes difficult. Flexibility diminishes.

What initially appears as control gradually reveals itself as constraint.

Dependency Injection introduces a subtle shift: you do not have to construct everything yourself. You can define what you need and allow something else to provide it.

The Illusion of the Self-Sufficient Object

Traditional object-oriented design often encourages the idea of self-sufficient components. A class that creates its own dependencies appears strong and independent. In reality, it becomes burdened by its own responsibilities.

Consider an OrderProcessor that creates its own database connection, logging service, and notification sender. It may function correctly, but it carries too much weight. It cannot operate without those exact implementations. It cannot be tested in isolation. It is tightly bound to its environment.

This is not strength—it is entanglement.

To practice Dependency Injection is to recognize that an object does not need to know how to build its collaborators. It only needs to know how to use them. By removing the new keyword from within the class, we release it from unnecessary responsibility.

The object becomes lighter, more focused, and more adaptable. It is no longer the center of the system, but a participant within it.

Designing for Receptivity

Dependency Injection is, at its core, a design for receptivity. Instead of reaching outward to construct dependencies, a class becomes open to receiving them.

public class OrderService : IOrderService
{
    private readonly IRepository _repository;
    private readonly 
    ILogger<OrderService> _logger;

    public OrderService(IRepository repository, 
    ILogger<OrderService> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task ProcessOrder(Order order)
    {
        _logger.LogInformation("Processing order 
        {OrderId}", order.Id);
        await _repository.SaveAsync(order);
    }
}

Here, OrderService does not concern itself with whether IRepository is backed by SQL, an in-memory store, or a mock implementation. It simply declares its needs and remains ready to receive them.

This lack of knowledge is not a weakness. It is architectural clarity.

By focusing only on its role, the class becomes stable even as the surrounding infrastructure evolves. The implementation can change without disturbing the core logic. The service remains still, while the system around it adapts.

The Container as an Invisible Orchestrator

If components are designed for receptivity, the Dependency Injection container becomes the orchestrator that fulfills those needs. In .NET, this role is played by the service container configured at application startup.

builder.Services.AddScoped<IRepository, 
SqlRepository>();
builder.Services.AddScoped<IOrderService, 
OrderService>();

Here, we define the relationships between abstractions and implementations. Once configured, the container takes over the responsibility of constructing objects, resolving dependencies, and managing lifetimes.

app.MapPost("/orders", async 
(IOrderService service, Order order) =>
{
    await service.ProcessOrder(order);
    return Results.Ok();
});

There is no manual instantiation, no wiring logic scattered across the application. The container quietly provides what is needed, when it is needed.

At first, this invisibility can feel unfamiliar. We are used to seeing construction happen explicitly. But over time, the absence of noise becomes a strength. The system feels composed rather than assembled.

You begin to trust that not every detail needs to be handled directly.

Surrender Is Not Neglect

Surrender in software design does not mean abandoning responsibility. It requires precision and awareness.

To use Dependency Injection effectively, you must still make deliberate choices:

builder.Services.AddSingleton<
ICacheService, MemoryCacheService>();
builder.Services.AddScoped<
IOrderRepository, OrderRepository>();
builder.Services.AddTransient<
IEmailSender, EmailSender>();

Each lifetime carries meaning. A singleton persists across the application. A scoped service aligns with a request. A transient service is created anew each time.

These decisions shape the behavior of the system.

Surrender, in this context, is not about doing less—it is about placing responsibility at the right level. You design the structure, define the relationships, and allow the container to handle execution.

Testing as the Fruit of Non-Attachment

One of the most practical outcomes of Dependency Injection is testability. But this benefit is not separate from the philosophy—it is a direct result of non-attachment.

When a class is no longer bound to concrete implementations, it becomes easy to substitute its dependencies.

var mockRepository = new 
Mock<IRepository>();
var mockLogger = new Mock<ILogger<
OrderService>>();

var service = new OrderService
(mockRepository.Object, mockLogger.Object);

await service.ProcessOrder(new Order());

The class can now be tested in isolation, free from databases, networks, or external systems. You can simulate success, failure, or edge cases without altering the class itself.

Because the class is not attached to a specific implementation, it can function in any environment where its requirements are fulfilled.

This is the quiet power of non-attachment: flexibility without modification.

The Discipline of Boundaries

Dependency Injection also introduces a discipline that is easy to overlook—the discipline of boundaries.

Every constructor becomes a declaration of intent. It tells you exactly what a class depends on and, by extension, what it is responsible for.

public class OverloadedService
{
    public OverloadedService(
        IServiceA a,
        IServiceB b,
        IServiceC c,
        IServiceD d,
        IServiceE e)
    {
    }
}

When dependencies begin to accumulate, the design reveals its own imbalance. The class is trying to do too much.

DI does not prevent poor design, but it makes it visible. It exposes complexity instead of hiding it.

This visibility allows you to refactor with clarity, breaking large components into smaller, focused ones. Each class becomes aligned with a single purpose.

The Quiet Strength of Simple Design

As systems evolve, complexity often creeps in unnoticed. We add layers, handle edge cases, and attempt to account for every possibility within individual components.

Dependency Injection gently counters this tendency.

When you stop trying to control the entire dependency graph from within each class, the system becomes more resilient. Changes can be made at the edges—within configuration—without disturbing the core logic.

You can swap implementations, upgrade infrastructure, or introduce new behaviors with minimal friction.

The result is a system that adapts without strain.

This is not achieved through more code, but through better separation of concerns. Each component does what it is meant to do, and nothing more.

Conclusion: Stillness in Execution

Dependency Injection is often introduced as a technique for decoupling, but its deeper value lies in the clarity it brings to design.

When you stop constructing dependencies and begin receiving them, your code shifts. It becomes more intentional, more focused, and more stable. Each component trusts the system to provide what it needs.

This is not a loss of control. It is a refinement of it.

You move from managing every detail to defining meaningful relationships. From forcing structure to allowing it to emerge.

In this way, Dependency Injection becomes more than a pattern. It becomes a practice—one that invites clarity, reduces friction, and brings a certain stillness to the way systems are built and understood.

And in that stillness, both the code and the mind that writes it become easier to work with.

That’s all for now. May your intention be clear and your mind be still. With this quiet wish, I rest my pen and return to the silence.


Author : Bipin Joshi
Bipin Joshi is an independent software consultant, trainer, and author, specializing in Microsoft web development technologies. Having embraced the yogic way of life, he also mentors select individuals in Ajapa Gayatri and allied meditative practices. Blending the disciplines of code and consciousness, he has been meditating, programming, writing, and teaching for over 31 years. As a prolific author, he shares his insights on both software development and yogic wisdom through his websites.

Posted On : 20 April 2026