2118 từ
11 phút
Tìm hiểu về Kiến Trúc Phần Mềm (Software Architecture)

Bạn đã bao giờ “chữa cháy” một hệ thống không có kiến trúc?#

Hình dung thế này: bạn vừa join một dự án e-commerce đã chạy production 2 năm. Code chạy được, nhưng mỗi lần thêm feature mới — ví dụ tích hợp cổng thanh toán thứ hai — cả team mất 3 tuần thay vì 3 ngày. Lý do? Không ai biết logic thanh toán nằm ở đâu, business rules trộn lẫn với UI code, và mỗi thay đổi nhỏ kéo theo hàng loạt bug không liên quan.

Đây không phải lỗi của dev — đây là hậu quả của việc không có kiến trúc rõ ràng từ đầu.

AI đang thay thế những công việc code lặp đi lặp lại. Thứ AI chưa thay được là khả năng ra quyết định kiến trúc — chọn pattern nào, chia hệ thống ra sao, đánh đổi gì. Đây là kỹ năng giúp bạn không bị thay thế, và cũng là thứ quyết định hệ thống bạn xây có sống được lâu dài hay không.

Bài viết này là bài đầu tiên trong series Software Architecture — tổng hợp các khái niệm nền tảng trước khi đào sâu ở những bài sau.

Software architecture là gì?#

Software architecture là tổng thể các patterns, principles, processes, và mối quan hệ giữa các thành phần trong hệ thống — tạo nên một thiết kế tổng thể.

Giống như codebase, kiến trúc cần được maintain liên tục theo sự thay đổi của business. Mục tiêu tối thượng: giải quyết bài toán của người dùng.

Tại sao nhiều dự án bỏ qua và trả giá đắt?#

Dev đầu career thường muốn nhảy vào code ngay. Kết quả: dự án chạy được một thời gian thì bắt đầu ngập bug, khó mở rộng, khó bảo trì.

Thay vì dành 1-2 tháng làm rõ yêu cầu và chọn kiến trúc phù hợp, bạn sẽ tốn gấp nhiều lần thời gian để fix lỗi, tái cấu trúc, hỗ trợ khách hàng ngoài giờ. Team mệt mỏi, khách hàng mất niềm tin, doanh nghiệp mất sức mạnh.

WARNING

Chi phí thay đổi kiến trúc tăng theo cấp số nhân theo thời gian. Thay đổi lúc thiết kế tốn vài giờ, lúc đang phát triển tốn vài ngày, nhưng sau khi đã lên production có thể tốn vài tháng — hoặc thậm chí phải viết lại từ đầu.

Câu hỏi đúng không phải “có cần kiến trúc không?” mà là “kiến trúc nào phù hợp?“

7 key areas trong software architecture#

  1. Business analysis: Bài toán cần giải quyết là gì? Đâu là core features và boundaries? — Ví dụ: với hệ thống e-commerce, bạn cần xác định rõ Order Management là bounded context riêng hay gộp chung với Inventory.

  2. Infrastructure: Cloud-agnostic hay không? Chiến lược scale? Budget tối đa? — Một startup giai đoạn đầu có thể chọn single server trên AWS, nhưng cần thiết kế sao cho sau này migrate sang multi-region không phải viết lại từ đầu.

  3. Deployment strategy: Single hay multiple deployment units? — Nếu team chỉ có 3-4 người, deploy monolith duy nhất sẽ đơn giản hơn nhiều so với quản lý 10 microservices với CI/CD riêng biệt.

  4. Solution architecture: Patterns nào? Các thành phần giao tiếp với nhau qua phương thức gì? — REST API cho synchronous communication, hay message queue (RabbitMQ, Kafka) cho asynchronous? Mỗi lựa chọn ảnh hưởng đến cách bạn handle failures.

  5. Test strategy: Chiến lược test nào cho dự án? — Hệ thống tài chính cần integration tests dày đặc cho payment flows, trong khi CMS có thể tập trung vào unit tests cho business logic.

  6. Release strategy: Tần suất phát hành phiên bản? — Team dùng trunk-based development có thể release hàng ngày với feature flags, còn team dùng GitFlow thường release theo sprint 2 tuần.

  7. Teams: Phân chia team thế nào? Năng lực hiện tại của các thành viên ra sao? — Conway’s Law nói rằng kiến trúc hệ thống phản ánh cấu trúc tổ chức. Nếu bạn có 2 team, đừng chia hệ thống thành 5 services — không ai maintain nổi.

