Giới thiệu
Trong hành trình phát triển backend của tôi, có một câu hỏi luôn làm tôi băn khoăn: Tại sao chúng ta không thể viết tất cả logic trong controller? Nghe có vẻ đơn giản: lấy dữ liệu, áp dụng một số quy tắc và cập nhật cơ sở dữ liệu, tất cả trong một nơi. Tôi đã thử, nhưng nhận ra rằng điều này không hiệu quả cho các công ty lớn. Trong bài viết này, tôi sẽ giải thích về kiến trúc sạch (Clean Architecture) bằng những thuật ngữ dễ hiểu với các ví dụ thực tế để bạn có thể áp dụng vào công việc của mình.
Kiến Trúc Sạch Là Gì?
Kiến trúc sạch là một cách tổ chức mã nguồn để giúp cho ứng dụng của bạn trở nên dễ bảo trì, mở rộng và kiểm thử. Nó phân chia trách nhiệm thành các lớp khác nhau: Controllers, Services và Repositories. Mỗi lớp có một nhiệm vụ cụ thể, giúp mã nguồn trở nên rõ ràng hơn.
Hình Dung Ứng Dụng Như Một Công Ty
Lớp | Ai Là Người | Công Việc Làm |
---|---|---|
Controller | Nhân viên tiếp tân | Nhận yêu cầu và chuyển tiếp chúng |
Service | Giám đốc ngân hàng | Áp dụng quy tắc, đưa ra quyết định |
Repository | Nhân viên lưu trữ | Lấy và cập nhật hồ sơ trong hệ thống |
ILogger | Camera an ninh | Lưu lại lịch sử những gì đã xảy ra |
Serilog/NLog | Phòng lưu trữ | Quyết định nơi lưu trữ các bản ghi |
Luồng Dữ Liệu (Sơ Đồ)
[ Yêu cầu từ người dùng ]
↓
[ Controller ] → Nhận yêu cầu và chuyển tiếp đến Services
↓
[ Service ] → Áp dụng quy tắc và logic kinh doanh
↓
[ Repository ] → Giao tiếp với cơ sở dữ liệu (chỉ lấy và cập nhật thông tin)
Controller: Nhân Viên Tiếp Tân
Controller là điểm vào của ứng dụng. Nó không đưa ra quyết định hoặc giao tiếp với cơ sở dữ liệu; nó chỉ nhận yêu cầu và chuyển tiếp chúng.
csharp
[ApiController]
[Route("api/transfer")]
public class TransferController : ControllerBase
{
private readonly ITransferService _service;
public TransferController(ITransferService service)
{
_service = service;
}
[HttpPost]
public IActionResult TransferMoney([FromBody] TransferRequest request)
{
_service.Transfer(request.FromAccount, request.ToAccount, request.Amount);
return Ok("Chuyển tiền thành công");
}
}
Service: Giám Đốc Ngân Hàng
Service (lớp logic kinh doanh) áp dụng quy tắc. Ví dụ, “Người gửi có đủ tiền để chuyển không?”
csharp
public class TransferService : ITransferService
{
private readonly IAccountRepository _repository;
private readonly ILogger<TransferService> _logger;
public TransferService(IAccountRepository repository, ILogger<TransferService> logger)
{
_repository = repository;
_logger = logger;
}
public void Transfer(string fromAcc, string toAcc, decimal amount)
{
var from = _repository.GetAccount(fromAcc);
var to = _repository.GetAccount(toAcc);
if (from.Balance < amount)
{
_logger.LogWarning("Tài khoản {Account} không đủ tiền", fromAcc);
throw new Exception("Không đủ tiền");
}
from.Balance -= amount;
to.Balance += amount;
_repository.UpdateAccount(from);
_repository.UpdateAccount(to);
_logger.LogInformation("Đã chuyển {Amount} từ {From} đến {To}", amount, fromAcc, toAcc);
}
}
Repository: Nhân Viên Lưu Trữ
Repository là nơi xảy ra việc truy cập dữ liệu. Không có quy tắc, không có quyết định, chỉ đọc và ghi dữ liệu.
csharp
public class AccountRepository : IAccountRepository
{
private readonly BankingDbContext _context;
private readonly ILogger<AccountRepository> _logger;
public AccountRepository(BankingDbContext context, ILogger<AccountRepository> logger)
{
_context = context;
_logger = logger;
}
public Account GetAccount(string accountId)
{
_logger.LogInformation("Lấy thông tin tài khoản {AccountId}", accountId);
return _context.Accounts.FirstOrDefault(a => a.Id == accountId);
}
public void UpdateAccount(Account account)
{
_context.Accounts.Update(account);
_context.SaveChanges();
_logger.LogInformation("Đã cập nhật tài khoản {AccountId}", account.Id);
}
}
Logging: Camera An Ninh
Trong lĩnh vực tài chính, việc ghi log là rất quan trọng. Mỗi giao dịch, thành công hay lỗi phải được theo dõi. Đây là lúc ILogger phát huy vai trò của mình.
csharp
_logger.LogInformation("Giao dịch {TransactionId} thành công tại {Time}", transaction.Id, DateTime.UtcNow);
_logger.LogError("Không thể xử lý giao dịch {TransactionId}", transaction.Id);
Nhưng ILogger chỉ là một giao diện. Bạn vẫn cần một nhà cung cấp ghi log như Serilog hoặc NLog để quyết định nơi lưu trữ những bản ghi đó (file, đám mây, cơ sở dữ liệu, v.v.).
csharp
// Ví dụ cấu hình Program.cs với Serilog
Log.Logger = new LoggerConfiguration()
.WriteTo.File("logs/transactions.txt")
.CreateLogger();
builder.Host.UseSerilog();
Tóm Tắt: Ai Làm Gì
Lớp | Trách Nhiệm | Ẩn Dụ |
---|---|---|
Controller | Nhận yêu cầu và trả về phản hồi | Nhân viên tiếp tân |
Service | Áp dụng quy tắc kinh doanh | Giám đốc ngân hàng |
Repository | Xử lý truy cập dữ liệu | Nhân viên lưu trữ |
ILogger | Ghi lại hành động, lỗi và kiểm toán | Camera an ninh |
Serilog/NLog | Lưu trữ log (file, DB, cloud, v.v.) | Phòng lưu trữ |
Các Công Ty Lớn So Với Dự Án Nhỏ
- Dự án nhỏ: Bạn có thể trộn lẫn mọi thứ trong controller. Nó hoạt động một thời gian.
- Công ty lớn: Cấu trúc này là bắt buộc. Nó giúp ứng dụng:
- Dễ kiểm thử
- Dễ mở rộng
- Dễ bảo trì
- An toàn hơn (đặc biệt là các hệ thống liên quan đến tiền)
Nhìn nhận theo cách này:
- Nếu liên quan đến xử lý yêu cầu API → Controller
- Nếu liên quan đến quy tắc, logic hoặc quyết định → Service
- Nếu liên quan đến lấy hoặc lưu dữ liệu → Repository
- Nếu liên quan đến theo dõi hoạt động → ILogger (với Serilog/NLog để lưu trữ)
Những Lưu Ý Quan Trọng
- Tách biệt trách nhiệm: Giúp mã nguồn dễ đọc và bảo trì hơn.
- Ghi log đầy đủ: Quan trọng để theo dõi các giao dịch và xử lý sự cố.
Kết Luận
Nếu bạn đang phát triển một dự án nhỏ, bạn có thể cắt giảm một số quy trình. Nhưng nếu bạn muốn làm việc tại các công ty lớn hoặc xây dựng phần mềm có thể phát triển và bền vững, thì kiến trúc sạch chính là nền tảng bạn phải theo đuổi.
Luôn ghi nhớ: nhân viên tiếp tân, giám đốc ngân hàng, nhân viên lưu trữ và camera an ninh, mỗi người có một vai trò riêng. Điều này giúp bạn nhớ và biết được mã nguồn thuộc về phần nào. Giữ cho chúng tách biệt giúp hệ thống của bạn sạch sẽ, có thể mở rộng và chuyên nghiệp.
Câu Hỏi Thường Gặp (FAQ)
1. Kiến trúc sạch có thể áp dụng cho các dự án nhỏ không?
Có, nhưng nên cân nhắc sử dụng đơn giản hơn để không làm phức tạp hóa.
2. Tại sao lại cần logging trong ứng dụng?
Logging giúp theo dõi các giao dịch và phát hiện lỗi, từ đó xử lý kịp thời.
3. Làm thế nào để chọn giữa Serilog và NLog?
Cả hai đều có ưu điểm; chọn dựa trên nhu cầu cụ thể của dự án.
Kết Nối Với Tôi
Nếu bài viết này có ích cho bạn, hãy để lại một ❤️ hoặc 🦄 và theo dõi tôi trên dev.to. Tôi sẽ chia sẻ thêm nhiều kiến thức bổ ích như thế này. Bạn cũng có thể kết nối với tôi trên các mạng xã hội khác. Tôi rất mong được học hỏi, chia sẻ và phát triển cùng bạn!