Chuyển đến nội dung chính (Skip to content)
Mục lục bài viết
Open Source Essentials

Open Source Essentials 051.2: Kiến trúc của Phần mềm

Open Source Essentials 051.2: Kiến trúc của Phần mềm

Hiểu client/server, frontend/backend, SPA, API, REST và ranh giới triển khai; lần theo request trong lab loopback, phân biệt lỗi HTTP với lỗi kết nối.

Một nhân viên mở màn hình tra cứu phiếu hỗ trợ nhưng chỉ thấy vòng chờ. Trang giao diện đã tải, còn yêu cầu lấy dữ liệu trả lỗi. Đồng nghiệp kết luận “server hỏng”, dù lỗi có thể nằm ở đường dẫn API, quyền truy cập hoặc dịch vụ phụ thuộc. Hiểu ranh giới thành phần giúp Help Desk mô tả đúng triệu chứng và chuyển bằng chứng tới đúng nhóm.

Bài 051.2, Kiến trúc phần mềm, tham chiếu Open Source Essentials 050-100 trang in 21–37, được viết độc lập và đối chiếu ngày 23/10/2026. Bạn cần hiểu chương trình, module và dòng lệnh cơ bản. Sau bài, bạn nên phân biệt vai trò client/server, API nội bộ với API mạng, cách điều hướng web và lựa chọn nguyên khối hoặc microservices. Lab dùng dữ liệu DEMO, không truy cập hệ thống nghiệp vụ.

Client và server là vai trò trong tương tác

Client khởi tạo yêu cầu; server tiếp nhận và phục vụ theo giao thức. Hai vai trò có thể nằm trên hai máy, hai tiến trình cùng máy hoặc các thành phần phù hợp của một hệ thống. Máy chủ vật lý chuyên dụng là một cách triển khai, không phải điều kiện định nghĩa server. Một chương trình cũng có thể phục vụ browser rồi trở thành client khi gọi dịch vụ khác.

Giả sử browser hỏi trạng thái phiếu, API hỏi kho dữ liệu rồi trả JSON. Browser là client của API; nếu kho dữ liệu là tiến trình riêng nhận kết nối, API là client của nó. Vẽ mũi tên theo từng trao đổi giúp tránh nhầm địa chỉ người dùng truy cập với nơi thực sự phát sinh lỗi.

Thin client dựa nhiều vào xử lý từ xa; thick client giữ nhiều logic hoặc dữ liệu tại thiết bị. Đây là mức phân bố trách nhiệm, không phải nhãn quyết định CPU bắt buộc mạnh hay yếu. Một ứng dụng cài trên máy vẫn có thể cần kết nối để đăng nhập hoặc đồng bộ. Browser cũng có thể thực hiện xử lý đáng kể bằng JavaScript.

Trong tình huống mất mạng, hãy hỏi chức năng nào còn dùng được, dữ liệu nào đã có cục bộ và thao tác nào phải chờ server. Không suy ra mọi thick client hoạt động hoàn toàn ngoại tuyến. Khi bàn giao sự cố, ghi riêng việc giao diện mở được và việc truy cập dữ liệu thành công; đó là hai bằng chứng khác nhau.

Frontend, backend và cách tải trang

Frontend cung cấp tương tác và biểu diễn cho người dùng. Backend thực hiện các trách nhiệm phía dịch vụ như xử lý yêu cầu, kiểm tra quyền và truy cập dữ liệu. Ranh giới triển khai có thể thay đổi; không có quy tắc backend luôn chiếm phần lớn mã. Việc trường dữ liệu bị ẩn trên giao diện cũng không thay thế kiểm tra quyền phía server.

Một trang được phục vụ dưới dạng tệp tĩnh vẫn có thể chứa JavaScript thực hiện chức năng ứng dụng. Vì vậy, chỉ nhìn cách hosting hoặc đuôi HTML chưa đủ để kết luận có ứng dụng hay không. Điều có ích hơn khi chẩn đoán là tìm tài liệu ban đầu, script, tài sản CSS và các yêu cầu dữ liệu tiếp theo.

MDN mô tả SPA là cách tải một tài liệu web rồi cập nhật nội dung bằng API JavaScript. SPA vẫn có thể đổi URL bằng cơ chế điều hướng phù hợp. Nó không tự biết dữ liệu server đã thay đổi: nhà phát triển phải triển khai lấy lại, polling, push hoặc cơ chế khác. Không dùng từ “một trang” để suy ra chỉ có một màn hình.

