Chia tầng
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Quy tắc "hết hàng thì không cho đặt" đang nằm ở đâu? Nếu nó nằm lẫn trong controller của API, thì app WinForms phải viết lại một bản, và hai bản sớm muộn sẽ lệch nhau. Chia tầng tách code theo vai trò, để quy tắc nghiệp vụ chỉ nằm ở một chỗ.
Khái niệm
🍰 Kiến trúc phân tầng (layered architecture): chia ứng dụng thành các tầng theo vai trò, mỗi tầng chỉ gọi xuống tầng ngay bên dưới.
| Tầng | Vai trò | Trong dự án cửa hàng |
|---|---|---|
| Giao diện (presentation) | nhận yêu cầu, hiển thị kết quả | controller của API, form WinForms |
| Nghiệp vụ (business) | quy tắc của cửa hàng | OrderService, ShippingCalculator |
| Dữ liệu (data) | đọc ghi database | IProductStore, DbProductStore |
Ví dụ
Controller mỏng: nhận request, gọi tầng nghiệp vụ, trả kết quả. Quy tắc đặt
hàng nằm trong OrderService của bài Fake thay phụ thuộc:
// ShopApi
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly OrderService _orders;
public OrdersController(OrderService orders)
{
_orders = orders;
}
[HttpPost]
public async Task<IActionResult> Place(
int productId, int quantity)
{
bool ok = await _orders.PlaceAsync(
productId, quantity);
if (!ok)
{
return BadRequest("Không đủ hàng");
}
return Ok();
}
}- Controller không biết tồn kho được kiểm tra thế nào, cũng không biết có
database. Nó chỉ đổi kết quả thành response HTTP:
BadRequesthayOk. OrderServicekhông biết mình được gọi từ API hay từ form. Đăng ký nó như bài Dependency injection trong ASP.NET Core:builder.Services.AddScoped<OrderService>();.- Controller nhận thẳng class
OrderService, không qua interface: service nghiệp vụ không cần thay bằng bản khác, còn phần cần thay là database đã nằm sauIProductStore. - Form WinForms có thể gọi đúng
OrderService.PlaceAsync, nên hai app dùng cùng một quy tắc.
Thử ngay
Gọi tầng nghiệp vụ từ một test, không cần API hay form. Thêm class này vào
ShopApi.Tests, dùng FakeProductStore của bài Fake thay phụ thuộc, rồi
chạy dotnet test:
// ShopApi.Tests
using Xunit;
public class LayerTests
{
[Fact]
public async Task Place_WithoutUi()
{
var store = new FakeProductStore();
store.Products.Add(new Product
{
Id = 1, Name = "Bút bi", Stock = 120
});
var orders = new OrderService(store);
Assert.True(await orders.PlaceAsync(1, 20));
Assert.False(await orders.PlaceAsync(1, 500));
Assert.Equal(100, store.Products[0].Stock);
}
}Đoán trước khi chạy: test qua hay đỏ?
Xem kết quả
Passed! - Failed: 0, ...Qua. Đơn 20 cái được đặt, kho còn 100. Đơn 500 cái bị từ chối vì chỉ còn 100. Tầng nghiệp vụ chạy được mà không có giao diện nào, nên test được và dùng chung được.
Lỗi hay gặp
Để quy tắc nghiệp vụ và DbContext trong controller. Controller vừa đọc
database, vừa kiểm tra tồn kho, vừa trả HTTP. App WinForms không dùng lại
được, còn test thì phải dựng cả API.
// SAI — ShopApi: quy tắc nghiệp vụ trong controller
[HttpPost]
public async Task<IActionResult> Place(int id, int qty)
{
var product = await _db.Products.FindAsync(id);
if (product == null || product.Stock < qty)
{
return BadRequest("Không đủ hàng");
}
product.Stock = product.Stock - qty;
await _db.SaveChangesAsync();
return Ok();
}// ĐÚNG — ShopApi: controller chỉ gọi service
using Microsoft.AspNetCore.Mvc;
public class OrdersController : ControllerBase
{
private readonly OrderService _orders;
public OrdersController(OrderService orders)
{
_orders = orders;
}
[HttpPost]
public async Task<IActionResult> Place(
int id, int qty)
{
bool ok = await _orders.PlaceAsync(id, qty);
if (!ok)
{
return BadRequest("Không đủ hàng");
}
return Ok();
}
}Tóm tắt
- Ba tầng: giao diện, nghiệp vụ, dữ liệu. Mỗi tầng chỉ gọi tầng dưới.
- Quy tắc nghiệp vụ nằm ở một chỗ, trong các service.
- Controller và form mỏng: nhận yêu cầu, gọi service, hiển thị kết quả.
- Tầng nghiệp vụ không phụ thuộc giao diện, nên test và dùng lại được.
Tự kiểm tra
0/3 câuQuy tắc "đơn trên 500.000 được miễn phí giao hàng" nên nằm ở tầng nào?
Controller nên làm những việc gì?
Vì sao đặt quy tắc đặt hàng trong OrderService lại giúp app WinForms?