

Kiến trúc Onion trong ASP.NET Core
- 26-06-2025
- Toanngo92
- 0 Comments
Mục lục
9.1 Khái niệm về Kiến trúc Onion
Kiến trúc Onion là một mô hình được giới thiệu vào năm 2008 nhằm giải quyết các vấn đề liên quan đến sự phụ thuộc trong thiết kế ứng dụng. Các vấn đề chính trong thiết kế truyền thống là ràng buộc chặt (tight coupling) và sự lo ngại về khả năng phân chia.
Tight Coupling (Ràng buộc chặt)
Trong lập trình hướng đối tượng, khi một lớp phụ thuộc mạnh vào lớp khác, thì bất kỳ thay đổi nào trong lớp phụ thuộc cũng ảnh hưởng đến lớp hiện tại. Điều này được gọi là ràng buộc chặt – thường thấy trong các chương trình nhỏ, nhưng không phù hợp cho các ứng dụng lớn, phức tạp.
Loose Coupling (Ràng buộc lỏng)
Trong ràng buộc lỏng, hai đối tượng không phụ thuộc trực tiếp vào nhau. Một đối tượng có thể thay đổi mà không làm ảnh hưởng đến đối tượng kia. Điều này làm tăng tính linh hoạt và khả năng bảo trì của phần mềm.
9.1.1 Đảo ngược điều khiển (Inversion of Control – IoC)
Trong thiết kế hướng đối tượng, nhiều phụ thuộc giữa các đối tượng và thành phần có thể gây khó khăn trong quản lý. Inversion of Control (IoC) là một nguyên lý cấp cao giúp tách biệt trách nhiệm của các lớp, cho phép tạo ra các mối quan hệ ràng buộc lỏng hơn.
- Khi áp dụng IoC, các lớp không còn chịu trách nhiệm tạo hoặc kiểm soát các đối tượng khác – điều này giúp giảm sự phụ thuộc và cải thiện khả năng bảo trì, kiểm thử, và mở rộng.
- Trong phương pháp truyền thống, mã gọi trực tiếp đến thư viện. Trong khi đó, với IoC, framework sẽ điều khiển luồng thay vì lập trình viên.
- Trong ASP.NET Core, IoC thường được hiện thực thông qua Dependency Injection (DI), nơi các tham số được truyền thông qua constructor hoặc thuộc tính.
9.1.2 Nguyên lý Đảo ngược Phụ thuộc (Dependency Inversion Principle – DIP)
DIP là một trong những nguyên lý cốt lõi trong SOLID, được giới thiệu bởi Robert C. Martin:
- DIP khuyến khích việc thiết kế phần mềm sao cho các mô-đun cấp cao không phụ thuộc trực tiếp vào các mô-đun cấp thấp.
- Cả hai loại mô-đun nên phụ thuộc vào các abstraction (lớp trừu tượng).
- Abstraction không nên phụ thuộc vào chi tiết; thay vào đó, chi tiết nên phụ thuộc vào abstraction.
Điều này giúp:
- Giảm ràng buộc chặt
- Tăng khả năng kiểm thử
- Cải thiện khả năng mở rộng và bảo trì
Để áp dụng IoC và DIP hiệu quả, cần thực hiện:
- Áp dụng IoC
- Áp dụng DIP bằng abstraction
- Áp dụng Dependency Injection
- Sử dụng container DI
- Tạo các lớp ràng buộc lỏng
Hình 9.1: Triển khai IoC và DIP