Ứng dụng nhiều trang thường lấy tài liệu mới khi chuyển phần nội dung. Điều đó không đòi hỏi luôn có phản hồi redirect HTTP: một liên kết tới URL khác có thể nhận ngay HTML. Cũng không thể tuyên bố mọi SPA nhanh hơn hay mọi ứng dụng nhiều trang khó dùng hơn. Cần đo nhiệm vụ thực tế, khả năng truy cập, lượng mã tải và hành vi khi JavaScript lỗi.

API là hợp đồng, không phải lớp bảo mật tự động

API mô tả cách thành phần khác yêu cầu chức năng hoặc dữ liệu. Hàm readTicket(id) trong module là API ở mức chương trình; endpoint GET /tickets/DEMO-7 là giao diện mạng. Không phải API nào cũng dùng Internet hoặc HTTP. Hai phía cần thống nhất đầu vào, đầu ra, lỗi và điều kiện tương thích.

Trong lab, hàm trả object hoặc null; route chuyển chúng thành phản hồi 200 hoặc 404. Nếu thay kho lưu trữ mà vẫn giữ hợp đồng hàm, phần xử lý HTTP có thể giữ nguyên. Đây là lợi ích của tách trách nhiệm, nhưng vẫn phải kiểm thử tích hợp: khác biệt kiểu dữ liệu, thời gian xử lý hoặc lỗi có thể vượt ranh giới.

API không tự ngăn lộ thông tin nhạy cảm. Một endpoint được đoán đúng có thể vẫn truy cập được nếu server thiếu kiểm tra quyền. Giao diện chỉ giấu nút hoặc trường không làm endpoint an toàn. Lab chỉ dùng bản ghi cố định; khi xây ứng dụng thật phải thiết kế xác thực, phân quyền, validation và bảo vệ truyền tải theo ngữ cảnh.

Client gửi yêu cầu qua route và hợp đồng hàm; mỗi ranh giới có đầu vào, đầu ra riêng.
Hình 1. Client gửi yêu cầu qua route và hợp đồng hàm; mỗi ranh giới có đầu vào, đầu ra riêng. Mở hình để xem chi tiết.

REST và trạng thái cần được hiểu đúng

Luận án của Fielding định nghĩa REST bằng các ràng buộc gồm client/server, stateless, cache, giao diện thống nhất, phân lớp và code-on-demand tùy chọn. Dùng HTTP cùng JSON chưa chứng minh đáp ứng đầy đủ REST.

Stateless yêu cầu mỗi request đủ ngữ cảnh để hiểu yêu cầu; nó không cấm server lưu tài nguyên trong cơ sở dữ liệu và không loại bỏ việc kiểm tra quyền. Không suy ra “không lưu phiên” đồng nghĩa “không cần xác thực”. Lab chỉ là API HTTP đọc bản ghi, không được dùng làm chứng nhận thiết kế REST đầy đủ.

Nguyên khối và microservices: chọn ranh giới triển khai

Ứng dụng nguyên khối thường có một đơn vị triển khai chính. Bên trong vẫn có thể chia module rõ ràng; nhiều bản sao cũng có thể cùng chạy để phục vụ tải. Một cơ sở dữ liệu ở tiến trình riêng không tự biến ứng dụng thành microservices, và nguyên khối không có nghĩa mọi thứ phải nằm trong một tệp hoặc một máy.

Microservices tổ chức thành dịch vụ có ranh giới trách nhiệm và khả năng triển khai độc lập. Microsoft nêu cả lợi ích lẫn chi phí: có thể mở rộng riêng, nhưng phải xử lý giao tiếp mạng, dữ liệu và vận hành phức tạp hơn. Tách dịch vụ không tự loại bỏ phụ thuộc hoặc xung đột giữa nhóm.

Với công cụ hỗ trợ nhỏ do một nhóm quản lý, nguyên khối chia module có thể là lựa chọn dễ vận hành. Nếu các miền nghiệp vụ thực sự cần lịch phát hành và tải khác nhau, cân nhắc tách dịch vụ sau khi xác định chi phí. Không chọn chỉ vì sơ đồ có nhiều ô. Đây là tình huống phân tích, chưa phải phép đo chi phí hoặc độ sẵn sàng.

Lab: lần theo yêu cầu qua một API nhỏ

Dùng Node.js có hỗ trợ ES modules, tạo thư mục riêng rồi lưu server.mjs. Mã chỉ đọc dữ liệu cố định và bind loopback; không có database, tài khoản hoặc ghi tệp. Cổng được hệ điều hành cấp rồi in ra, tránh giả định cổng quen thuộc còn trống.

import http from 'node:http';
const readTicket = id => id === 'DEMO-7'
  ? {id, status: 'open'} : null;

