Abstraction is a powerful concept in software design

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.


Notice Inappropriate?

If you come across any inappropriate content, please report it to our administrators!

Leave a Reply

Please login to post comments.