Architecture Pattern - Phần 7: Hexagonal Architecture
12 Aug, 2025
Hướng nội
AuthorHexagonal Architecture là pattern kiến trúc mà mọi tương tác từ bên ngoài đều phải đi qua một Adapter để kết nối vào một Port của Application

Mục Lục
Trong bài viết này, chúng ta sẽ khám phá một trong những phong cách kiến trúc được áp dụng rất rộng rãi trong các hệ thống backend: Hexagonal Architecture, còn được biết đến với tên gọi Ports and Adapters.
Được xem là phiên bản tiến hoá từ layered architecture, Hexagonal Architecture giúp chuyển sự tập trung vào thứ quan trọng nhất là business domain, chứ không phải là các frameworks, databases, hay những công cụ bên ngoài xoay quanh nó.
1. Giới thiệu
Mục tiêu của Layered Architectures truyền thống là để phân tách một application thành các layer khác nhau, trong đó mỗi layer chứa các modules và classes có trách nhiệm chung hoặc tương tự, và làm việc cùng nhau để thực hiện các tác vụ cụ thể.
Có nhiều biến thể khác nhau của Layered Architectures và không có quy tắc nào xác định nên có bao nhiêu lớp (layers). Pattern phổ biến nhất là 3 tier architecture, nơi application được chia thành Presentation Layer, Logic Layer, và Data Layer.
Việc tuân theo một Layered Architecture mang lại nhiều lợi ích, một trong những lợi ích quan trọng nhất là separation of concerns. Tuy nhiên, luôn có rủi ro, vì không có cơ chế tự nhiên nào để phát hiện khi logic bị rò rỉ (leak) giữa các lớp, developer có thể sẽ đưa business logic vào user interface, data layer.
Vào năm 2005, Alistair Cockburn nhận ra rằng không có nhiều sự khác biệt giữa cách user interface và database tương tác với một application, vì cả hai đều là các actors bên ngoài có thể hoán đổi cho nhau bằng các components tương tự, và cũng sẽ tương tác với application theo cách tương đương.
Bằng cách nhìn nhận vấn đề theo hướng này, chúng ta có thể tập trung vào việc giữ cho application được agnostic (không phụ thuộc) với các actors "bên ngoài", cho phép chúng tương tác thông qua Ports and Adapters, nhờ đó tránh được sự vướng víu (entanglement) và rò rỉ logic (logic leakage) giữa business logic và các external components.
2. Hexagonal Architecture là gì?
Kiến trúc Hexagonal Architecture (còn có tên gọi khác là Ports and Adapters) là một pattern kiến trúc mà ở đó, mọi tương tác từ bên ngoài – dù là từ users hay các hệ thống khác – đều phải đi qua một Adapter để kết nối vào một Port của Application. Tương tự, khi Application cần gửi dữ liệu ra ngoài, nó cũng sẽ đi qua một Port để đến một Adapter tương ứng.

