Nội dung chi tiết Chương 2: Kiến trúc phần mềm (Software Architecture)
1. Kiến trúc phần mềm là gì?
Kiến trúc phần mềm là một tập hợp các cấu trúc cơ bản cần thiết để suy luận về một hệ thống phần mềm, cũng như các quy tắc để tạo ra những cấu trúc và hệ thống đó. Mỗi cấu trúc bao gồm sự sắp xếp của các yếu tố phần mềm, mối quan hệ giữa các yếu tố và tính chất của chúng. Không có một cấu trúc đơn lẻ nào có thể được coi là toàn bộ kiến trúc. Có 3 loại cấu trúc quan trọng nhất:
- Module (Cấu trúc Mô-đun): Là các thành phần nhỏ gọn đóng góp vào sự vận hành tổng thể của hệ thống, có tính thống nhất và thực hiện các nhiệm vụ riêng biệt. Các mô-đun được giao cho các nhóm lập trình (programming teams) xử lý độc lập.
- Component and Connector (Cấu trúc Thành phần và Kết nối - C&C): Tập trung vào cách các thành phần tương tác với nhau tại thời gian chạy (runtime) để thực hiện chức năng hệ thống. Một thành phần luôn là một thực thể runtime, ví dụ như các dịch vụ, cơ sở hạ tầng, và các mối quan hệ đồng bộ hóa.
- Allocation (Cấu trúc Phân bổ): Mô tả quá trình ánh xạ từ cấu trúc phần mềm sang môi trường thực tế của hệ thống (tổ chức, phát triển, cài đặt, thực thi). Ví dụ: giao mô-đun cho các team, gán vị trí file, hoặc triển khai component lên phần cứng.
Kiến trúc phần mềm là một sự trừu tượng vì nó chọn lọc các chi tiết sắp xếp, tương tác và loại bỏ các chi tiết triển khai nội bộ riêng tư. Sự trừu tượng này là rất cần thiết để xem xét các kiến trúc phức tạp. Mỗi hệ thống đều nên có tài liệu kiến trúc, mặc dù một kiến trúc có thể tồn tại độc lập với thông số kỹ thuật của nó (như trong trường hợp mất tài liệu hoặc mã nguồn).
2. Architecture Styles (Các phong cách kiến trúc)
A. Kiến trúc nguyên khối (Monolithic Architecture)
Đây là mô hình truyền thống, được xây dựng như một đơn vị thống nhất, độc lập và không phụ thuộc ứng dụng khác. Tất cả các thành phần như truy cập dữ liệu, logic nghiệp vụ, giao diện người dùng đều liên kết chặt chẽ.
- Ưu điểm:
- Thiết kế (Design): Đơn giản do chỉ có 1 code base duy nhất với các function phụ thuộc nhau.
- Phát triển (Development): Dễ dàng hơn trong giai đoạn đầu vì mọi thứ nằm chung một code base.
- Triển khai (Deployment): Chỉ cần thực thi 1 file hoặc directory nên triển khai dễ dàng.
- Gỡ lỗi (Debugging): Dễ theo dõi request và tìm sự cố vì code nằm chung một nơi.
- Kiểm thử (Testing): Dễ dàng test toàn bộ ứng dụng trong môi trường đồng nhất.
- Hiệu suất (Performance): Đạt hiệu suất cao đối với các ứng dụng ít phức tạp.
- Bảo trì: Dễ bảo trì hơn nhờ tính đơn giản.
- Giao tiếp: Giao tiếp giữa các component nhanh hơn.
- Nhược điểm:
- Tốc độ phát triển chậm: Khi ứng dụng lớn, quá trình phát triển trở nên phức tạp và chậm đi.
- Khả năng mở rộng (Scalability): Khó mở rộng theo chiều ngang và không thể mở rộng riêng lẻ từng thành phần.
- Độ tin cậy (Reliability/Fault Tolerance): Nếu một phần bị lỗi, toàn bộ ứng dụng có thể sụp đổ.
- Rào cản công nghệ: Bị giới hạn bởi công nghệ ban đầu; thay đổi ngôn ngữ/framework tốn kém và ảnh hưởng toàn hệ thống.
- Thiếu linh hoạt: Các thành phần gắn kết chặt chẽ làm giảm tính linh hoạt.
- Triển khai khó khăn khi nâng cấp: Một thay đổi nhỏ cũng bắt buộc phải triển khai lại toàn bộ hệ thống.
B. Kiến trúc phân tán (Distributed Architecture)
Hệ thống được triển khai trên nhiều đơn vị làm việc cùng nhau.
- Ưu điểm:
- Khả năng chịu lỗi và tin cậy: Một dịch vụ lỗi không làm sập các dịch vụ khác.
- Khả năng thích ứng: Các đơn vị được triển khai riêng biệt, dễ xác định và áp dụng thay đổi, giảm rủi ro triển khai và thu hẹp phạm vi kiểm thử.
- Khả năng mở rộng: Tập hợp các máy độc lập giúp dễ dàng mở rộng theo chiều ngang.
- Độ trễ thấp: Nhiều máy chủ có thể đặt gần người dùng để phản hồi truy vấn nhanh hơn.
- Nhược điểm:
- Độ phức tạp cao: Quản lý nhiều chương trình nhỏ phức tạp hơn rất nhiều, đòi hỏi quản lý giao dịch phân tán, đồng bộ hóa dữ liệu và xử lý lỗi.
- Bắt buộc deploy tự động: Yêu cầu cơ chế triển khai tự động hóa.
- Giảm hiệu suất tổng thể: Tiêu tốn nhiều tài nguyên (bộ nhớ, CPU, băng thông mạng) hơn so với kiến trúc nguyên khối cùng quy mô.
- Khó gỡ lỗi: Việc tổng hợp log và dò theo dấu vết một request thất bại qua nhiều môi trường chạy khác nhau rất khó khăn.
- Chi phí cao: Tăng dung lượng bộ nhớ, trùng lặp tài nguyên (VMs, container) và tốn băng thông mạng.
C. Phân loại theo cách chia cấu trúc tổng thể
- Phân vùng kỹ thuật (Technical Partitioning): Tổ chức theo chức năng kỹ thuật (VD: Presentation, Business, Service, Persistence).
- Ưu điểm: Phù hợp với tổ chức team theo vai trò (FE, BE); source code độc lập nên sửa tầng này không ảnh hưởng tầng kia; dễ viết unit test cho từng tầng.
- Nhược điểm: Nếu quy tắc nghiệp vụ (business) thay đổi, khả năng cao phải sửa lại toàn bộ các tầng.
- Phân vùng theo domain (Domain Partitioning): Phân chia theo quy tắc nghiệp vụ/domain (VD: Đặt hàng, Thanh toán, Kho).
- Ưu điểm: Khi nghiệp vụ thay đổi, chỉ service chứa domain đó bị ảnh hưởng; rất phù hợp cho các nhóm đa chức năng (cross-functional team).
- Nhược điểm: Khó viết unit test do logic của các tầng phục vụ chung một domain bị trộn lẫn.
3. Architecture Patterns (Các mẫu kiến trúc)
A. Kiến trúc phân lớp (Layered Architecture)
Thường gồm 4 lớp tuần tự: Presentation (hiển thị giao diện), Business/Service (xử lý logic nghiệp vụ), Persistence (gửi yêu cầu đến database), và Database (quản lý lưu trữ). Các lớp có thể đóng (bắt buộc truyền dữ liệu tuần tự qua từng lớp) hoặc mở (cho phép bỏ qua lớp ngay dưới).
- Ưu điểm: Tách biệt rõ ràng logic giữa các tầng; thuộc loại monolithic nên ít phức tạp; rất phổ biến nên lập trình viên dễ làm quen; phù hợp với tổ chức team chia theo chuyên môn kỹ thuật.
- Nhược điểm: Gây lãng phí tài nguyên do dữ liệu bắt buộc phải chạy qua các tầng dù tầng đó không có nhiệm vụ xử lý dữ liệu.
B. Client – Server Architecture
Bao gồm nhiều máy khách (Client - thiết bị gửi yêu cầu) và một máy chủ trung tâm (Server - xử lý và đáp ứng yêu cầu) giao tiếp qua giao thức như HTTP hay SQL.
- Ưu điểm:
- Quản lý tập trung: Dễ bảo trì, cập nhật và quản lý bảo mật từ một vị trí.
- Khả năng mở rộng: Dễ dàng thêm máy khách hoặc nâng cấp máy chủ.
- Tối ưu hóa tài nguyên: Server lo xử lý/lưu trữ nặng, Client lo UI tương tác.
- Độ tin cậy và khả dụng cao: Được hỗ trợ bởi cơ sở hạ tầng máy chủ mạnh mẽ.
- Nhược điểm:
- Rủi ro từ điểm tập trung: Server dễ bị quá tải khi có quá nhiều request, và nếu Server sập thì toàn bộ hệ thống ngừng hoạt động.
- Chi phí: Server đắt đỏ và cần chuyên gia mạng quản lý.
- Bảo mật & Dữ liệu: Dễ bị tấn công từ chối dịch vụ (DoS); dữ liệu có thể bị thất thoát hoặc thay đổi trên đường truyền.
C. Pipeline Architecture
Dùng các kênh truyền một chiều (Pipes) để gửi dữ liệu giữa các bộ lọc độc lập (Filters).
- Ưu điểm: Dễ thay đổi, bảo trì và tái sử dụng từng filter; dễ nâng cấp bằng cách lắp thêm filter mới.
- Nhược điểm: 2 filter kề nhau bắt buộc phải tuân thủ chung một định dạng dữ liệu. Mẫu này chỉ hợp cho các ứng dụng xử lý dữ liệu qua nhiều công đoạn tuần tự.
D. Kiến trúc hướng sự kiện (Event-Driven Architecture)
Các thành phần giao tiếp linh hoạt thông qua các sự kiện (Event) do Event Producer sinh ra và Event Consumer tiếp nhận. Gồm 2 mô hình chính:
- Broker topology: Phân phối message qua message broker (như RabbitMQ) tới các node mà không cần trung tâm điều phối.
- Mediator topology: Có "Event Mediator" làm trung gian nhận Event Queue và điều phối luồng sự kiện tới các processor.
- Lưu ý từ tài liệu: Mô hình này tạo ra hệ thống linh hoạt, dễ mở rộng và tích hợp, giải quyết các sự kiện tuần tự, nhất quán thông qua cơ chế gửi và quên (fire-and-forget).
E. Kiến trúc hướng dịch vụ (Service-Oriented Architecture - SOA)
Sử dụng các "dịch vụ" độc lập cung cấp tính năng doanh nghiệp để tạo thành ứng dụng.
- Ưu điểm (mục đích sử dụng): Các dịch vụ giao tiếp được qua nhiều nền tảng/ngôn ngữ; cho phép tái sử dụng một dịch vụ (VD: xác thực người dùng) cho nhiều hệ thống phức tạp trong toàn bộ tổ chức doanh nghiệp.
F. Kiến trúc Microservices
Chia hệ thống thành các dịch vụ cực nhỏ (microservice), mỗi cái thực hiện một chức năng duy nhất, sở hữu database riêng và giao tiếp qua API Gateway hoặc Message Broker (P2P hoặc Pub/Sub).
Abstract Factory (Object Scope)
Lý thuyết: Cung cấp một giao diện để tạo ra các "họ" (families) đối tượng liên quan hoặc phụ thuộc lẫn nhau mà không cần chỉ định rõ các lớp cụ thể của chúng
.
Ví dụ: Ứng dụng quản lý Cửa hàng nội thất (Furniture Shop). Giao diện FurnitureFactory khai báo việc tạo ra một họ các sản phẩm như createChair(), createSofa()
. Có các nhà máy sản xuất cụ thể như ModernFurnitureFactory sẽ tạo ra một họ đồ nội thất phong cách hiện đại (ModernChair, ModernSofa), trong khi VictorianFurnitureFactory chuyên tạo đồ phong cách cổ điển (VictorianChair, VictorianSofa)
.
Singleton (Object Scope)
Lý thuyết: Đảm bảo rằng một lớp chỉ có duy nhất một thể hiện (instance) được tạo ra và cung cấp một điểm truy cập toàn cục tới đối tượng đó cho toàn bộ mã nguồn
. Thường được hiện thực hóa bằng một constructor private và phương thức tĩnh getInstance()
.
Ví dụ: Singleton rất hữu ích trong việc đảm bảo các luồng (threads) khác nhau cùng truy cập đồng bộ vào một tài nguyên duy nhất. Thường được sử dụng làm các lớp Logger (ghi log), lớp quản lý Cấu hình (Configurations) hoặc quản lý kết nối chung
Adapter (Class/Object Scope)
Lý thuyết: Đóng vai trò là một cầu nối giữa hai giao diện không tương thích. Nó chuyển đổi giao diện của một lớp đang có sẵn thành một giao diện khác mà phía client mong đợi, giúp các lớp vốn không thể làm việc chung có thể hoạt động cùng nhau
.
Ví dụ: Cắm máy chiếu HDMI vào máy tính chỉ có cổng USB. Lớp Client là Laptop chỉ hỗ trợ giao diện USBPort (Target) cần kết nối với HDMIProjector (Adaptee). Khi đó, lớp trung gian HDMIAdapter sẽ triển khai giao diện USBPort, chứa (composition) đối tượng HDMIProjector bên trong và bọc phương thức connectUSB() để chuyển đổi thành lời gọi connectHDMI()
.
Composite (Object Scope)
Lý thuyết: Gom nhóm (Takes a group) các đối tượng lại thành một đối tượng duy nhất. Mẫu này là sự lựa chọn hoàn hảo để biểu diễn các cấu trúc phân cấp dạng "toàn thể - bộ phận" (part-whole hierarchies)
.
Ví dụ: Trong hệ thống đồ họa, có giao diện chung Shape kèm theo phương thức draw()
. Các hình dạng đơn lẻ như Triangle (Tam giác) và Circle (Hình tròn) có thể được kết hợp vào bên trong một lớp lớn hơn là Drawing (Bản vẽ). Lớp bản vẽ này quản lý một danh sách nhiều hình dạng và tương tác với chúng như một thực thể đồng nhất
.
Decorator (Object Scope)
Lý thuyết: Được sử dụng để sửa đổi hoặc thêm chức năng mới cho một đối tượng một cách linh hoạt ngay tại thời gian chạy (runtime), mà không ảnh hưởng tới các phiên bản khác của cùng một lớp hay bắt buộc phải sửa đổi lớp gốc
. Đây là sự áp dụng hoàn hảo của nguyên lý OCP (Mở rộng thoải mái, đóng với sự thay đổi code cũ) của SOLID
.
Ví dụ: Hệ thống tính giá nhà cửa (Home Pricing System). BasicHome đại diện cho ngôi nhà cơ bản với một mức giá
. Lớp trừu tượng LuxuryDecorator bọc lại lớp giao diện Home này. Các lớp trang trí cụ thể như SwimmingPool (Thêm hồ bơi) hay PlayGround (Thêm sân chơi) sẽ mở rộng từ LuxuryDecorator, cho phép linh hoạt đính kèm và cộng dồn giá tiền lên ngôi nhà ban đầu mà không cần thay đổi logic của BasicHome
.
Observer (Object Scope)
Lý thuyết: Định nghĩa một mối quan hệ phụ thuộc dạng một-nhiều (one-to-many) giữa các đối tượng, trong đó khi một đối tượng (Subject) thay đổi trạng thái, tất cả các đối tượng phụ thuộc (Observer) của nó sẽ được thông báo và tự động cập nhật
.
Ví dụ: Hệ thống đăng ký thông báo chủ đề. Lớp MyTopic (đóng vai trò Subject) duy trì trạng thái và danh sách các đối tượng đăng ký theo dõi. Các lớp quan sát viên như MyTopicSubscriber (đóng vai trò Observer) sẽ nhận được tín hiệu qua hàm notifyObservers() khi hệ thống MyTopic có thông điệp thay đổi
.
State (Object Scope)
Lý thuyết: Cho phép một đối tượng thay đổi hoàn toàn hành vi của nó khi trạng thái nội tại thay đổi. Điều này làm cho đối tượng dường như đã thay đổi hoàn toàn sang một lớp khác, thay vì phải kiểm tra logic thông qua hàng loạt các cấu trúc if-else
.
Ví dụ: Máy bán nước tự động (Vending Machine). Lớp VendingMachineContext là ngữ cảnh chứa các hành vi chèn đồng xu (insertCoin()) và chọn nước (selectDrink()). Các xử lý này được ủy quyền (delegate) thực hiện bởi các đối tượng state cụ thể tuân theo giao diện chung State như: WaitingForMoneyState (đợi tiền), HasMoneyState (đã có tiền) hay OutOfStockState (hết hàng)
. Hành vi của máy nước sẽ hoàn toàn tự động cập nhật linh hoạt theo các đối tượng trạng thái đó
.
Strategy (Object Scope)
Lý thuyết: Định nghĩa một họ (family) các thuật toán phục vụ cho một tác vụ cụ thể, đóng gói (encapsulate) từng thuật toán lại và giúp chúng có thể hoán đổi (interchangeable) cho nhau. Chiến lược (Strategy) cho phép thuật toán độc lập và được quyết định bởi client ở thời điểm runtime
.
Ví dụ: Chức năng thanh toán trong Giỏ hàng mua sắm (Shopping Cart). Giỏ hàng có một tác vụ thanh toán nhưng tùy thuộc vào lựa chọn của người dùng, họ có thể truyền vào một thuật toán thanh toán thực tế khác nhau áp dụng giao diện PaymentStrategy, ví dụ như CreditCardStrategy (thanh toán thẻ tín dụng) hay PaypalStrategy (thanh toán qua ví Paypal)
.
| |