TIP

Bạn không cần trả lời hết 7 areas cùng lúc. Hãy bắt đầu từ Business Analysis và Teams — hai yếu tố này sẽ định hình phần còn lại.

4 architecture drivers quyết định kiến trúc#

Không có kiến trúc nào phù hợp cho mọi dự án. Mỗi quyết định kiến trúc phải dựa trên các drivers cụ thể, không phải cảm tính.

1. Functional Requirements#

Hệ thống phải làm gì để đáp ứng mục tiêu kinh doanh?

  • Tập trung vào what (hệ thống cần làm gì), không phải how (làm bằng cách nào).
  • Hỏi thẳng stakeholders: “Người dùng cuối muốn gì?”
  • Dùng Domain StorytellingUser Stories để khai thác yêu cầu — cực kỳ hiệu quả.

Ví dụ thực tế: bạn xây hệ thống đặt lịch khám bệnh. Functional requirement là “bệnh nhân có thể đặt lịch khám với bác sĩ theo chuyên khoa”. Đừng vội nghĩ đến database schema hay API endpoint — hãy hiểu rõ flow nghiệp vụ trước: bệnh nhân chọn chuyên khoa → xem lịch trống → chọn slot → xác nhận. Từ flow này, kiến trúc sẽ tự hiện ra.

image

2. Business Constraints#

Thời gian, ngân sách, nguồn lực, quy định pháp luật — những thứ bạn buộc phải tuân thủ.

  • Nguồn lực hạn chế? Giảm scope, đừng cố làm tất cả.
  • Ràng buộc kinh doanh thường quyết định thành bại nhiều hơn kỹ thuật.

Ví dụ: khách hàng yêu cầu MVP trong 2 tháng với budget 200 triệu. Bạn muốn xây microservices cho “chuẩn” nhưng team chỉ có 2 backend devs. Lúc này business constraint buộc bạn phải chọn monolith — và đó là quyết định đúng. Kiến trúc “đẹp” mà không ship được thì vô nghĩa.

3. Technical Constraints#

Hạn chế về công nghệ hoặc hạ tầng mà bạn phải sống chung.

Ví dụ:

  • Phải triển khai trên nền tảng nội bộ đã có sẵn.
  • Codebase phải viết bằng C# vì toàn bộ hệ thống công ty đang dùng.
  • Database phải dùng Oracle vì công ty đã mua license dài hạn — dù PostgreSQL phù hợp hơn cho use case của bạn.

Technical constraints không phải lúc nào cũng hợp lý, nhưng bạn phải tôn trọng chúng. Việc của architect là tìm cách thiết kế tốt nhất trong giới hạn đó, không phải đấu tranh để thay đổi constraint (trừ khi constraint đó thực sự gây hại nghiêm trọng đến dự án).

4. Quality Attributes#

Các đặc điểm phi chức năng: hiệu suất, khả năng mở rộng, tính bảo trì… Ảnh hưởng trực tiếp đến trải nghiệm người dùng.

  • Đừng cố tối ưu mọi thứ — tập trung vào quality attributes mà dự án bạn thật sự cần.
  • Ví dụ: uptime 99%, báo cáo phải trả về trong 7 giây vào giờ cao điểm.

Khi các drivers xung đột với nhau#