Cách làm này tạo ra một abstraction layer (lớp trừu tượng) có tác dụng che chắn và bảo vệ phần core (lõi) của Application. Nó giúp core được cô lập hoàn toàn khỏi các tools và technologies bên ngoài, vốn không phải là mối bận tâm của logic nghiệp vụ cốt lõi.
2.1 Port trong Hexagonal Architecture
Bạn có thể hình dung Port giống như một lối vào (entry point) hoàn toàn không phụ thuộc vào công nghệ. Nhiệm vụ của nó là định nghĩa ra một interface chuẩn, để các actor từ bên ngoài có thể giao tiếp được với Application, mà không cần quan tâm xem ai hay cái gì sẽ implement (triển khai) cái interface đó.
Ví dụ kinh điển là cổng USB: nó cho phép vô số loại device (thiết bị) giao tiếp với máy tính, miễn là chúng có một USB adapter phù hợp. Theo chiều ngược lại, các Port cũng là nơi để Application giao tiếp ra các hệ thống hoặc service bên ngoài, ví dụ như database, message broker, hay các application khác.
Cách làm này giúp bạn kiểm thử (test) Application core một cách hoàn toàn độc lập mà không cần khởi động database hay các hệ thống phức tạp khác.
2.2 Adapter trong Hexagonal Architecture
Một Adapter sẽ chủ động tương tác với Application thông qua một Port, và nó là nơi triển khai một technology (công nghệ) cụ thể. Ví dụ dễ hiểu nhất: một REST controller chính là một Adapter. Nó cho phép một client (như trình duyệt web) giao tiếp được với Application của chúng ta.
Điểm hay là, bạn có thể tạo ra bao nhiêu Adapter cũng được cho cùng một Port, và việc này hoàn toàn không gây ra rủi ro hay ảnh hưởng gì đến bản thân các Port hay Application Core.
2.3 Application trong Hexagonal Architecture
Application chính là phần core (lõi) của toàn bộ hệ thống. Bên trong nó chứa các Application Services có nhiệm vụ điều phối các chức năng hoặc các use case của phần mềm. Nó cũng là nơi chứa Domain Model – tức là toàn bộ business logic (logic nghiệp vụ) – được thể hiện thông qua các khái niệm như Aggregate, Entity, và Value Object.
Application được mô tả bằng một hình lục giác. Hình lục giác này nhận các command (lệnh thực thi) hoặc query (truy vấn dữ liệu) từ các Port đi vào. Đồng thời, nó cũng gửi các request (yêu cầu) ra các actor bên ngoài (như database) thông qua các Port khác.
Khi được áp dụng cùng với Domain-Driven Design (DDD), phần Application sẽ bao gồm cả hai layer là Application layer và Domain layer. Các layer còn lại như User Interface (giao diện người dùng) và Infrastructure (hạ tầng) sẽ được đặt ở bên ngoài hình lục giác đó.
3. Ví dụ thực tế về Hexagonal Architecture
Giả sử chúng ta được giao nhiệm vụ code một chức năng nghiệp vụ đơn giản: "Tìm tất cả các khách hàng VIP đang hoạt động":
- Đang hoạt động (active) có nghĩa là khách hàng có trạng thái
status = 1trong database. - VIP có nghĩa là khách hàng đã chi tiêu tổng cộng trên
10,000,000 VND.
Cách làm sai: class CustomerService (đáng lẽ chỉ giữ logic nghiệp vụ) lại biết quá nhiều về cấu trúc và ngôn ngữ database (MySQL).
public class CustomerService {
public List<Customer> findActiveVipCustomers() {
List<Customer> customers = new ArrayList<>();
// --- LOGIC LEAK NẰM Ở ĐÂY ---
// 1. Logic nghiệp vụ bị phụ thuộc chặt chẽ vào cú pháp SQL.
// 2. Logic nghiệp vụ biết rõ tên bảng (`customers`), tên các cột (`status`, `total_spent`).
String sqlQuery = "SELECT id, name, email FROM customers WHERE status = 1 AND total_spent > 10000000";
try {
// Chi tiết về việc kết nối DB cũng vậy
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/shopdb", "user", "pass");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sqlQuery);
while (rs.next()) {
customers.add(new Customer(rs.getInt("id"), rs.getString("name"), rs.getString("email")));
}
} catch (SQLException e) {
}
return customers;
}
}
Làm sao bạn có thể test hàm findActiveVipCustomersmà không cần phải khởi tạo một database MySQL thật? Bạn không thể kiểm tra logic một cách độc lập và viết Unit Test cho nó được.
Cách làm đúng: Dùng Ports and Adapters, Port ở đây sẽ là một interface định nghĩa những gì class nghiệp vụ cần, không chứa code thực thi.
// Port
public interface ICustomerRepository {
// Interface này không biết gì về SQL hay MongoDB.
// Nó chỉ định nghĩa một nhu cầu: "tìm khách hàng theo trạng thái và mức chi tiêu".
List<Customer> findByStatusAndMinSpending(CustomerStatus status, double minSpending);
}
// Enum để thể hiện nghiệp vụ, thay vì dùng số 1, 2...
public enum CustomerStatus {
ACTIVE,
INACTIVE
}
Class CustomerService sẽ không biết gì về database, chỉ biết về interface ICustomerRepository.
public class CustomerService {
private final ICustomerRepository customerRepository;
public CustomerService(ICustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public List<Customer> findActiveVipCustomers() {
return customerRepository.findByStatusAndMinSpending(CustomerStatus.ACTIVE, 10000000.0);
}
}
Tạo Adapter cho MySQL Đây là class nằm ở tầng hạ tầng (infrastructure), có nhiệm vụ triển khai interface ICustomerRepository bằng MySQL.
public class MySqlCustomerRepository implements ICustomerRepository {
@Override
public List<Customer> findByStatusAndMinSpending(CustomerStatus status, double minSpending) {
int statusValue = (status == CustomerStatus.ACTIVE) ? 1 : 0;
String sqlQuery = "SELECT id, name, email FROM customers WHERE status = " + statusValue + " AND total_spent > " + minSpending;
System.out.println("Đã thực thi câu lệnh SQL cho MySQL!");
return new ArrayList<Customer>(); // Giả lập kết quả trả về
}
}
Nếu bạn muốn đổi sang MongoDB? Đơn giản là tạo một Adapter mới MongoDbCustomerRepository.java và triển khai lại hàm findByStatusAndMinSpending bằng API của MongoDB. Class CustomerService không cần thay đổi.
Bạn có thể dễ dàng tạo một MockCustomerRepository trong Unit Test để giả lập dữ liệu trả về và kiểm tra logic của CustomerService mà không cần đến database thật.
Đây chính là sức mạnh của việc bạn coi database như một actor bên ngoài và giao tiếp với nó qua Port and Adapter, giúp tránh hoàn toàn sự rò rỉ về logic và giúp cho code dễ maintain hơn.
4. Kết luận
Mục tiêu cuối cùng của một kiến trúc tốt là quản lý được sự phức tạp ngày càng tăng cao của codebae, giúp hệ thống phát triển một cách bền vững. Nếu có thời gian hãy thử ứng dụng Hexagonal Architecture vào project hiện tại và cho chúng mình biết cảm nhận của bạn nhé.