Vòng tròn thể hiện quá trình chuyển đổi từ các lớp ràng buộc chặt thành các lớp ràng buộc lỏng bằng cách:
- Sử dụng pattern factory (mẫu nhà máy)
- Tạo abstraction
- Triển khai DI
- Sử dụng container IoC
9.2 Tại sao sử dụng Kiến trúc Onion?
Kiến trúc Onion được xây dựng để giảm thiểu sự phụ thuộc giữa các lớp, từ đó giúp dễ dàng quản lý và kiểm thử.
- Nó khắc phục các vấn đề của kiến trúc N-layers truyền thống.
- Giúp tách biệt các mối quan tâm (Separation of Concerns).
- Tăng tính linh hoạt, khả năng mở rộng, và tái sử dụng.
Lợi ích chính của Onion Architecture:
- Các lớp được tổ chức thành các vòng đồng tâm (concentric layers)
- Lớp domain luôn nằm ở trung tâm – dựa trên logic nghiệp vụ, không phụ thuộc vào UI hay cơ sở dữ liệu.
- Giúp giao tiếp hiệu quả giữa các lớp
- Hỗ trợ các nguyên lý như testability, maintainability, consistency
Kiến trúc Onion được đề xuất bởi Jeffrey Palermo.
9.3 Các lớp trong Kiến trúc Onion
Hình 9.2: Kiến trúc truyền thống
- Bao gồm: Data Layer → Business Logic Layer → UI Layer → Hạ tầng.
- Có xu hướng phụ thuộc từ trên xuống dưới.
Hình 9.3: Các lớp trong Kiến trúc Onion
- Lớp trung tâm: Domain Entities
- Bao quanh bởi: Repository, Service, UI
- Lớp ngoài cùng có thể thay đổi thường xuyên (như giao diện người dùng – UI)
Lưu ý: Số lượng lớp có thể thay đổi tùy theo ứng dụng, nhưng lớp domain luôn ở trung tâm. Các lớp bên ngoài có thể bị thay đổi, nhưng domain thì không nên bị ảnh hưởng.
Dưới đây là bản dịch chi tiết phần tiếp theo từ các trang bạn gửi, nối tiếp phần trước về Onion Architecture trong ASP.NET Core – Phần I:
9.3 Các lớp của Kiến trúc Onion
Hình 9.2 và 9.3 hiển thị kiến trúc truyền thống và các lớp khác nhau trong kiến trúc Onion tương ứng:
Hình 9.2: Kiến trúc truyền thống
- Data Layer: xử lý lưu trữ dữ liệu.
- Business Logic Layer: xử lý logic nghiệp vụ.
- UI Layer: tương tác người dùng.
- Infrastructure: các phần hỗ trợ không liên quan trực tiếp tới logic nghiệp vụ.
Hình 9.3: Các lớp của Kiến trúc Onion
- Lõi trung tâm: Domain Entities (thực thể miền)
- Bao quanh lần lượt bởi: Repository → Service → UI

