System Design Interview bắt đầu từ việc làm rõ yêu cầu
Một hướng dẫn thực tế cho những phút đầu của System Design Interview: làm rõ người dùng, quy mô, luồng chính, ràng buộc và trade-off trước khi chọn công cụ.
Bài viết
Cách tổ chức codebase để hệ thống dễ hiểu, dễ sửa và vẫn đứng vững khi team lớn lên.
Những bài viết thực tế về software architecture và source code structure: từ Ports & Adapters, Clean Architecture đến dependency, module boundary, scaling và trade-off. Mỗi bài cố gắng giải thích bằng sơ đồ, ví dụ gần với đời làm việc, để người mới không bị ngợp và người đã đi làm vẫn có thứ mang về áp dụng.
Một hướng dẫn thực tế cho những phút đầu của System Design Interview: làm rõ người dùng, quy mô, luồng chính, ràng buộc và trade-off trước khi chọn công cụ.
Một reflection thực tế về cách chọn default bảo vệ user và system khi configuration, rollout hoặc external dependency còn bất định.
Một reflection thực tế về cách thiết kế data contract hẹp, testable và đủ ổn định để team có thể dựa vào.
Một reflection thực tế về cách chọn software boundary đơn giản để ownership, testing và future change dễ suy luận hơn.
Một reflection thực tế về cách xây safe operational escape hatch rõ ràng, có audit và có boundary thay vì back door bị giấu.
Khi hệ thống chậm, thiếu dữ liệu hoặc chưa chắc chắn, giao diện nên nói rõ phần người dùng cần biết trước khi họ tự phát hiện qua lỗi.
Thiết kế phần mềm nên ưu tiên những kiểu thay đổi đã lặp lại, thay vì trả trước quá nhiều cho một tương lai còn mơ hồ.
Tên một lớp chỉ cho ta cái nhãn. Ranh giới kiến trúc chỉ thật sự rõ khi đội ngũ có ví dụ cụ thể về trách nhiệm, hành vi và những gì được phép đi qua.
Quyết định kiến trúc nên nói rõ điều đang chốt, điều cố ý để sau, invariant cần bảo vệ và tín hiệu sẽ khiến nhóm xem lại lựa chọn. Phạm vi đủ nhỏ giúp hệ thống tiếp tục học mà không biến lý lẽ của hôm qua thành luật bất di bất dịch.
Một interface bền là lời hứa rõ với caller: giữ hành vi sản phẩm ổn định trong khi storage, provider và implementation bên dưới tiếp tục tiến hóa.
Cách lần theo luật nghiệp vụ, giảm coupling và gom phần quan trọng về một lõi dễ tìm, dễ đọc, khó vô tình đi vòng qua.
Code chỉ cho thấy hình dạng cuối cùng, không cho biết vì sao nó ra đời. Một ghi chú kiến trúc ngắn giữ lại trade-off, hướng đã bỏ và lúc nên xem lại để người đến sau thay đổi hệ thống an toàn hơn.
Kiến trúc nên bắt đầu từ quyền sở hữu, dữ liệu và nhịp thay đổi của nghiệp vụ. Khi ranh giới đã rõ, framework, queue, database hay service mới có một bài toán thật để giải.
Observability không phải lớp trang trí gắn thêm sau khi hệ thống đã chạy. Log, metric, trace, dashboard và alert cần được thiết kế từ sớm để khi production có vấn đề, hệ thống đủ khả năng kể lại chuyện gì vừa xảy ra.
Outbox Pattern xử lý một lỗi rất đời của hệ thống phân tán: dữ liệu đã ghi vào database nhưng event lại không được gửi đi. Mẫu này khép khoảng hở của dual write, nhưng vẫn đòi hỏi retry, idempotency, ordering, dọn dữ liệu và observability nghiêm túc.
Service mesh chỉ đáng thêm khi lỗi giữa các dịch vụ đã lặp lại và đo được. Đây là cách đọc số liệu, nhìn trước điểm hỏng, thử trên phạm vi nhỏ và chuẩn bị đường rút.
Event Sourcing giữ chuỗi event chỉ nối thêm làm bản ghi gốc, rồi dựng trạng thái hiện tại qua projection. Đổi lại khả năng audit và replay là chi phí versioning, vận hành và phục hồi.
Một bài giải thích thực tế về Saga pattern trong distributed transaction: vì sao một database transaction không còn đủ khi hệ thống tách service, orchestration và choreography khác nhau thế nào, và vì sao compensation, observability, idempotency lại quan trọng.
Một góc nhìn thực tế về trách nhiệm của API gateway: routing, authentication boundary, rate limiting, request aggregation, trade-off của backend-for-frontend, failure mode và observability cần có để vận hành edge của hệ thống rõ ràng hơn.
Một hướng dẫn thực tế về cách áp dụng Hexagonal Architecture cho một feature nhỏ: vẽ boundary, đặt tên port bằng ngôn ngữ business, giữ adapter ở rìa hệ thống, test phần core, và biết khi nào pattern này đáng với chi phí của nó.
Khi service nào cũng gọi trực tiếp service khác, một dependency chậm có thể kéo cả checkout đi xuống. Bài này giải thích event-driven architecture bằng cách gần gũi: khi nào nên dùng sync hay async, command khác event ra sao, message broker thật sự bảo đảm điều gì, choreography và orchestration khác nhau thế nào, và lúc nào events chỉ làm hệ thống rối hơn.
Tách code thường dễ hơn tách dữ liệu. Khi mỗi service cần sở hữu dữ liệu riêng, team phải đối mặt với shared database, eventual consistency, dual-write bug, outbox, saga, CQRS và event sourcing. Bài này đi qua những lựa chọn đó bằng ví dụ thực tế, để bạn hiểu trade-off trước khi hệ thống phân tán bắt đầu làm mọi thứ khó hơn.
Một lời gọi ra khỏi process có thể chậm, lỗi, timeout, hoặc chạy hai lần. Đó không phải chuyện hiếm, mà là đời sống bình thường của distributed systems. Bài này gom lại những kỹ thuật giúp hệ thống bền hơn: timeout, retry với backoff và jitter, idempotency, circuit breaker, bulkhead, graceful degradation và observability.
Khi traffic tăng, database thường là nơi kêu cứu đầu tiên. Nhưng không phải cứ chậm là lao vào sharding. Bài này đưa ra một nấc thang thực tế để scale data layer: đo trước, thêm index, dùng read replica, cache đúng chỗ, hiểu replication lag và chỉ đụng tới partitioning hoặc sharding khi thật sự đã đến lúc.
Khi một request đi qua nhiều service, không còn một log file duy nhất để kể toàn bộ câu chuyện. Bài này giải thích observability theo cách dễ nắm: metrics cho biết hệ thống đang khỏe hay không, logs kể chuyện chi tiết, traces nối các bước lại với nhau, còn SLO giúp team nói về reliability bằng một ngôn ngữ rõ ràng hơn.
Microservices có thể tạo quyền tự chủ thật cho team, nhưng chuyển chi phí sang vận hành, dữ liệu, observability, testing và phối hợp. Bài viết hỏi khi nào cái giá đó đáng trả, và khi nào modular monolith tử tế hơn.
Ports & Adapters nghe có vẻ học thuật, nhưng ý tưởng cốt lõi rất thực tế: business logic nên đứng vững dù database, UI hay API bên ngoài thay đổi. Bài này giải thích port, adapter và dependency direction bằng ví dụ gần gũi, để bạn biết áp dụng vừa đủ cho dự án nhỏ lẫn codebase lớn.
Clean Architecture, Onion Architecture và Hexagonal Architecture thường bị xem như ba trường phái khác nhau. Thực ra chúng cùng nhắc về một điều: domain và business rule nên nằm ở trung tâm, framework và chi tiết kỹ thuật ở ngoài rìa. Bài này so sánh nhẹ nhàng để bạn chọn cách gọi và cách vẽ phù hợp với team.
DI và IoC không phải phép màu của framework. Ý tưởng đơn giản hơn nhiều: đừng để một class tự tạo mọi dependency mà nó cần; hãy đưa dependency từ bên ngoài vào. Bài này giải thích vì sao cách làm đó giúp code dễ test hơn, dễ thay đổi hơn và đỡ dính chặt vào database, SDK hay service cụ thể.
Một codebase bắt đầu lớn lên từ những lựa chọn rất nhỏ: thư mục trên cùng nên là controllers/services/models, hay orders/billing/auth? Bài này so sánh cách tổ chức theo layer, theo feature và theo domain, chỉ ra mỗi kiểu hợp với giai đoạn nào, và vì sao cấu trúc thư mục thật ra đang kể cách team suy nghĩ về sản phẩm.
Microservices không phải điểm xuất phát bắt buộc, mà thường là cái giá phải trả khi team và hệ thống đã đủ lớn. Bài này đi qua hành trình từ monolith, modular monolith đến microservices, với các dấu hiệu thực tế giúp bạn biết khi nào nên tách, khi nào nên giữ lại, và khi nào chỉ đang đổi sự đơn giản lấy thêm vận hành.
Micro-frontends có thể giúp nhiều team deploy độc lập hơn, nhưng cũng mang theo chi phí về routing, shared dependency, design system, performance và ownership. Bài này nhìn micro-frontends từ kinh nghiệm thực tế, để bạn biết khi nào nó giải phóng team, và khi nào nó chỉ biến frontend thành một distributed system khó chăm hơn.
Layered architecture dễ nhận ra và dễ dạy; vertical slice giữ thay đổi gần với feature người dùng chạm vào. Bài viết so sánh hai cách qua những thay đổi code hằng ngày và trade-off về boundary.
Dependency direction là một quy tắc đơn giản giúp team thống nhất: business rule ổn định không nên phụ thuộc vào chi tiết delivery dễ đổi. Bài viết dùng một tình huống release nhỏ để giải thích vì sao hướng phụ thuộc giúp code, review và trao đổi trong team bình tĩnh hơn.
Một bài giải thích thực tế về eventual consistency ngoài định nghĩa sách vở: người dùng thật sự thấy gì, team đang đổi lấy điều gì, product nên đặt kỳ vọng ra sao, và vận hành phải làm gì để sự thật đến trễ không biến thành dữ liệu lệch âm thầm.
Một bài giải thích thực tế về CQRS: tách command khỏi query, khi nào read model và write model nên có hình dạng khác nhau, và cách tránh biến một pattern hữu ích thành kiến trúc thừa.
Một góc nhìn thực tế về serverless architecture: team được gì khi giao bớt infrastructure cho managed platform, vẫn phải sở hữu phần nào, và khi nào function, queue, managed service là một lựa chọn hợp lý.
Một bài giải thích thực tế về caching: cache giúp ở đâu, cache-aside, read-through, write-through, TTL và invalidation khác nhau thế nào, và vì sao stale data, cache stampede, key design cùng observability quan trọng không kém tốc độ.
Một bài giải thích thực tế về database sharding: điều gì thay đổi khi một database được chia thành nhiều partition nhỏ hơn, khi nào sharding đáng với chi phí vận hành, và vì sao shard key, hotspot, rebalancing cùng observability rất quan trọng.
Một bài so sánh thực tế giữa GraphQL và REST: mỗi cách định hình API contract, độ linh hoạt cho frontend, caching, observability, ownership trong team và trade-off vận hành khi chọn một phong cách API.
Một bài giải thích thực tế về idempotent API design: vì sao retry luôn xảy ra, duplicate request tạo rủi ro thế nào, và cách idempotency key, state transition ổn định cùng response contract rõ ràng làm distributed system bình tĩnh hơn.
Một bài giải thích thực tế về rate limiting và throttling: cách hệ thống bảo vệ capacity chung, điều tiết traffic, giao tiếp limit rõ ràng và ngăn overload biến thành outage.