Tổng kết — Modern Java trong Spring
Cheat sheet record DTO, quyết định bật virtual threads, RestClient so với @HttpExchange, sealed Result, kèm self-assessment năm năng lực của module.
TL;DR: Module 01 bóc bốn chỗ Java hiện đại chạm vào runtime Spring Boot: record khoá cửa mutation ở tầng DTO, virtual threads đổi cách Boot cấp thread cho tải I/O-bound (kèm ba cạm bẫy khi bật), RestClient/@HttpExchange gọi HTTP ra ngoài có timeout và xử lý lỗi tường minh, và sealed Result thay exception cho lỗi nghiệp vụ dự đoán được. Đây là 1 trang để bookmark: cheat sheet quyết định nhanh, liên hệ hai module sau, và self-assessment năm năng lực.
Đã đi qua những gì
Bài 01 khoá cửa mutation bằng record: Jackson bind JSON qua canonical constructor thay vì setter, nên không còn chỗ nào ghi đè giữa đường, nhưng JPA entity vẫn cần class mutable vì Hibernate cần constructor rỗng, field ghi được và class không final để proxy lazy-loading. Bài 02 bật spring.threads.virtual.enabled=true, đúng ba chỗ đổi cùng lúc: Tomcat, @Async, taskScheduler, và lằn ranh thắng-thua chỉ có một câu hỏi: phần lớn thời gian request là chờ hay là tính. Bài 03 cho thấy bật cấu hình đó chưa đủ: pinning khi block trong synchronized, ThreadLocal nhân bản theo số thread tăng vọt, và pool cũ từng âm thầm làm van chặn tải giờ không còn. Bài 04 dựng RestClient với timeout tường minh, chặn đúng lớp lỗi gây treo cả node ở bài trước. Bài 05 gói chuỗi endpoint lặp lại thành interface @HttpExchange, proxy vẫn giao việc gửi/nhận cho đúng RestClient đã cấu hình. Bài 06 khép mảng kỹ thuật bằng sealed Result: lỗi nghiệp vụ dự đoán được là kết quả kiểu dữ liệu, không phải exception, và switch exhaustive bắt lỗi ngay lúc build. Mini-challenge (bài 07) ráp cả bốn kỹ thuật vào TaskFlow rồi đo lại throughput.
flowchart LR A["Record DTO"] --> B["Virtual threads"] B --> C["Cam bay VT"] C --> D["RestClient"] D --> E["HttpExchange"] E --> F["Sealed Result"]
🗺️ Cheat sheet
| Chủ đề | Quyết định / cơ chế chính | Pitfall lớn nhất |
|---|---|---|
| Record ở ranh giới HTTP | DTO request/response và JPA projection: dùng được. JPA entity và bean cần setter: không, vì thiếu constructor rỗng, field không mutable, class final chặn proxy | Khai canonical constructor tường minh đủ tham số làm mất tự động copy constraint Bean Validation, không cảnh báo gì |
| Bật virtual threads | Bật khi phần lớn thời gian request là chờ I/O (gọi API, query DB); đừng bật khi việc nặng là tính CPU hoặc đã dùng WebFlux non-blocking thật sự | Đo latency một request rồi kết luận "không tác dụng" — phải đo throughput ở tải vượt xa pool cũ mới thấy khác biệt |
| Ba cạm bẫy virtual threads | Pinning (block trong synchronized, JDK 21 luôn ghim, JDK 24 JEP 491 gỡ phần lớn) · ThreadLocal nhân bản theo số thread (vài trăm lên hàng nghìn) · mất backpressure (pool cũ từng là van chặn tải) | Tưởng bật một cờ config là xong, không kiểm pinning bằng JFR (jdk.VirtualThreadPinned) và không dựng lại Semaphore/rate limiter thay pool cũ |
RestClient so với @HttpExchange | Fluent (RestClient) khi cần rẽ nhánh theo status code, header động phức tạp, hoặc streaming; declarative (@HttpExchange) khi hợp đồng ổn định và lặp lại nhiều endpoint | Quên khai timeout (chờ downstream vô thời hạn) hoặc onStatus chỉ bắt 4xx mà quên 5xx |
| Sealed Result so với exception | Result cho lỗi nghiệp vụ dự đoán được, nằm trong đặc tả (hết hàng, quá hạn mức); exception cho sự cố ngoài dự kiến hoặc lỗi phải lan qua nhiều tầng gọi nhau | @Transactional không tự rollback khi trả về nhánh lỗi của Result vì đó là return bình thường — phải validate trước khi ghi, hoặc chủ động setRollbackOnly() |
Liên hệ các module khác
- Module 02 — Observability & Production-ready: virtual thread không map 1-1 với OS thread nữa, nên module 02 dạy Observation API và tracing OTLP nối trace ID vào log — đúng công cụ để đọc lại đúng luồng xử lý khi thread dump không còn nói lên nhiều điều.
RestClient/@HttpExchangeở module này cũng là chỗ Observation API tự động instrument khi bạn gọi ra ngoài. - Module 03 — Caching, Async & Scheduling:
@Asyncở module 03 chạy trên virtual thread đúng theo cấu hình đã bật ở bài 02; pitfallThreadLocalphình to ở bài 03 áp thẳng vào bất kỳKeyGeneratorcache nào tự đọc context theo thread. Sealed Result tiếp tục làm kiểu trả về khi luồng phụ qua Spring Events cần báo kết quả thành công hay thất bại.
✅ Self-assessment
Bạn đã đạt module này nếu trả lời được:
- Dựng được record làm DTO request/response, và giải thích được vì sao nó gãy khi dùng làm JPA entity hay bean cần setter injection — nếu chưa chắc, đọc lại bài 01 mục "Ranh giới record: chỗ hợp, chỗ gãy".
- Quyết định đúng có nên bật virtual threads hay không, dựa trên hồ sơ tải I/O-bound hay CPU-bound của service — nếu chưa chắc, đọc lại bài 02 mục "Bật hay không — bảng quyết định".
- Chẩn đoán được ba hỏng hóc hay gặp sau khi bật virtual threads: pinning,
ThreadLocalphình to, và mất backpressure của pool — nếu chưa chắc, đọc lại bài 03. - Dựng được client gọi HTTP ra ngoài bằng RestClient hoặc
@HttpExchange, với timeout và xử lý lỗi tường minh — nếu chưa chắc, đọc lại bài 04 mục "Timeout: mặc định là chờ mãi" và bài 05 mục "Cơ chế bên dưới: proxy quanh RestClient". - Model được kết quả nghiệp vụ bằng sealed type và pattern matching, rồi map sang ProblemDetail thay vì ném exception — nếu chưa chắc, đọc lại bài 06 mục "Dựng Result bằng sealed interface" và phần Pitfall về
@Transactional.
🚀 What's next
Bạn vừa hiện đại hoá tầng Java bên trong TaskFlow: dữ liệu bất biến, runtime request phù hợp tải, client ra ngoài có kiểm soát, kết quả nghiệp vụ tường minh. Câu hỏi tiếp theo tự nhiên là: hệ thống này có đang chạy khoẻ không, và bạn biết được điều đó bằng cách nào? Module 02 "Observability & Production-ready" dạy Actuator, Micrometer, percentile và tracing OTLP để trả lời đúng câu hỏi đó.
Observability & Production-ready — tổng quan
📚 Tài liệu mở rộng
- JEP 444 — Virtual Threads — định nghĩa chính xác pinning và event JFR
jdk.VirtualThreadPinned(cờ-Djdk.tracePinnedThreadschỉ tồn tại tới JDK 23). - JEP 491 — Synchronize Virtual Threads without Pinning — thay đổi từ JDK 24: monitor gắn với virtual thread, gỡ phần lớn pinning do
synchronized. - Sealed Classes and Interfaces — Oracle Java Language docs — ràng buộc trên mỗi nhánh permit và vì sao sealed cho phép exhaustiveness checking.
- Spring Framework Reference — REST Clients —
RestClient,retrieve()/onStatus(), HTTP Interface. - Spring Boot Reference — Task Execution and Scheduling — hành vi chính xác của
spring.threads.virtual.enabled. - Spring Framework Reference — Rolling back a declarative transaction — quy tắc rollback mặc định và
setRollbackOnly.
- Gunnar Morling — Enforcing Java Record Invariants With Bean Validation — tác giả Hibernate Validator giải thích cơ chế copy constraint sang record.
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên