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
| Benefit | Description |
|---|---|
| Maintainability | Change one concern without affecting others |
| Testability | Test each concern in isolation |
| Reusability | Reuse concerns across applications |
| Parallel Development | Different teams work on different concerns |
| Flexibility | Easy 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
- Identify Clear Boundaries: Define what each concern is responsible for
- Use Interfaces: Depend on abstractions, not concrete implementations
- Minimize Dependencies: Only depend on concerns you need
- Cohesion: Keep related code together
- Consistency: Apply same separation across entire codebase