Trong thực tế, các drivers hiếm khi “thuận” nhau — chúng thường kéo bạn về nhiều hướng khác nhau.

Performance vs Maintainability: Bạn có thể tối ưu performance bằng cách viết raw SQL queries với caching phức tạp ở nhiều tầng. Nhưng 6 tháng sau, dev mới vào team sẽ không hiểu nổi flow dữ liệu và sửa một bug mất cả tuần. Ngược lại, code sạch với ORM dễ maintain nhưng có thể chậm hơn ở các queries phức tạp.

Security vs User Experience: Xác thực 2 bước (2FA), session timeout ngắn, CAPTCHA ở mọi form — bảo mật tốt nhưng người dùng sẽ bực mình. Hệ thống ngân hàng chấp nhận trade-off này, nhưng app mạng xã hội thì không nên.

Scalability vs Cost: Thiết kế hệ thống auto-scale trên Kubernetes nghe rất xịn, nhưng nếu bạn chỉ có 500 users/ngày thì chi phí infrastructure sẽ nuốt hết lợi nhuận.

TIP

Khi drivers xung đột, hãy quay lại hỏi: “Quality attribute nào quan trọng nhất với business?” Đặt thứ tự ưu tiên rõ ràng, đừng cố cân bằng mọi thứ — vì cân bằng mọi thứ nghĩa là không ưu tiên gì cả.

5 nguyên tắc áp dụng thực tế#

1. Bắt đầu đơn giản#

Đừng thiết kế phức tạp hơn những gì bạn cần.

  • Nên: Bắt đầu với monolith, tách module rõ ràng bên trong. Khi cần scale, tách service ra từ module đã có sẵn boundaries.
  • Đừng: Xây 15 microservices từ ngày đầu cho một ứng dụng quản lý nội bộ 50 users. Bạn sẽ dành 80% thời gian lo distributed systems thay vì giải quyết bài toán business.

2. Giải quyết vấn đề hiện tại#

Không lo xa khi chưa rõ tương lai (YAGNI — You Ain’t Gonna Need It).

  • Nên: Thiết kế database schema cho features hiện tại, dùng migration tool để thay đổi sau khi có yêu cầu mới.
  • Đừng: Tạo sẵn 20 bảng “phòng khi mai mốt cần” — rồi 6 tháng sau nhận ra business đi hướng khác hoàn toàn, 15 bảng không dùng đến.

3. Giữ linh hoạt#

Thiết kế đơn giản nhưng để lại chỗ để mở rộng.

  • Nên: Dùng interface/abstraction cho các external dependencies (payment gateway, email service). Khi đổi provider, chỉ cần thay implementation.
  • Đừng: Gọi thẳng Stripe API khắp nơi trong code. Ngày khách hàng muốn đổi sang VNPay, bạn phải sửa 47 files.

4. Đừng chạy theo trend#

Không dùng công nghệ chỉ vì nó “hot”.

  • Nên: Chọn tech stack dựa trên bài toán cụ thể và năng lực team. PostgreSQL “nhàm chán” nhưng giải quyết 90% use cases.
  • Đừng: Chọn event sourcing + CQRS cho một CRUD app vì đọc được bài blog hay trên Medium. Complexity không tự nhiên mất — nó chỉ di chuyển từ chỗ này sang chỗ khác.

5. Hiểu trade-offs#

Mọi quyết định kiến trúc đều có cái được và cái mất — không có “best practice” tuyệt đối.

  • Nên: Khi chọn giữa SQL và NoSQL, liệt kê rõ: “SQL cho data consistency nhưng scale vertical khó, NoSQL cho flexibility và horizontal scaling nhưng mất ACID transactions”. Chọn dựa trên requirement cụ thể.
  • Đừng: Tuyên bố “microservices tốt hơn monolith” mà không xét context. Netflix cần microservices vì có hàng nghìn engineers và hàng trăm triệu users — startup của bạn thì không.
WARNING

