SYSTEM DESIGN MỨC FRESHER + KIẾN THỨC DOMAIN NGÂN HÀNG
1. Danh sách chủ đề con cần ôn (xếp theo độ ưu tiên cho fresher)
Ưu tiên CAO (gần như chắc chắn bị hỏi)
- REST API design cơ bản: naming convention (danh từ số nhiều, không dùng động từ), HTTP methods (GET/POST/PUT/PATCH/DELETE), HTTP status codes (200, 201, 400, 401, 403, 404, 409, 500), versioning (
/api/v1/...), pagination.
- Xử lý tiền tệ: tại sao KHÔNG dùng
float/double cho tiền, dùng BigDecimal (Java) hoặc DECIMAL/NUMERIC trong DB. Đây là câu "lọc ứng viên" kinh điển của ngân hàng.
- Idempotency: khái niệm, tại sao API chuyển tiền phải idempotent, idempotency key. Rất hay hỏi ở ngân hàng.
- Stateless vs Stateful: định nghĩa, tại sao REST API nên stateless, liên hệ với JWT/session và khả năng scale.
- Bảo mật cơ bản: SQL Injection (nguyên nhân + cách chống bằng PreparedStatement/parameterized query), hash password bằng bcrypt (không phải encrypt, không phải MD5), không log dữ liệu nhạy cảm (số thẻ, OTP, password), OTP/2FA khái niệm.
- Tính nhất quán dữ liệu (consistency): tại sao ngân hàng ưu tiên consistency, liên hệ ACID transaction (chuyển tiền = trừ A + cộng B trong 1 transaction).
Ưu tiên TRUNG BÌNH (hỏi để phân loại ứng viên khá)
- Monolith vs Microservices: định nghĩa, ưu nhược mỗi bên, khi nào chọn cái nào (fresher chỉ cần mức khái niệm).
- Message Queue: dùng để làm gì (async, decouple, buffer traffic), Kafka vs RabbitMQ ở mức "biết tên và use case", ví dụ gửi notification/SMS sau khi chuyển tiền thành công.
- Caching: cache-aside pattern, TTL, cache invalidation, Redis là gì, dữ liệu nào nên/không nên cache (KHÔNG cache số dư tài khoản một cách ngây thơ).
- Audit log: tại sao ngân hàng bắt buộc phải có, log cái gì (ai, làm gì, khi nào, kết quả), tính bất biến (append-only).
- XSS khái niệm: input validation, output encoding (backend chỉ cần hiểu khái niệm + validate/sanitize input).
- Câu design mini: "thiết kế API chuyển tiền đơn giản" — luyện khung trả lời trước ở nhà.
Ưu tiên THẤP (biết thì cộng điểm, không biết không sao)
- Load balancer: khái niệm, round-robin, health check, liên hệ với stateless.
- API Gateway: khái niệm cửa ngõ chung (auth, rate limiting, routing).
- Rate limiting: tại sao cần (chống brute-force OTP, chống spam API).
- CAP theorem: chỉ cần phát biểu được và nói "ngân hàng chọn C (consistency)".
- Distributed transaction / Saga: chỉ cần biết tên, nói được "chuyển tiền liên ngân hàng không dùng được local transaction nên cần cơ chế bù trừ (compensation)" là quá đủ cho fresher.
- HTTPS/TLS khái niệm: dữ liệu ngân hàng phải mã hoá khi truyền (in transit) và khi lưu (at rest).
2. Câu hỏi phỏng vấn khả năng cao + hướng trả lời
Câu 1: Một REST API được thiết kế tốt trông như thế nào?
- Resource đặt tên bằng danh từ số nhiều:
GET /accounts/{id}/transactions, không dùng động từ kiểu /getTransaction.
- Dùng đúng HTTP method: GET đọc (không side effect), POST tạo, PUT/PATCH sửa, DELETE xoá.
- Trả đúng status code: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 500 Internal Server Error.
- Có versioning (
/api/v1/) để nâng cấp không phá client cũ; có pagination cho danh sách (?page=&size=).
- Body lỗi có cấu trúc thống nhất:
{ "errorCode": "...", "message": "..." }.
- Ý ăn điểm: phân biệt 401 (chưa xác thực) vs 403 (không đủ quyền); nói thêm GET phải an toàn (safe) và PUT/DELETE idempotent.
- Lỗi cần tránh: nói "cái gì cũng trả 200 rồi để lỗi trong body" — interviewer ngân hàng rất dị ứng; dùng động từ trong URL.
Câu 2: Stateless vs Stateful là gì? Tại sao REST API nên stateless?
- Stateless: server không lưu trạng thái phiên của client giữa các request; mỗi request tự chứa đủ thông tin (vd JWT token trong header).
- Stateful: server nhớ session của client (vd session lưu trong memory của server).
- Stateless giúp scale ngang dễ: thêm server sau load balancer, request nào rơi vào server nào cũng xử lý được.
- Stateful gây vấn đề khi server chết hoặc phải dùng sticky session.
- Ý ăn điểm: liên hệ JWT (stateless) vs server-side session (stateful), và nói được trade-off: JWT khó revoke ngay lập tức — ngân hàng đôi khi vẫn cần session/blacklist để force logout khi nghi ngờ gian lận.
- Lỗi cần tránh: nói "stateless nghĩa là không có database" — sai; state nghiệp vụ vẫn nằm trong DB, stateless là nói về session giữa client-server.
Câu 3: Tại sao KHÔNG dùng float/double để lưu tiền? (câu bắt buộc phải trả lời đúng ở ngân hàng)
- Float/double là số chấm động nhị phân, không biểu diễn chính xác nhiều số thập phân:
0.1 + 0.2 != 0.3 (ra 0.30000000000000004).
- Sai số tích luỹ qua hàng triệu giao dịch → lệch sổ sách, không đối soát (reconcile) được — với ngân hàng là sự cố nghiêm trọng.
- Giải pháp: Java dùng BigDecimal (khởi tạo bằng String:
new BigDecimal("10.05"), không phải từ double), DB dùng DECIMAL/NUMERIC(19,4); hoặc lưu số tiền theo đơn vị nhỏ nhất bằng số nguyên (long, vd VND vốn không có phần lẻ).
- So sánh BigDecimal bằng
compareTo, không dùng equals (vì 1.0 vs 1.00 khác nhau với equals).
- Ý ăn điểm: nói được ví dụ
0.1 + 0.2 cụ thể + chi tiết new BigDecimal("...") từ String + compareTo, và nhắc phải chỉ định RoundingMode khi chia (vd tính lãi suất).
- Lỗi cần tránh: chỉ nói "vì không chính xác" mà không giải thích được tại sao; nói dùng
Math.round để sửa.
Câu 4: Idempotency là gì? Tại sao API chuyển tiền phải idempotent?
- Idempotent = gọi 1 lần hay N lần cho cùng một request thì kết quả/trạng thái hệ thống như nhau.
- Kịch bản thực tế: client gọi API chuyển tiền, bị timeout → không biết thành công hay chưa → retry → nếu không idempotent thì trừ tiền 2 lần.
- Cách làm: client gửi idempotency key (UUID) trong header/body; server lưu key + kết quả; nếu key đã xử lý thì trả lại kết quả cũ, không thực hiện lại.
- Lưu ý: check-key và xử lý phải atomic (unique constraint trên key trong DB) để chống 2 request song song cùng key.
- Ý ăn điểm: nêu đúng kịch bản "timeout ≠ thất bại" — client không phân biệt được request đã đến server hay chưa; phân loại method: GET/PUT/DELETE vốn idempotent, POST thì không nên phải tự thiết kế.
- Lỗi cần tránh: nhầm idempotency với "chống duplicate submit trên UI bằng disable nút" (đó chỉ là giảm thiểu phía client, không phải giải pháp); nói "retry thì cứ tạo transaction mới rồi hoàn tiền sau".
Câu 5: Thiết kế xử lý chuyển tiền giữa 2 tài khoản trong cùng ngân hàng, đảm bảo không mất tiền?
- Đặt cả 2 thao tác (trừ A, cộng B) trong 1 database transaction → ACID: hoặc cả hai thành công, hoặc rollback cả hai.
- Kiểm tra nghiệp vụ trước khi ghi: số dư đủ, tài khoản active, hạn mức giao dịch.
- Chống race condition khi 2 giao dịch cùng lúc trên 1 tài khoản: pessimistic lock (
SELECT ... FOR UPDATE) hoặc optimistic lock (cột version).
- Ghi transaction record (bảng lịch sử giao dịch) trong cùng transaction, kèm trạng thái (PENDING/SUCCESS/FAILED).
- Chống deadlock: lock các tài khoản theo thứ tự cố định (vd theo account id tăng dần).
- Ý ăn điểm: chủ động nhắc lock + idempotency key mà không cần interviewer gợi ý; nhắc double-entry (bút toán ghi cả nợ và có).
- Lỗi cần tránh: viết 2 câu UPDATE rời rạc không transaction; check số dư bằng SELECT thường rồi mới UPDATE (bị race condition giữa check và update).
Câu 6: Monolith vs Microservices — ưu nhược điểm? Fresher nên nói gì?
- Monolith: 1 codebase, 1 deployment. Ưu: đơn giản, dễ debug, transaction ACID trong 1 DB, phù hợp team nhỏ/sản phẩm mới. Nhược: scale phải scale cả cục, deploy chậm khi codebase lớn, 1 lỗi có thể sập cả app.
- Microservices: tách theo nghiệp vụ (account service, transfer service, notification service...). Ưu: scale/deploy độc lập, mỗi team own 1 service, fault isolation. Nhược: phức tạp vận hành, distributed transaction khó, network latency, cần monitoring/tracing.
- Ngân hàng lớn như VPBank thường theo hướng microservices cho kênh số, nhưng core banking vẫn có thể là hệ thống tập trung.
- Ý ăn điểm: nói "không có kiến trúc nào tốt tuyệt đối, tuỳ quy mô team và bài toán — start with monolith là hợp lý"; nêu được cái giá lớn nhất của microservices trong ngân hàng là mất ACID transaction xuyên service → cần Saga/compensation.
- Lỗi cần tránh: chê monolith là "cũ, xấu" một chiều; nói microservices như trend phải theo mà không nêu được nhược điểm.
Câu 7: Message Queue dùng để làm gì? Cho ví dụ trong ngân hàng.
- Giao tiếp bất đồng bộ (async) giữa các service: producer gửi message vào queue, consumer xử lý sau.
- Lợi ích: decouple (2 bên không cần biết nhau, notification service chết thì chuyển tiền vẫn chạy), buffer khi traffic đột biến (sale 11/11, lương về cuối tháng), retry khi consumer lỗi.
- Ví dụ ngân hàng: sau khi chuyển tiền commit thành công → publish event
TransferCompleted → các consumer gửi SMS/push notification, ghi hệ thống phân tích, kiểm tra fraud — những việc không cần nằm trong luồng chính.
- Kafka: throughput cao, lưu log event, replay được; RabbitMQ: message broker truyền thống, routing linh hoạt — fresher chỉ cần mức này.
- Ý ăn điểm: nói rõ nguyên tắc "trừ tiền phải sync trong transaction, gửi notification thì async qua queue" — biết phân biệt việc gì được phép async; nhắc consumer phải idempotent vì message có thể bị deliver lại (at-least-once).
- Lỗi cần tránh: nói "cho việc trừ tiền vào queue cho nhanh" — sai nghiêm trọng về nghiệp vụ; nói queue đảm bảo exactly-once một cách chắc nịch.
Câu 8: Caching là gì? Cache-aside hoạt động thế nào? Trong ngân hàng nên cache gì?
- Cache = lưu dữ liệu đọc nhiều vào bộ nhớ nhanh (Redis/in-memory) để giảm tải DB, giảm latency.
- Cache-aside: app đọc cache trước → miss thì đọc DB rồi ghi vào cache (kèm TTL); khi dữ liệu đổi thì xoá (invalidate) cache, lần đọc sau tự nạp lại.
- Vấn đề kinh điển: cache invalidation (dữ liệu stale), thundering herd khi key hết hạn đồng loạt.
- Trong ngân hàng: NÊN cache dữ liệu ít đổi (danh mục ngân hàng thụ hưởng, tỷ giá theo phiên, phí, thông tin sản phẩm); CẨN TRỌNG với số dư tài khoản — nếu cache thì TTL rất ngắn hoặc invalidate ngay khi có giao dịch, và bước quyết định trừ tiền luôn đọc từ DB trong transaction.
- Ý ăn điểm: câu "hiển thị số dư có thể đọc cache, nhưng quyết định cho phép chuyển tiền phải đọc DB (source of truth)" — thể hiện hiểu consistency vs performance.
- Lỗi cần tránh: nói "cache mọi thứ kể cả số dư cho nhanh"; không nhắc gì đến invalidation/TTL.
Câu 9: Load balancer là gì? Liên quan gì đến stateless?
- Đứng trước nhiều instance của service, phân phối request (round-robin, least connections...), có health check để loại instance chết.
- Cho phép scale ngang và high availability — 1 server chết thì traffic dồn sang server khác, người dùng không thấy gián đoạn.
- Điều kiện để load balance "đẹp": service stateless — session để ở Redis/JWT chứ không nằm trong memory của 1 server.
- Ý ăn điểm: nối được 3 khái niệm stateless → load balancer → horizontal scaling thành 1 câu chuyện; ngân hàng cần HA vì downtime = khách không chuyển được tiền = thiệt hại uy tín.
- Lỗi cần tránh: chỉ định nghĩa suông không nêu được vì sao cần; nhầm load balancer với API gateway (gateway làm thêm auth/rate-limit/routing theo path).
Câu 10: SQL Injection là gì? Chống thế nào?
- Kẻ tấn công chèn SQL vào input, vd login với
' OR '1'='1 → câu query nối chuỗi trở thành luôn đúng → bypass đăng nhập hoặc kéo dữ liệu (UNION SELECT ...).
- Nguyên nhân gốc: nối chuỗi input của user vào câu SQL.
- Chống: PreparedStatement / parameterized query (JPA/Hibernate với named parameter cũng an toàn), validate input, tài khoản DB chạy app chỉ cấp quyền tối thiểu (least privilege).
- Với ngân hàng: SQL injection = lộ dữ liệu khách hàng, vi phạm quy định bảo mật → mức độ nghiêm trọng cao nhất.
- Ý ăn điểm: viết được ví dụ query bị inject cụ thể và bản fix bằng
? placeholder; nhắc "kể cả dùng ORM, nếu tự viết native query nối chuỗi vẫn dính".
- Lỗi cần tránh: nói "escape ký tự đặc biệt bằng tay là đủ"; nói "dùng framework rồi thì miễn nhiễm hoàn toàn".
Câu 11: Lưu password của user thế nào cho đúng?
- Không bao giờ lưu plaintext; cũng không "mã hoá" (encrypt) vì encrypt giải mã ngược được.
- Dùng hash một chiều + salt: bcrypt (hoặc scrypt/Argon2). Bcrypt tự sinh salt và có cost factor làm hash chậm có chủ đích → chống brute-force khi lộ DB.
- Không dùng MD5/SHA-1/SHA-256 trần: quá nhanh, dò được bằng rainbow table/GPU.
- Khi login: hash input rồi so sánh, không bao giờ giải mã; không log password kể cả khi login fail.
- Ý ăn điểm: phân biệt rành mạch hashing (một chiều) vs encryption (hai chiều); giải thích salt chống rainbow table; nhắc thêm ngân hàng còn có 2FA/OTP nên password không phải lớp duy nhất.
- Lỗi cần tránh: dùng lẫn lộn từ "mã hoá password" rồi mô tả như encrypt; nói "SHA-256 là đủ an toàn cho password".
Câu 12: OTP/2FA là gì? Tại sao ngân hàng bắt buộc? Cần lưu ý gì phía backend?
- 2FA = xác thực bằng 2 yếu tố khác loại: something you know (password) + something you have (điện thoại nhận OTP/smart OTP) hoặc something you are (sinh trắc học).
- OTP: mã dùng 1 lần, hết hạn nhanh (30s–5 phút), dùng xong vô hiệu, giới hạn số lần nhập sai (3-5 lần) và rate limit gửi lại — chống brute-force.
- Ngân hàng bắt buộc cho giao dịch (theo quy định NHNN, giao dịch giá trị cao còn yêu cầu xác thực mạnh hơn như smart OTP/sinh trắc) vì password đơn lẻ dễ bị lộ/phishing.
- Backend: OTP lưu dạng hash hoặc trong Redis với TTL, không được ghi OTP ra log, verify xong xoá ngay.
- Ý ăn điểm: nói được các biện pháp chống abuse (expiry + max attempts + rate limit + invalidate sau khi dùng) — thể hiện tư duy bảo mật thực tế.
- Lỗi cần tránh: mô tả OTP mà quên expiry và giới hạn số lần thử; nói lưu OTP plaintext trong DB lâu dài.
Câu 13: Tại sao ngân hàng cần audit log? Log gì và KHÔNG log gì?
- Audit log ghi lại ai (user/service), làm gì (action), lên đối tượng nào, khi nào, từ đâu (IP/device), kết quả ra sao — phục vụ điều tra sự cố, tranh chấp giao dịch ("tôi không chuyển khoản này"), yêu cầu pháp lý/kiểm toán của NHNN.
- Tính chất: append-only (không sửa/xoá được), lưu trữ lâu dài theo quy định, có timestamp chuẩn.
- KHÔNG log dữ liệu nhạy cảm: password, OTP, số thẻ đầy đủ (chỉ mask
****1234), token, thông tin định danh khi không cần → nếu cần thì mask/redact.
- Phân biệt audit log (bằng chứng nghiệp vụ) vs application log (debug kỹ thuật).
- Ý ăn điểm: nói được câu "log là nơi lộ dữ liệu phổ biến nhất — dev quen tay log cả request body chứa password/số thẻ"; nhắc audit log phục vụ non-repudiation (chống chối bỏ).
- Lỗi cần tránh: coi audit log chỉ là "in log ra file để debug"; không biết khái niệm masking.
Câu 14: Tại sao ngân hàng ưu tiên consistency cao? CAP theorem liên quan gì?
- Sai lệch dữ liệu tiền = mất tiền thật của khách/ngân hàng, vi phạm pháp lý, mất niềm tin — nên nghiệp vụ tiền yêu cầu strong consistency, không chấp nhận "eventually consistent" cho số dư tại thời điểm quyết định giao dịch.
- Thể hiện qua: ACID transaction, isolation level phù hợp, locking, đối soát (reconciliation) cuối ngày.
- CAP: khi có network partition phải chọn Consistency hoặc Availability — hệ thống lõi ngân hàng chọn C (thà từ chối giao dịch còn hơn xử lý trên dữ liệu sai).
- Nhưng không phải mọi thứ đều strong consistency: notification, lịch sử hiển thị, báo cáo có thể eventual — biết phân tầng là điểm cộng.
- Ý ăn điểm: câu "thà trả lỗi 'thử lại sau' còn hơn cho chuyển tiền với số dư sai" + biết phân biệt phần nào cần strong, phần nào eventual là được.
- Lỗi cần tránh: phát biểu CAP sai kiểu "chỉ được chọn 2 trong 3 mọi lúc" (P là bắt buộc khi có partition, bình thường vẫn có cả C và A); nói eventual consistency dùng được cho trừ tiền.
3. Câu hỏi DESIGN MINI + khung trả lời (luyện trước ở nhà)
Mini design 1: "Hãy thiết kế một API chuyển tiền nội bộ đơn giản"
Khung trả lời theo thứ tự (nói thành tiếng từng bước, đừng nhảy thẳng vào code):
- Bước 1 — Làm rõ yêu cầu (clarify): cùng ngân hàng hay liên ngân hàng? có cần OTP không? giới hạn hạn mức? → thể hiện thói quen hỏi lại, interviewer rất thích. Giả định: nội bộ, đã đăng nhập.
- Bước 2 — API contract:
POST /api/v1/transfers
- Header:
Authorization: Bearer <token>, Idempotency-Key: <uuid>
- Body:
{ "fromAccountId", "toAccountId", "amount": "500000.00" (string/decimal, KHÔNG float), "currency": "VND", "description" }
- Response:
201 + { "transferId", "status": "SUCCESS|PENDING|FAILED", "timestamp" }; lỗi: 400 (input sai), 402/422 (không đủ số dư — nêu là tuỳ convention), 409 (duplicate idempotency key đang xử lý), 401/403.
- Bước 3 — Luồng xử lý backend:
- Validate input + auth (user có sở hữu fromAccount không — chống IDOR).
- Check idempotency key: đã có → trả kết quả cũ.
- Mở DB transaction: lock 2 account theo thứ tự id (
SELECT ... FOR UPDATE), check số dư + hạn mức, trừ A cộng B, insert bản ghi transfer + audit log, commit.
- Sau commit: publish event lên message queue → notification service gửi SMS/push (async).
- Bước 4 — Tự nêu edge case: retry/timeout (idempotency), 2 request song song (lock), server chết giữa chừng (transaction rollback, trạng thái PENDING + job đối soát), số âm/số 0/amount quá lớn (validate).
- Ý ăn điểm: tự nhắc BigDecimal/DECIMAL, idempotency key, transaction + lock, audit log, async notification mà không cần gợi ý — đủ 5 ý này gần như full điểm câu này với fresher.
- Lỗi cần tránh: dùng
GET /transfer?from=..&to=..&amount=.. (GET có side effect + lộ thông tin trên URL/log); amount kiểu float; quên trường hợp retry; nhét gửi SMS vào trong DB transaction.
Mini design 2: "Thiết kế API xem lịch sử giao dịch của một tài khoản"
GET /api/v1/accounts/{accountId}/transactions?from=2026-01-01&to=2026-06-30&page=0&size=20
- Authorization: kiểm tra user đang login có quyền xem accountId này không — nếu chỉ check "đã login" là dính lỗ hổng IDOR (đổi id trên URL xem trộm tài khoản người khác). Đây là ý ăn điểm lớn nhất của câu này trong ngữ cảnh ngân hàng.
- Pagination bắt buộc (tài khoản lâu năm có hàng chục nghìn giao dịch), sort mặc định theo thời gian giảm dần, filter theo khoảng ngày.
- Dữ liệu trả về: mask thông tin nhạy cảm nếu có (số thẻ), status codes: 200, 400 (date range sai), 403 (không phải chủ tài khoản), 404.
- Có thể nhắc: đọc từ read replica/cache vì là dữ liệu đọc nhiều, chấp nhận trễ vài giây — trong khi số dư quyết định giao dịch thì không.
- Lỗi cần tránh: quên pagination; quên check ownership (IDOR); dùng POST cho thao tác đọc thuần.
4. Ghi chú riêng cho ngữ cảnh phỏng vấn ngân hàng (VPBank)
- Ba từ khoá nên chủ động lồng vào câu trả lời bất cứ khi nào hợp lý: BigDecimal, idempotency, transaction/ACID — đây là bộ lọc quen thuộc của interviewer ngân hàng với fresher backend.
- Tư duy được đánh giá cao: "an toàn dữ liệu trước, performance sau" — thà chậm/từ chối còn hơn sai số tiền.
- Khi không biết, trả lời thẳng "em chưa làm thực tế phần này nhưng theo em hiểu khái niệm là..." — ngân hàng đánh giá cao sự trung thực hơn là bịa (liên quan văn hoá risk/compliance).
- Nên đọc lướt trước về app VPBank NEO và các nghiệp vụ cơ bản (chuyển tiền 24/7 qua Napas, số dư khả dụng vs số dư thực tế, hạn mức giao dịch, sao kê) để có ví dụ minh hoạ khi trả lời — không cần sâu, chỉ cần cho thấy có tìm hiểu về công ty.