Kiến trúc backend .net core
Tạo một dự án backend sử dụng .NET Core kết hợp với Entity Framework để viết API là một quá trình phức tạp. Dưới đây, tôi sẽ hướng dẫn bạn qua từng bước để tạo một ứng dụng CRUD đơn giản bằng .NET Core và Entity Framework.
Bước 1: Cài đặt .NET Core và Visual Studio
Nếu bạn chưa cài đặt .NET Core và Visual Studio, hãy tải và cài đặt chúng từ trang web chính thức của Microsoft.
Bước 2: Tạo dự án .NET Core mới
Mở Visual Studio và chọn "Create a new project." Sau đó, chọn "ASP.NET Core Web Application."
Đặt tên cho dự án và chọn thư mục lưu trữ.
Chọn phiên bản .NET Core.
Chọn mẫu dự án "API" và nhấn "Create."
Bước 3: Tạo thư mục và tệp cơ bản
Trong dự án của bạn, bạn có thể tạo các thư mục và tệp cơ bản như sau:
Controllers: Thư mục này chứa các tệp controller. Controller quản lý các yêu cầu HTTP và xử lý logic ứng dụng.
Models: Thư mục này chứa các model đại diện cho các đối tượng trong cơ sở dữ liệu. Ví dụ, nếu bạn có một bảng "User" trong cơ sở dữ liệu, bạn sẽ có một model tương ứng.
Data: Thư mục này có thể chứa các tệp liên quan đến cơ sở dữ liệu. Trong ví dụ của bạn, bạn có thể tạo một tệp DbContext ở đây.
Services: Nếu bạn có logic kinh doanh phức tạp hoặc các dịch vụ, bạn có thể tạo các tệp ở đây.
Bước 4: Tạo model
Trong thư mục "Models," tạo các lớp đại diện cho các đối tượng trong cơ sở dữ liệu. Ví dụ, nếu bạn muốn quản lý người dùng, bạn có thể tạo một lớp User.cs:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
}
Bước 5: Tạo DbContext
Trong thư mục "Data," tạo một lớp DbContext để kết nối với cơ sở dữ liệu. Sử dụng Entity Framework, bạn có thể tạo DbContext như sau:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
public DbSet Users { get; set; }
}
Bước 6: Tạo Controller
Trong thư mục "Controllers," tạo các tệp controller để xử lý các yêu cầu HTTP và gửi các phản hồi JSON. Ví dụ, bạn có thể tạo một UserController.cs:
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly ApplicationDbContext _context;
public UserController(ApplicationDbContext context)
{
_context = context;
}
// Định nghĩa các phương thức CRUD ở đây.
}
Bước 7: Cấu hình Routing và Middleware
Cấu hình routing trong Startup.cs để xác định các địa chỉ URL API của bạn và đảm bảo rằng các yêu cầu được định tuyến đúng.
Bước 8: Chạy ứng dụng và thử nghiệm API
Chạy ứng dụng của bạn và sử dụng Postman hoặc trình duyệt để thử nghiệm các API mà bạn đã tạo. Bạn có thể thêm, sửa đổi, xóa và lấy dữ liệu từ cơ sở dữ liệu.
Đây là một hướng dẫn tổng quan về cách tạo một dự án backend đơn giản sử dụng .NET Core và Entity Framework để viết API. Tuy nhiên, dự án thực tế sẽ phức tạp hơn, bao gồm xử lý lỗi, xác thực người dùng, quản lý biến môi trường, và nhiều khía cạnh khác.
ở bước 6 thường tách biệt việc gọi api với các xử lí bên phía dữ liệu :
Trước tiên, bạn cần tạo một dịch vụ (service) để xử lý tương tác với cơ sở dữ liệu. Bạn có thể tạo một tệp UserService.cs hoặc tùy chỉnh tên theo yêu cầu của bạn.
public class UserService
{
private readonly ApplicationDbContext _context;
public UserService(ApplicationDbContext context)
{
_context = context;
}
// Định nghĩa các phương thức để truy vấn cơ sở dữ liệu ở đây.
}
Sau đó, trong controller, bạn sẽ inject dịch vụ UserService hoặc tên tương tự.
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly UserService _userService;
public UserController(UserService userService)
{
_userService = userService;
}
// Sử dụng _userService để gọi các phương thức từ dịch vụ để truy vấn cơ sở dữ liệu.
}
Điều này giúp tách biệt logic xử lý yêu cầu HTTP (controller) và tương tác với cơ sở dữ liệu (dịch vụ) trong ứng dụng của bạn. Controller sẽ chỉ chịu trách nhiệm gọi các phương thức từ dịch vụ để thực hiện tác vụ CRUD và xử lý dữ liệu trả về.
Trong một controller .NET Core, bạn có thể inject dịch vụ vào constructor của controller để sử dụng chúng trong các phương thức của controller. Cụ thể, bạn thực hiện việc inject dịch vụ trong constructor của controller như sau:
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly UserService _userService;
public UserController(UserService userService)
{
_userService = userService;
}
// Các phương thức của controller ở đây.
}
Trong đoạn mã trên:
UserService là một dịch vụ (service) bạn đã tạo trước đó, và nó được inject vào constructor của UserController.
_userService là một biến thành viên (field) của UserController, và bạn gán nó bằng giá trị userService được truyền vào constructor.
Việc inject dịch vụ vào controller giúp bạn có khả năng sử dụng các phương thức và tính năng của dịch vụ trong các phương thức của controller. Điều này giúp bạn tách biệt logic xử lý yêu cầu HTTP (trong controller) và logic tương tác với cơ sở dữ liệu hoặc các dịch vụ khác (trong dịch vụ), giúp mã nguồn trở nên dễ quản lý và bảo trì hơn.
Trong .NET Core, việc quản lý và cung cấp các thể hiện của các dịch vụ (services) được thực hiện thông qua một cơ chế gọi là Dependency Injection (DI). DI giúp bạn quản lý và cung cấp các dịch vụ (ví dụ: UserService) cho các thành phần khác trong ứng dụng của bạn.
Dưới đây là cách .NET Core biết rằng UserService nên được cung cấp từ đâu:
Đăng ký dịch vụ: Trước hết, bạn cần đăng ký UserService với DI container của .NET Core. Điều này thường được thực hiện trong phương thức ConfigureServices của tệp Startup.cs. Ví dụ:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped();
// Các đăng ký khác
}
Inject dịch vụ vào controller: Khi bạn định nghĩa constructor của UserController như sau:
public UserController(UserService userService)
{
_userService = userService;
}
.NET Core sẽ tự động cung cấp một thể hiện của UserService cho bạn khi bạn tạo một instance của UserController. .NET Core biết rằng UserService phải được lấy từ DI container và nó lấy từ đó.
Sử dụng dịch vụ trong controller: Bây giờ, bạn có thể sử dụng _userService trong các phương thức của UserController để thực hiện các thao tác liên quan đến người dùng hoặc công việc khác mà UserService cung cấp.
Điều này giúp bạn tách biệt việc cấu hình và quản lý dịch vụ (DI container) khỏi logic của ứng dụng và cho phép bạn tái sử dụng các dịch vụ dễ dàng trong nhiều thành phần khác nhau của ứng dụng.
trong dự án thực tế của tôi thì chia ra 3 tầng là :
DMS.API chứa controller , appsettings.json , Program.cs
DMS.BUSINESS chứa folder service và các file service tương ứng
và DMS.CORE chứa folder configuration và các file tương ứng , các entities và file AppDbContext.cs
Kiến trúc mà bạn mô tả tương tự kiến trúc phân lớp (Layered Architecture) và có thể được gọi là kiến trúc 3 tầng hoặc kiến trúc 3 lớp. Kiến trúc này là một trong những kiến trúc phổ biến trong phát triển ứng dụng web và phát triển ứng dụng gốc dữ liệu (data-driven applications).
Cụ thể, kiến trúc của bạn bao gồm:
DMS.API: Tầng giao diện người dùng hoặc tầng điều hướng. Thường chứa các controller để xử lý yêu cầu HTTP và gửi phản hồi cho các khách hàng. Các tệp appsettings.json và Program.cs là phần cấu hình và khởi động ứng dụng.
DMS.BUSINESS: Tầng logic kinh doanh hoặc tầng dịch vụ. Chứa các dịch vụ (services) và logic xử lý kinh doanh. Dịch vụ trong tầng này thực hiện các nhiệm vụ liên quan đến xử lý logic và truy cập cơ sở dữ liệu. Điều này giúp tách biệt logic kinh doanh khỏi controller và tạo khả năng tái sử dụng cao.
DMS.CORE: Tầng truy cập dữ liệu hoặc tầng dữ liệu. Chứa các thứ liên quan đến dữ liệu như Entities (đại diện cho các đối tượng trong cơ sở dữ liệu), DbContext (để kết nối và tương tác với cơ sở dữ liệu), và configuration (cấu hình dành cho dự án). Tầng này đóng vai trò quan trọng trong việc truy cập và quản lý dữ liệu.
Lợi ích:
Tách biệt logic: Kiến trúc 3 tầng giúp tách biệt logic kinh doanh, logic điều hướng, và logic truy cập dữ liệu, làm cho ứng dụng dễ bảo trì và dễ hiểu hơn.
Tái sử dụng: Các dịch vụ (services) trong tầng BUSINESS có thể được tái sử dụng trong nhiều phần của ứng dụng hoặc trong các dự án khác.
Phân chia công việc: Kiến trúc này cho phép nhiều người tham gia phát triển ứng dụng mà không ảnh hưởng đến nhau. Các nhóm có thể làm việc độc lập trên các tầng khác nhau.
Luồng hoạt động:
Yêu cầu HTTP được gửi từ người dùng đến controller trong tầng API.
Controller xử lý yêu cầu và gọi các dịch vụ từ tầng BUSINESS để xử lý logic kinh doanh.
Các dịch vụ trong tầng BUSINESS có thể tương tác với cơ sở dữ liệu thông qua các Entities và DbContext trong tầng CORE.
Kết quả được trả về từ tầng BUSINESS, và controller sẽ xây dựng phản hồi HTTP để trả về cho người dùng.
Tổng quan, kiến trúc 3 tầng giúp tạo ứng dụng dễ bảo trì, mở rộng và tái sử dụng, cũng như giúp phân chia rõ ràng trách nhiệm của từng tầng trong ứng dụng của bạn.
Tầng CORE (hoặc tầng dữ liệu) của kiến trúc 3 tầng là nơi bạn xác định cấu trúc và quản lý dữ liệu của ứng dụng. Đây là nơi bạn tương tác trực tiếp với cơ sở dữ liệu và định nghĩa các đối tượng dữ liệu (Entities) cũng như cấu hình cơ sở dữ liệu. Dưới đây là một số thư mục và chức năng phổ biến trong tầng CORE:
Entities: Thư mục này thường chứa các đối tượng (class) đại diện cho các bảng hoặc bản ghi trong cơ sở dữ liệu. Mỗi đối tượng Entity tương ứng với một bảng trong cơ sở dữ liệu và thường chứa các thuộc tính để mô tả dữ liệu.
DbContext: Lớp DbContext là một phần quan trọng của Entity Framework, và nó đại diện cho cơ sở dữ liệu của ứng dụng. Nó chứa các tệp cấu hình (configurations) để liên kết các Entities với các bảng trong cơ sở dữ liệu, và nó có thể được sử dụng để thực hiện các thao tác CRUD (Create, Read, Update, Delete) với cơ sở dữ liệu.
Migrations: Thư mục này chứa các tệp Migration, được sử dụng để cập nhật cơ sở dữ liệu với các thay đổi cấu trúc (ví dụ: thêm bảng mới, sửa đổi cột). Migration cho phép bạn duyệt qua các phiên bản cơ sở dữ liệu và thực hiện các thay đổi mà không làm mất dữ liệu hiện có.
Configuration: Thư mục này thường chứa các tệp hoặc lớp cấu hình để định nghĩa quy tắc và cách mà dữ liệu sẽ được lưu trữ trong cơ sở dữ liệu. Điều này bao gồm quy tắc về quan hệ giữa các bảng, định nghĩa các khóa chính và ngoại, và định dạng dữ liệu.
Repositories: Một số dự án có thư mục "Repositories" để định nghĩa các phương thức tương tác với cơ sở dữ liệu. Repositories thường được sử dụng để tạo một lớp trung gian giữa dịch vụ (BUSINESS) và DbContext, giúp quản lý các thao tác truy vấn dữ liệu.
Chức năng của tầng CORE là tạo ra một lớp trung gian để tương tác với cơ sở dữ liệu và định nghĩa cấu trúc dữ liệu (Entities) và cấu hình cơ sở dữ liệu. Điều này giúp tách biệt logic kinh doanh (BUSINESS) khỏi việc tương tác với cơ sở dữ liệu, làm cho mã nguồn dễ bảo trì và giúp giảm sự phụ thuộc vào cơ sở dữ liệu trong toàn bộ ứng dụng.
Các đoạn code như public DbSet tblMdArea { get; set; } trong lớp DbContext (ví dụ: AppDbContext) có chức năng định nghĩa và ánh xạ các đối tượng Entity (Entities) vào các bảng trong cơ sở dữ liệu. Chúng được gọi là "DbSet" và được sử dụng để tương tác với dữ liệu trong cơ sở dữ liệu. Điều này có các chức năng chính sau:
Ánh xạ bảng và Entity: DbSet giúp bạn ánh xạ một đối tượng Entity vào một bảng cụ thể trong cơ sở dữ liệu. Trong ví dụ của bạn, tblMdArea là một đối tượng Entity và tblMdArea là tên của bảng trong cơ sở dữ liệu.
Cung cấp các phương thức truy cập dữ liệu: DbSet cung cấp các phương thức sẵn có để thực hiện các thao tác cơ bản trên dữ liệu như thêm (Insert), cập nhật (Update), xóa (Delete), và truy vấn (Select).
Ví dụ, sau khi bạn đã định nghĩa một DbSet cho một Entity, bạn có thể sử dụng các phương thức như Add, Update, Remove, Find, ToList, và nhiều phương thức khác để thực hiện các thao tác tương tác với cơ sở dữ liệu.
Dưới đây là một ví dụ sử dụng DbSet:
public DbSet tblMdArea { get; set; }
// ...
// Thêm một đối tượng vào bảng tblMdArea
var newArea = new tblMdArea { Name = "New Area" };
tblMdArea.Add(newArea);
SaveChanges(); // Lưu thay đổi vào cơ sở dữ liệu
Các DbSet trong lớp DbContext cũng giúp Entity Framework biết cách tạo và thực hiện các câu lệnh SQL tương ứng để thao tác với cơ sở dữ liệu, giúp bạn tránh việc viết mã SQL thủ công và quản lý các yêu cầu cơ sở dữ liệu một cách dễ dàng hơn.
Trong dự án .NET Core, file .dto thường được sử dụng để định nghĩa các Data Transfer Objects (DTOs). DTOs là các lớp hoặc đối tượng được sử dụng để truyền dữ liệu giữa các phần của ứng dụng hoặc giữa ứng dụng và giao diện người dùng. Có một số lý do phổ biến để sử dụng DTOs trong ứng dụng:
Phân tách giữa dữ liệu hiển thị và dữ liệu thật sự: DTOs cho phép bạn tạo một lớp đối tượng dự phòng để chứa dữ liệu chỉ cần thiết cho một mục đích cụ thể (ví dụ: hiển thị trên giao diện người dùng) mà không cần phải truyền toàn bộ đối tượng Entity hoặc dữ liệu liên quan.
Tối ưu hóa hiệu suất: DTOs giúp tối ưu hóa hiệu suất bằng cách giảm lượng dữ liệu được truyền qua mạng hoặc giữa các tầng của ứng dụng. Nếu bạn chỉ truyền dữ liệu cần thiết, bạn có thể giảm lưu lượng dữ liệu và tăng tốc độ truyền tải.
Bảo mật: Sử dụng DTOs có thể giúp bạn kiểm soát dữ liệu được truyền từ tầng BUSINESS (hoặc tầng dịch vụ) đến tầng API hoặc giao diện người dùng. Bạn có thể loại bỏ hoặc ẩn các thông tin nhạy cảm khỏi DTO trước khi truyền đi.
Loại bỏ vòng lặp tham chiếu (Circular References): Trong trường hợp Entity Framework hoặc các ORM khác, DTOs giúp tránh việc tạo vòng lặp tham chiếu (circular references) khi bạn trả về đối tượng Entity chứa tham chiếu đến chính nó hoặc đến các đối tượng liên quan.
Ví dụ về cách sử dụng DTOs:
public class UserDto
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
// Sử dụng DTO để truyền dữ liệu từ tầng CORE đến tầng API hoặc giao diện người dùng
public IActionResult GetUser(int userId)
{
var userEntity = _userService.GetUserById(userId);
var userDto = new UserDto
{
UserId = userEntity.Id,
UserName = userEntity.UserName,
Email = userEntity.Email
};
return Ok(userDto);
}
Các hàm mapping trong các file DTO thường được sử dụng để chuyển đổi dữ liệu từ đối tượng Entity (Entities) sang DTO và ngược lại. Mapping có thể thực hiện bằng cách sử dụng các thư viện như AutoMapper hoặc bằng cách thủ công trong mã nguồn của bạn.
Lý do để sử dụng các hàm mapping là để tạo một cách chuyên nghiệp và rõ ràng để ánh xạ dữ liệu giữa các đối tượng Entity và DTO. Các lợi ích của việc sử dụng hàm mapping bao gồm:
Dễ bảo trì và quản lý: Các hàm mapping giúp tách biệt logic chuyển đổi dữ liệu giữa các đối tượng, làm cho mã nguồn dễ bảo trì hơn. Nếu bạn cần thay đổi cách chuyển đổi dữ liệu, bạn chỉ cần chỉnh sửa một nơi duy nhất, thay vì phải thay đổi từng phần mã nguồn trong nhiều nơi.
Tối ưu hóa sử dụng bộ nhớ: Hàm mapping có thể giúp bạn loại bỏ các thuộc tính không cần thiết khi chuyển đổi dữ liệu. Điều này có thể giúp tối ưu hóa việc sử dụng bộ nhớ và giảm lưu lượng dữ liệu truyền qua mạng.
Quy tắc ánh xạ tùy chỉnh: Các hàm mapping cho phép bạn xác định cách ánh xạ các thuộc tính cụ thể và thực hiện quy tắc ánh xạ tùy chỉnh nếu cần.
Dưới đây là một ví dụ đơn giản về việc sử dụng AutoMapper để tạo các hàm mapping:
// Cấu hình AutoMapper
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap();
cfg.CreateMap();
});
// Tạo mapper
var mapper = config.CreateMapper();
// Sử dụng mapper để ánh xạ từ Entity sang DTO
var userEntity = _userService.GetUserById(userId);
var userDto = mapper.Map(userEntity);
Hàm mapping có thể được sử dụng để chuyển đổi dữ liệu giữa các đối tượng Entity và DTO một cách dễ dàng và rõ ràng .
Nhận xét
Đăng nhận xét