Abstraction is a powerful concept in software design, but over-abstraction can inadvertently harm your project. Here’s a real experience from a .NET Core (C#) project.
-> The Problem: Abstraction Done Too Early
In one of our enterprise .NET Core applications, we aimed for “perfect architecture” and created multiple interfaces for a simple CRUD-based User module, including:
– IUserService
– IUserManager
– IUserProcessor
– IUserRepository
– IBaseRepository<T>
– IReadOnlyRepository<T>
Example:
public interface IUserService
{
Task<UserDto> GetUserAsync(int id);
}
public class UserService : IUserService
{
private readonly IUserManager _userManager;
public UserService(IUserManager userManager)
{
_userManager = userManager;
}
public async Task<UserDto> GetUserAsync(int id)
{
return await _userManager.GetUserAsync(id);
}
}
This resulted in:
– No business logic
– Just method-to-method forwarding
– More files, more DI registrations, more confusion
-> Real Impact on the Project
– New developers took longer to understand the flow
– Debugging required jumping across 4–5 layers
– Change requests took more time
– “Flexible architecture” became hard to maintain
-> The Fix: Abstraction Where It Matters
We refactored by asking, “Is this abstraction solving a real problem today?”
What we kept:
– Repository abstraction (DB may change)
– Service layer only where business logic exists
-> What we removed:
– Pass-through interfaces
– Premature layers
-> Simplified version:
public class UserService
{
private readonly UserRepository _repository;
public UserService(UserRepository repository)
{
_repository = repository;
}
public async Task<UserDto> GetUserAsync(int id)
{
var user = await _repository.GetByIdAsync(id);
if (!user.IsActive)
throw new Exception("Inactive user");
return MapToDto(user);
}
}
– Clear
– Readable
– Easy to change
– Faster onboarding
-> Following are the important points:
– Abstraction is a tool, not a rule
– Don’t abstract until you see variation or change
– YAGNI still applies in modern .NET Core projects
– Simple code scales better than “clever” architecture
Good architecture evolves -> it is not forced on Day One.