const server = http.createServer((req, res) => {
  const send = (status, value, headers = {}) => {
    res.writeHead(status, {
      'Content-Type': 'application/json; charset=utf-8',
      ...headers
    });
    res.end(JSON.stringify(value));
  };
  if (req.method !== 'GET') {
    return send(405, {error: 'method'}, {Allow: 'GET'});
  }
  const path = new URL(req.url, 'http://localhost').pathname;
  const match = /^\/tickets\/([A-Z]+-\d+)$/.exec(path);
  if (!match) return send(404, {error: 'route'});
  const ticket = readTicket(match[1]);
  if (!ticket) return send(404, {error: 'ticket'});
  send(200, ticket);
});
server.listen(0, '127.0.0.1', () => {
  console.log(`http://127.0.0.1:${server.address().port}`);
});

Chạy node server.mjs trong terminal thứ nhất. Ghi URL được in ra. Trong terminal thứ hai, thay cổng 12345 bên dưới bằng giá trị của bạn. curl -i hiển thị cả status và header; không chỉ nhìn body hoặc exit status của curl.

curl -i http://127.0.0.1:12345/tickets/DEMO-7
curl -i http://127.0.0.1:12345/tickets/DEMO-8
curl -i http://127.0.0.1:12345/missing
curl -i -X POST http://127.0.0.1:12345/tickets/DEMO-7

Kết quả lần lượt: 200 với id DEMO-7 và status open; 404 với error ticket; 404 với error route; 405 với error method và header Allow: GET. Hai trường hợp 404 có cùng status nhưng đi tới hai nhánh xử lý khác nhau. Không có nghĩa bản ghi DEMO-8 bị xóa, vì fixture chưa từng định nghĩa nó.

Dừng tiến trình bằng Ctrl+C rồi gọi lại URL. Khi không còn listener trên cổng đó, client phải báo lỗi kết nối thay vì nhận một status HTTP mới. Không khởi chạy dịch vụ khác vào cổng cũ trong bước thử này. Phân biệt lỗi trước khi có phản hồi với phản hồi lỗi từ ứng dụng là kỹ năng quan trọng khi ghi ticket hỗ trợ.

Fixture phân biệt các nhánh status, JSON và lỗi kết nối sau khi dừng listener.
Hình 2. Fixture phân biệt các nhánh status, JSON và lỗi kết nối sau khi dừng listener. Mở hình để xem chi tiết.

Quan sát, lỗi thường gặp và giới hạn

Bản kiểm chứng chạy Node.js 24.22.0 trong container Linux, không đặc quyền, filesystem chính chỉ đọc và tắt mạng ngoài. HTTP client tự động gọi loopback, so status, JSON và Allow đúng bốn trường hợp, sau đó xác nhận kết nối thất bại khi đóng server. Tài liệu API dòng 24 được đối chiếu riêng; không nhận rằng runtime đã thử mọi bản vá mới hơn.

Nếu mọi URL đều lỗi kết nối, kiểm tra tiến trình còn chạy và cổng bạn đã chép trước. Nếu nhận 404 route, so pathname với biểu thức nhận diện. Nếu nhận 404 ticket, định dạng đường dẫn đã khớp nhưng fixture không có id. Nếu nhận 405, kiểm tra method. Thứ tự này giúp tránh sửa dữ liệu khi yêu cầu còn chưa tới đúng nhánh.

Kết quả không kiểm chứng TLS, browser, phiên đăng nhập, xử lý tải, lưu dữ liệu hay microservices phân tán. Có thể vẽ thêm dịch vụ thông báo giả định và đánh dấu điểm lỗi, nhưng không trình bày sơ đồ như bằng chứng hệ thống đã chạy. Trước khi gửi log thật, loại bỏ token và dữ liệu cá nhân theo quy trình của tổ chức.

Tự kiểm tra

  1. API trả 404 khác gì client không kết nối được? Trường hợp đầu đã có phản hồi HTTP; trường hợp sau chưa có phản hồi từ listener mong đợi.
  2. Chuyển hàm vào tệp module riêng có tạo microservice không? Chưa; cần xét ranh giới tiến trình và triển khai, không chỉ số tệp.
  3. Browser hiển thị nút bị ẩn có bảo đảm endpoint bị cấm không? Không; server phải kiểm tra quyền cho yêu cầu.
  4. SPA có bắt buộc giữ nguyên URL không? Không. Cách cập nhật tài liệu và cách quản lý lịch sử điều hướng là các vấn đề liên quan nhưng khác nhau.
  5. Có thể suy ra hệ thống production dùng nguyên khối từ lab này không? Không. Lab minh họa đường request trong chương trình tự viết, không khảo sát một hệ thống bên ngoài.

Nguồn tham khảo

Bài liên quan trong bộ LPI

Tìm hiểu thêm