Giới thiệu
Trong năm nay, tôi đã quyết định thực hiện một dự án: không chỉ dừng lại ở việc tạo giao diện mà còn tìm hiểu lại các khái niệm cơ bản về lập trình và kỹ thuật phần mềm, những điều có thể đã bị lãng quên trong quá trình làm việc hàng ngày. Một trong những chủ đề quan trọng mà tôi muốn khám phá hôm nay là kiến trúc theo lớp, đặc biệt là mối quan hệ giữa modules, services, controllers, repositories và clients.
Hãy cùng nhau khơi dậy những kiến thức cũ và cùng thảo luận nhé!
Mục lục
- Module
- Controller
- Service
- Repository
- Client
- Tóm tắt
- Thực hành tốt nhất
- Những cạm bẫy thường gặp
- Mẹo hiệu suất
- Khắc phục sự cố
Module
Module có thể được xem như là bước chuẩn bị cho dịch vụ. Nếu bạn chưa quen với thuật ngữ này, hãy coi nó như là việc sắp xếp và chuẩn bị tất cả các thành phần cần thiết trước khi bắt đầu công việc.
Module thực hiện các nhiệm vụ sau:
- Cấu hình các provider
- Xuất các dịch vụ để sử dụng trong các module khác
- Nhập các phụ thuộc mà các
controllers,services,... cần thiết
Dưới đây là một ví dụ về cách tạo một module:
javascript
class UserModule {
static create() {
const repository = new UserRepository();
const service = new UserService(repository);
const controller = new UserController(service);
return { repository, service, controller }
}
}
💡 Lưu ý: Trong các kiến trúc phổ biến như Node/Java (không sử dụng framework), thuật ngữ
modulekhông phải lúc nào cũng được dùng cho khái niệm này. Thường thì, người ta nói đếninversion/injection of dependencies. Đối với bài viết này,moduleđược sử dụng như một lớp trừu tượng để tổ chức các phụ thuộc và khởi tạo thành phần, nhưng nó có thể mang nhiều ý nghĩa khác trong các ngữ cảnh khác.
Controller
Controller là điểm truy cập chính để định nghĩa các endpoint của API. Nó xử lý các yêu cầu và phản hồi, cũng như xác định loại yêu cầu mà các endpoint chấp nhận (GET, POST, DELETE, v.v.).
Từ góc nhìn của một người chưa quen với backend, controller nổi bật như là lớp dễ hiểu nhất về chức năng của dịch vụ.
Ngoài ra, controller còn đảm nhiệm các công việc khác:
- Chuyển đổi dữ liệu phản hồi sử dụng DTOs
- Ủy thác logic kinh doanh cho các
services
javascript
class UserController {
constructor(userService) {
this.userService = userService;
}
getUsers(){
try {
const users = this.userService.getAllUsers();
return { success: true, data: users };
} catch (error) {
return { success: false, error: error.message };
}
}
createUser(name, email) {
try {
const user = this.userService.createUser(name, email);
return { success: true, data: user };
} catch (error) {
return { success: false, error: error.message };
}
}
}
Service
Service là nơi mà mọi phép thuật diễn ra. Tất cả các hoạt động nặng nề và quy tắc kinh doanh đều được định nghĩa và thực hiện bên trong service. Nếu một endpoint trả về dữ liệu đã được lọc từ một danh sách lớn, có khả năng service sẽ làm công việc lọc này. Controller không cần phải biết các chi tiết phức tạp; chỉ cần gọi phương thức cần thiết và service sẽ xử lý mọi thứ.
javascript
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
getAllUsers() {
return this.userRepository.findAll();
}
createUser(name, email) {
if(!email.includes("@")) {
throw new Error("Email không hợp lệ");
}
return this.userRepository.create({ name, email });
}
}
Repository
Repository là lớp chịu trách nhiệm truy cập và thực hiện các thao tác với cơ sở dữ liệu. Nó đơn giản hóa các tương tác với cơ sở dữ liệu, đóng gói các truy vấn và tạo ra một giao diện sạch để truy cập dữ liệu.
javascript
class UserRepository {
constructor() {
this.users = [
{id: 1, name: "John", email: "john@email.com"},
{id: 2, name: "Jane", email: "jane@email.com"}
];
}
findAll() {
return this.users;
}
create(user) {
const newUser = { id: Date.now(), ...user };
this.users.push(newUser);
return newUser;
}
}
Client
Client là lớp chịu trách nhiệm giao tiếp với các dịch vụ bên ngoài. Nếu ứng dụng cần sử dụng các API khác, đây sẽ là điểm liên lạc giữa chúng, do đó Client thường chịu trách nhiệm:
- Thực hiện các yêu cầu HTTP đến các dịch vụ khác
- Chuyển đổi dữ liệu nhận được để chúng có thể được sử dụng trong ứng dụng
- Quản lý cache, headers và xác thực
javascript
class EmailClient {
constructor(apiKey) {
this.apiKey = apiKey;
}
async sendEmail(to, subject, message) {
const response = await fetch('https://some-api.email-service.com/send', {
method: 'POST',
headers: {
'Authorization': this.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ to, subject, message })
});
if (!response.ok) {
throw new Error('Gửi email thất bại');
}
return response.json();
}
}
Tóm tắt
Khi tách biệt các trách nhiệm theo lớp, quy trình hoạt động sẽ diễn ra như sau:
- Khi ứng dụng được khởi động (AppStart)
Moduletạo và tiêm các phụ thuộcUserRepositoryđược truyền choUserServiceUserServiceđược truyền choUserControllerEmailClientcó thể được tiêm vàoUserService, nếu cần thiết
- Một yêu cầu được thực hiện
- Người dùng ->
Controller->Service Servicetìm kiếm dữ liệu trongRepository(cơ sở dữ liệu) hoặcClient(API bên ngoài)- Phản hồi quay trở lại theo cùng một con đường đến người dùng
- Người dùng ->
Thực hành tốt nhất
- Áp dụng nguyên tắc SOLID: Đảm bảo mỗi lớp chỉ thực hiện một nhiệm vụ duy nhất.
- Sử dụng Dependency Injection: Giúp dễ dàng kiểm tra và bảo trì mã nguồn.
Những cạm bẫy thường gặp
- Phức tạp hóa quá mức: Đôi khi không cần phải chia nhỏ quá nhiều lớp.
- Không quản lý tốt các phụ thuộc: Điều này có thể dẫn đến mã nguồn khó hiểu và bảo trì.
Mẹo hiệu suất
- Tối ưu hóa truy vấn: Sử dụng kỹ thuật như pagination và caching cho các truy vấn lớn.
- Giảm thiểu số lần gọi API: Chỉ gọi API khi thật sự cần thiết.
Khắc phục sự cố
- Kiểm tra log: Xem log lỗi để xác định nguyên nhân.
- Thêm xử lý lỗi: Đảm bảo rằng mọi yêu cầu đều được xử lý lỗi hợp lý.
Kết luận
Kiến trúc theo lớp giúp tách biệt các trách nhiệm và làm cho mã nguồn trở nên dễ bảo trì hơn. Hy vọng qua bài viết này, bạn đã có cái nhìn rõ hơn về kiến trúc backend. Hãy bắt đầu áp dụng ngay vào dự án của bạn để nâng cao chất lượng mã nguồn và quy trình phát triển!
Nếu bạn có bất kỳ câu hỏi nào, đừng ngần ngại để lại ý kiến của bạn dưới đây nhé!