Lưu ý: Lớp Domain luôn ở trung tâm. Số lượng lớp có thể thay đổi tùy ứng dụng, nhưng Domain Entities không bao giờ bị thay đổi bởi các lớp bên ngoài. Các lớp ngoài như UI thường xuyên thay đổi.
Các lớp khác nhau trong Kiến trúc Onion:
- Lớp Domain Entities: Lớp sâu nhất, chứa tất cả thực thể miền của ứng dụng. Đây là các mô hình cơ sở dữ liệu được tạo theo hướng code-first.
- Lớp Repository: Là cầu nối giữa service và mô hình. Quản lý migration, context truy xuất dữ liệu, sử dụng mẫu thiết kế repository.
- Lớp Service: Chứa các API có thể công bố. Đóng vai trò trung gian giữa repository và lớp giao diện. Bao gồm cả logic nghiệp vụ và các interface độc lập nhằm đảm bảo separation of concerns và loose coupling.
- Lớp UI (hoặc Unit Test): Là giao diện người dùng hoặc lớp kiểm thử, chỉ đơn giản là chương trình frontend giao tiếp với API.
9.4 Khám phá nguyên lý DIP (Dependency Inversion Principle)
Nguyên lý DIP bao gồm hai nguyên lý cốt lõi:
- Các module cấp cao không được phụ thuộc vào module cấp thấp. Cả hai phải phụ thuộc vào abstraction.
- Module cấp cao: chứa logic nghiệp vụ hoặc các chức năng đặc thù.
- Module cấp thấp: xử lý chi tiết kỹ thuật hoặc giao tiếp thấp.
- Cả hai cần phụ thuộc vào abstraction thay vì lẫn nhau.
- Abstraction không phụ thuộc vào chi tiết. Chi tiết phải phụ thuộc vào abstraction.
- Abstraction là interface hoặc abstract class định nghĩa hành vi.
- Chi tiết là phần hiện thực cụ thể (implementation) tuân theo abstraction.
9.4.2 Ví dụ thực tế về triển khai DIP
Các nhà phát triển có thể triển khai DIP trong thực tế bằng cách thiết kế phần mềm dựa trên abstraction thay vì các class cụ thể.
1. Trừu tượng hóa dịch vụ (Service Abstraction)
Ví dụ: một module cấp cao cần thực hiện logging, nhưng không muốn gắn chặt với loại logging cụ thể. Có thể định nghĩa một interface:
Mã ví dụ 1:
public interface ILogger
{
void Log(string message);
}
2. Module cấp cao sử dụng abstraction
Mã ví dụ 2:
public class HighLevelModule
{
private readonly ILogger logger;
public HighLevelModule(ILogger logger)
{
this.logger = logger;
}
public void PerformOperation()
{
// Logic cấp cao
logger.Log("Operation performed successfully.");
}
}
Module này có thể sử dụng bất kỳ implementation nào của ILogger
như file logger, database logger, hoặc console logger. Như vậy, module cấp cao không bị phụ thuộc vào các chi tiết hiện thực cụ thể.
9.5 Truy cập dữ liệu và Kho lưu trữ trong tầng Hạ tầng
Trong kiến trúc phần mềm hiện đại, tầng hạ tầng (infrastructure) là nền tảng của ứng dụng, quản lý các cơ chế cấp thấp như truy xuất dữ liệu, giao tiếp bên ngoài và các mối quan tâm hạ tầng khác.
Đây là một trong những lớp trong kiến trúc Onion, đảm bảo separation of concerns và tổ chức code rõ ràng.
9.5.1 Tầng Hạ tầng (Infrastructure Layer)
- Tầng này quản lý các cơ chế cấp thấp cần thiết cho ứng dụng hoạt động.
- Bao gồm:
- Truy cập dữ liệu (data access)
- Giao tiếp với hệ thống bên ngoài
- Quản lý cấu hình, v.v.
9.5.2 Trách nhiệm của Tầng Hạ tầng
Xem trong Hình 9.4, gồm:
- Truy cập dữ liệu: tương tác với hệ lưu trữ dữ liệu như CSDL, thông qua DAO hoặc repository.
- Tích hợp hệ thống: tương tác với hệ thống ngoài như API hoặc dịch vụ web.
- Framework và thư viện: sử dụng framework, thư viện bên ngoài hỗ trợ chức năng.
- Cấu hình: quản lý cấu hình, thông tin môi trường, thông số kết nối, v.v.
9.5.3 Triển khai Truy cập Dữ liệu bằng Repository
Để triển khai việc truy cập dữ liệu:
- Tạo interface định nghĩa các hành động cơ bản (CRUD): Create, Read, Update, Delete.
- Việc này tách chi tiết hiện thực ra khỏi logic nghiệp vụ, đảm bảo tính đồng nhất và dễ mở rộng.
Bước 1: Định nghĩa Interface Repository (phần này sẽ tiếp tục ở trang kế tiếp).
Nếu bạn muốn mình dịch tiếp phần còn lại (từ bước 1 trở đi), hãy gửi thêm trang tiếp theo – mình sẽ tiếp tục bản dịch liền mạch.
Dưới đây là bản dịch tiếp theo từ các trang bạn đã gửi, nối tiếp phần trước về Onion Architecture trong ASP.NET Core – Phần I:
9.5.3 (tiếp theo) Triển khai Truy cập Dữ liệu bằng Repository
1. Định nghĩa Interface Repository
Đoạn mã sau định nghĩa một interface dùng để thực hiện các thao tác dữ liệu (CRUD):
Đoạn mã 3
public interface IRepository<T>
{
void Add(T entity);
void Update(T entity);
void Delete(T entity);
T GetById(int id);
IEnumerable<T> GetAll();
}
2. Cài đặt Repository
Tạo các implementation cụ thể của interface IRepository
cho từng kiểu thực thể hoặc kiểu dữ liệu. Ví dụ sau sử dụng Entity Framework để tương tác với cơ sở dữ liệu SQL:
Đoạn mã 4
public class SqlRepository<T> : IRepository<T> where T : class
{
private readonly DbContext context;
public SqlRepository(DbContext dbContext)
{
this.context = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
public void Add(T entity)
{
context.Set<T>().Add(entity);
context.SaveChanges();
}
public void Update(T entity)
{
context.Set<T>().Update(entity);
context.SaveChanges();
}
public void Delete(T entity)
{
context.Set<T>().Remove(entity);
context.SaveChanges();
}
public T GetById(int id)
{
return context.Set<T>().Find(id);
}
public IEnumerable<T> GetAll()
{
return context.Set<T>().ToList();
}
}
Ghi chú: Lớp
SqlRepository
ở trên cài đặt interfaceIRepository
, thực hiện các thao tác CRUD sử dụngDbContext
.
3. Tiêm Repository vào Service
Sử dụng Dependency Injection để truyền repository vào trong các service hoặc controller, giúp module cấp cao không phụ thuộc vào lớp hiện thực cụ thể.
Đoạn mã 5
public class UserService
{
private readonly IRepository<User> userRepository;
public UserService(IRepository<User> userRepository)
{
this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
}
public void AddUser(User user)
{
userRepository.Add(user);
}
public IEnumerable<User> GetAllUsers()
{
return userRepository.GetAll();
}
}
9.6 Ưu điểm của Kiến trúc Onion
MVC giúp giải quyết vấn đề tách biệt mối quan tâm (separation of concerns), nhưng vẫn không khắc phục được ràng buộc chặt (tight coupling).
Onion Architecture ra đời để:
- Đảm bảo tách biệt các lớp như giao diện, truy cập dữ liệu, logic nghiệp vụ
- Tập trung vào miền nghiệp vụ (Domain)
- Hạn chế phụ thuộc giữa các lớp
Các ưu điểm chính:
- ✅ Dễ bảo trì (Easy Maintenance): Dễ bảo trì vì mã nguồn được phân tách rõ theo các lớp hoặc tập trung ở trung tâm (domain).
- ✅ Cải thiện kiểm thử (Improved Testing): Có thể viết test đơn vị cho từng lớp mà không ảnh hưởng đến phần còn lại.
- ✅ Ràng buộc lỏng (Loose Coupling): Các lớp bên ngoài luôn tương tác với lớp bên trong qua interface.
- ✅ Triển khai dễ dàng (Easy Implementation): Khi dùng dịch vụ ngoài, có thể tạo interface để xử lý tại lớp cao hơn.
Nguyên tắc thực hiện kiến trúc Onion:
- Ứng dụng theo mô hình hướng đối tượng độc lập
- Mã có thể biên dịch riêng biệt so với tầng hạ tầng
- Ràng buộc chỉ hướng về lõi trung tâm (domain)
- Interface được định nghĩa ở lớp bên trong
- Lớp ngoài dùng để triển khai các interface đó
Kết luận
Onion Architecture dễ bảo trì vì mã được phân tách rõ ràng hoặc đặt tập trung ở domain. Kiến trúc này giúp:
- Kiểm thử từng phần dễ dàng
- Giảm ràng buộc giữa các lớp
- Đảm bảo sự tách biệt rõ ràng giữa các tầng
- Giao tiếp giữa lớp ngoài và trong thông qua interface → giúp xây dựng ứng dụng có ràng buộc lỏng (loosely coupled)
Nếu bạn muốn mình tổng hợp lại toàn bộ tài liệu này thành bản PDF tiếng Việt hoặc dạng slide, mình có thể hỗ trợ thêm. Bạn cũng có thể yêu cầu ví dụ thực tế hoặc sơ đồ giải thích để học sâu hơn về Onion Architecture.
Bài tập
1. Xây dựng ứng dụng Web áp dụng nguyên lý Onion Architecture
-
Triển khai một ứng dụng web sử dụng các nguyên tắc của Onion Architecture, đặc biệt tập trung vào vai trò của tầng hạ tầng (Infrastructure Layer) như:
-
Truy cập dữ liệu
-
Cài đặt repository
-
2. Triển khai mẫu Repository đơn giản trong C# sử dụng .NET Core
-
Tạo một interface
IRepository<T>
với các phương thức:-
Create
-
Read
-
Update
-
Delete (CRUD)
-
-
Sau đó, tạo một lớp hiện thực cụ thể của interface này cho một thực thể cụ thể (ví dụ: User).
-
Viết một đoạn mã ví dụ để minh họa cách thực hiện các thao tác CRUD trên thực thể User bằng repository đã tạo.
3. Triển khai Dependency Injection trong ứng dụng ASP.NET Core
-
Định nghĩa một interface dịch vụ và hiện thực tương ứng của nó.
-
Cấu hình Dependency Injection trong lớp
Startup.cs
của ứng dụng ASP.NET Core. -
Trình bày cách sử dụng dịch vụ đã tiêm (injected service) trong:
-
Controller
-
hoặc Middleware
-