Coi chừng “resume-driven development” — chọn tech stack vì muốn CV đẹp thay vì vì dự án cần. Đây là một trong những nguyên nhân phổ biến nhất khiến dự án over-engineered.

Các kiến trúc phổ biến#

Trước khi kết thúc, hãy điểm qua 4 kiến trúc mà bạn sẽ gặp nhiều nhất. Mỗi cái có đất diễn riêng — không cái nào “tốt nhất”.

Monolith#

Toàn bộ ứng dụng nằm trong một codebase, một deployment unit. Đơn giản để phát triển, test, và deploy. Phù hợp cho hầu hết dự án ở giai đoạn đầu. Vấn đề xuất hiện khi codebase phình to và nhiều team cùng làm trên một repo — merge conflicts, deploy chậm, một module lỗi kéo sập cả hệ thống.

Clean Architecture#

Tổ chức code theo layers với dependency rule: các layer bên trong (domain, use cases) không phụ thuộc vào layer bên ngoài (framework, database, UI). Giúp business logic độc lập với infrastructure, dễ test và thay đổi công nghệ. Nhưng đòi hỏi kỷ luật cao từ team và có learning curve — với CRUD app đơn giản, nó có thể là overkill.

Microservices#

Chia hệ thống thành nhiều services nhỏ, mỗi service có database riêng, deploy độc lập, giao tiếp qua API hoặc message broker. Cho phép scale từng phần, team autonomy cao, và chọn tech stack phù hợp cho từng service. Đổi lại, bạn phải đối mặt với distributed system complexity: network latency, data consistency, distributed tracing, và chi phí vận hành cao hơn nhiều lần.

Modular Monolith#

Một deployment unit như monolith, nhưng bên trong chia thành các modules có boundaries rõ ràng — mỗi module có domain, data access, và API riêng. Đây là lựa chọn “best of both worlds”: đơn giản trong deployment nhưng có cấu trúc tốt để tách thành microservices sau khi thực sự cần. Nhiều experts hiện nay khuyến khích bắt đầu với modular monolith thay vì nhảy thẳng vào microservices.

Bảng so sánh nhanh#

Tiêu chíMonolithClean ArchitectureMicroservicesModular Monolith
Độ phức tạp ban đầuThấpTrung bìnhCaoTrung bình
Phù hợp team size1-10 devsMọi quy mô20+ devs5-20 devs
Khả năng scaleVerticalTùy deploymentHorizontal từng serviceVertical, dễ tách sau
Chi phí vận hànhThấpTùy deploymentCaoThấp
Khi nào nên chọnMVP, startup, team nhỏKhi business logic phức tạp, cần testability caoKhi cần scale độc lập, nhiều teamKhi muốn cấu trúc tốt nhưng chưa cần distributed system
TIP

Nếu bạn đang phân vân, hãy bắt đầu với Modular Monolith. Nó cho bạn cấu trúc tốt từ đầu và giữ cánh cửa mở để chuyển sang microservices khi business thực sự yêu cầu.

Kết luận#

Software architecture = business + kỹ thuật + trải nghiệm thực tế. Architecture drivers giúp bạn ra quyết định có cơ sở thay vì đoán mò.

Luôn tự hỏi: “Điều gì quan trọng nhất với dự án này?” — và thiết kế mọi thứ xoay quanh câu trả lời đó.

Bài tiếp theo trong series, chúng ta sẽ cùng build một hệ thống từ đầu. Stay tuned.

Tìm hiểu về Kiến Trúc Phần Mềm (Software Architecture)
https://www.devwithxuan.com/vi/posts/software-architecture/understand-software-architecture/
Tác giả
XuanPD
Ngày đăng
2025-01-13
Giấy phép
CC BY-NC-SA 4.0
Chia sẻ:

Đăng ký nhận bản tin

Nhận thông báo khi có bài viết mới. Không spam, hủy bất cứ lúc nào.

Bình luận