19 Dec 2023



Intermediate

In the context of a simple ASP.NET Core WebAPI using C# and Clean Architecture, applying the Single Responsibility Principle (SRP) involves ensuring that each class or module has only one reason to change. Let's break down how SRP can be applied to various components within this architecture:

1. Controller (UI Layer):

  • Responsibility: Handling HTTP requests, parsing input, and returning HTTP responses.
  • SRP Application:
    • The controller should focus solely on handling the input and output concerns related to the HTTP protocol.
    • Business logic should be delegated to the application layer.
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly IUserService _userService;

    public UserController(IUserService userService)
    {
        _userService = userService;
    }

    [HttpPost]
    public IActionResult CreateUser([FromBody] CreateUserRequest request)
    {
        // Delegate to the application layer for user creation
        _userService.CreateUser(request.Username);
        return Ok();
    }

    [HttpGet("{userId}")]
    public IActionResult GetUserById(int userId)
    {
        // Delegate to the application layer for retrieving user by ID
        var username = _userService.GetUserById(userId);
        return Ok(username);
    }
    // Other controller actions...
}

2. Application Layer:

  • Responsibility: Containing high-level policies, business logic, and use case coordination.
  • SRP Application:
    • The application services or use case interactors within this layer should focus on a specific business capability.
    • Each service should have a single responsibility related to a particular aspect of the application.
// Interface defining the high-level user service
public interface IUserService
{
    void CreateUser(string username);
    string GetUserById(int userId);
    // Other user-related methods...
}

// High-level user service implementation
public class UserService : IUserService
{
    private readonly IUserRepository _userRepository;

    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public void CreateUser(string username)
    {
        // Business logic for creating a user...
        _userRepository.SaveUser(new User { Username = username });
    }

    public string GetUserById(int userId)
    {
        // Business logic for retrieving a user...
        var user = _userRepository.GetUserById(userId);
        return user?.Username;
    }
    // Other user-related methods...
}

3. Domain Layer:

  • Responsibility: Encapsulating business entities, domain logic, and business rules.
  • SRP Application:
    • Business entities should focus on representing the core data structures and state of the application.
    • Domain logic should consist of rules and behaviors specific to the business domain.
// Business entity
public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    // Other user-related properties...
}

// Interface defining the low-level user repository
public interface IUserRepository
{
    void SaveUser(User user);
    User GetUserById(int userId);
    // Other data access methods...
}

// Low-level user repository implementation
public class UserRepository : IUserRepository
{
    private readonly List<User> _users = new List<User>();

    public void SaveUser(User user)
    {
        // Database access logic to save the user...
        _users.Add(user);
    }

    public User GetUserById(int userId)
    {
        // Database access logic to retrieve the user...
        return _users.FirstOrDefault(u => u.Id == userId);
    }
    // Other data access methods...
}

4. Infrastructure Layer:

  • Responsibility: Dealing with external details such as databases, frameworks, and external services.
  • SRP Application:
    • Concrete implementations of interfaces defined in the Application and Domain Layers should focus on the specific details of interacting with external systems.
public void ConfigureServices(IServiceCollection services)
{
    // Register high-level policy module (Application Layer)
    services.AddScoped<IUserService, UserService>();

    // Register low-level detail module (Infrastructure Layer)
    services.AddScoped<IUserRepository, UserRepository>();

    // Other service registrations...
}

By following the Single Responsibility Principle in each layer of the Clean Architecture for an ASP.NET Core WebAPI, you create a modular and maintainable system. Each component has a clear and distinct responsibility, making the codebase more understandable, testable, and adaptable to change. The UI layer handles HTTP requests, the Application Layer contains business logic, the Domain Layer encapsulates business entities and rules, and the Infrastructure Layer deals with external details.

clean-architecture
single-responsibility-principle
asp.net-core
c#