Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Separation of Concerns (SoC)

Overview

Separation of Concerns là nguyên tắc thiết kế tách biệt một ứng dụng thành các phần riêng biệt, mỗi phần xử lý một responsibility cụ thể. Mục tiêu là giảm coupling, tăng maintainability, và dễ dàng thay đổi một phần mà không ảnh hưởng đến các phần khác.

Core Concept

Before: Mixed Concerns
┌─────────────────────────────────────────────┐
│         UserController                      │
│  - Handle HTTP requests                     │
│  - Business logic (validation, processing)  │
│  - Data access (queries)                    │
│  - Authentication                           │
│  - Logging                                  │
└─────────────────────────────────────────────┘

After: Separation of Concerns
┌─────────────────────────────────────────────┐
│  UserController    → Handle HTTP requests   │
│  UserService       → Business logic         │
│  UserRepository    → Data access            │
│  AuthService       → Authentication        │
│  Logger            → Logging               │
└─────────────────────────────────────────────┘

Types of Concerns

1. Business Logic Concern

// Business logic separated from other concerns
public class OrderService
{
    public decimal CalculateTotal(Order order, Discount discount)
    {
        // Pure business logic
        var subtotal = order.Items.Sum(i => i.Price * i.Quantity);
        return subtotal * (1 - discount.Percentage);
    }
}

2. Data Access Concern

public interface IOrderRepository
{
    Task<Order> GetByIdAsync(Guid id);
    Task<List<Order>> GetByCustomerIdAsync(Guid customerId);
    Task AddAsync(Order order);
    Task UpdateAsync(Order order);
}

3. Presentation Concern

public class OrdersController : ControllerBase
{
    // Only handles HTTP concerns
    [HttpGet("{id}")]
    public async Task<IActionResult> GetOrder(Guid id)
    {
        var order = await _orderService.GetOrderAsync(id);
        return Ok(order);
    }
}

4. Cross-Cutting Concerns

// Logging, security, validation - apply to multiple layers

// Logging interceptor
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
    
    public async Task<TResponse> Handle(TRequest request, 
        RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Handling {RequestType}", typeof(TRequest).Name);
        var response = await next();
        _logger.LogInformation("Handled {RequestType}", typeof(TRequest).Name);
        return response;
    }
}

Implementation Levels

1. Layer Separation

// Presentation Layer
public class ProductsController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetProducts()
    {
        // Only presentation logic
    }
}

// Application Layer
public class ProductService
{
    // Business logic
}

// Infrastructure Layer
public class ProductRepository
{
    // Data access
}

2. Class Level

// Single Responsibility Principle (SRP)
public class EmailService
{
    public void SendConfirmation(string email, Order order)
    {
        // Single responsibility: sending order confirmations
    }
}

public class OrderService
{
    // Different service for order operations
}

3. Method Level

public class OrderProcessor
{
    public void Process(Order order)
    {
        ValidateOrder(order);      // Validation concern
        CalculateTotal(order);     // Calculation concern
        SaveOrder(order);          // Persistence concern
        NotifyCustomer(order);     // Notification concern
    }
    
    // Each method handles one concern
}

Benefits

BenefitDescription
MaintainabilityChange one concern without affecting others
TestabilityTest each concern in isolation
ReusabilityReuse concerns across applications
Parallel DevelopmentDifferent teams work on different concerns
FlexibilityEasy to change implementation

Example: E-commerce Application

┌─────────────────────────────────────────────────────────────┐
│                    Architecture Layers                       │
├─────────────────────────────────────────────────────────────┤
│  Presentation (API)     │ Controllers, DTOs, Validators   │
├─────────────────────────────────────────────────────────────┤
│  Application (Use Cases)│ Services, Commands, Queries     │
├─────────────────────────────────────────────────────────────┤
│  Domain (Business Logic)│ Entities, Value Objects, Rules  │
├─────────────────────────────────────────────────────────────┤
│  Infrastructure        │ Repositories, External APIs      │
├─────────────────────────────────────────────────────────────┤
│  Cross-Cutting         │ Logging, Security, Caching       │
└─────────────────────────────────────────────────────────────┘

Implementation in ASP.NET Core

// Program.cs - Setting up concern separation
var builder = WebApplication.CreateBuilder(args);

// Services with clear separation
builder.Services.AddControllers();           // Presentation
builder.Services.AddScoped<IOrderService, OrderService>();  // Application
builder.Services.AddScoped<IOrderRepository, OrderRepository>();  // Infrastructure

// Cross-cutting concerns
builder.Services.AddLogging();               // Logging
builder.Services.AddAuthentication();        // Security
builder.Services.AddMemoryCache();           // Caching
builder.Services.AddAutoMapper();             // Mapping

Anti-Patterns to Avoid

// God Object - handles too many concerns
public class GodClass
{
    public void HandleHttpRequest() { }      // Presentation
    public void ValidateInput() { }          // Validation
    public void ProcessBusiness() { }        // Business
    public void SaveToDatabase() { }          // Data
    public void LogOperation() { }           // Logging
}

// Spaghetti Code - tangled concerns
public class RandomClass
{
    public void DoEverything() { } // Everything mixed together
}

Best Practices

  1. Identify Clear Boundaries: Define what each concern is responsible for
  2. Use Interfaces: Depend on abstractions, not concrete implementations
  3. Minimize Dependencies: Only depend on concerns you need
  4. Cohesion: Keep related code together
  5. Consistency: Apply same separation across entire codebase