Retry xử lý lỗi tạm thời, DLQ giữ message không xử lý được, còn reconciliation phát hiện cả những event chưa từng tới queue. Cần đủ cả ba lớp.
MioCraft / latest
Bài mới cập nhật
185 bài · bài mới nhất hiển thị trước
Không tìm thấy bài viết nào.
Gateway inject X-User-Id tiện nhưng chỉ an toàn khi downstream xác minh request thật sự đến từ trusted boundary. Internal flag không phải authentication.
Đọc tên và giá trị live từ service khác khiến nội dung hợp đồng thay đổi theo dữ liệu hiện tại. Snapshot có chủ ý bảo vệ lịch sử và domain invariant.
Ký hợp đồng và khóa đủ tiền là hai business fact khác nhau. Two-phase lock làm rõ buyer payment, seller deposit và thời điểm contract thật sự có hiệu lực.
Persistent volume khiến seed thiếu và migration stale không lộ ra. Chỉ một lần dựng stack hoàn toàn sạch mới kiểm tra được môi trường có tái tạo thật sự hay không.
Bọc cả @RabbitListener trong một catch khiến lỗi DB tạm thời bị gắn nhãn payload sai và mất cơ hội retry. Error boundary phải đặt đúng chỗ.
Nhận event xong lại gọi ngược producer để lấy dữ liệu khiến consumer vẫn phụ thuộc đồng bộ. Event-carried state transfer cắt chuỗi lỗi đó.
Gọi REST đồng bộ để thay đổi dữ liệu của service khác tạo ra distributed write không có transaction chung. Đúng hơn là để service sở hữu state tự xử lý event.
Check tồn tại trước khi save chỉ là bước đầu. Race condition, unique constraint và dữ liệu khác request mới quyết định một thao tác có thật sự idempotent hay không.
Exchange và binding đều đúng chưa có nghĩa business logic đã chạy. Message converter sai có thể làm listener fail trước dòng code đầu tiên.
GoF gặp một mâu thuẫn kinh điển: thêm operation mới mà không sửa class cũ, hoặc thêm class mới mà không sửa operation cũ — bạn chỉ được chọn một. Visitor giải nửa bài đó.
GoF gặp bài toán: cùng một traversal logic phải chạy trên array, linked list, tree, graph — mà không thay đổi code duyệt. Iterator tách 'cách duyệt' ra khỏi 'cấu trúc chứa data'. Java đã bake nó vào ngôn ngữ.
CSS variables, prefers-color-scheme, OS integration, và tại sao dark mode đúng cách phức tạp hơn mày nghĩ.
GoF gặp bài toán: một request cần được xử lý bởi nhiều handler theo thứ tự, nhưng sender không nên biết handler nào sẽ thật sự xử lý nó. Bạn đang dùng pattern này mỗi khi gửi request qua Spring Security.
Bạn thân mày hiện 'Active now' nhưng không reply 20 phút. Hay mày tắt app rồi mà vẫn thấy online. Đây không phải bug — đây là hàng loạt trade-off có chủ ý.
Click vào bit.ly/3xK9mP và mày đến đích trong tích tắc. Đằng sau đó là một database lookup, một HTTP response code, và một cái trick browser caching mà phần lớn dev không biết.
Mày search 'python tutorial' và Google trả về 4 tỷ kết quả trong 0.31 giây. Không phải vì server Google siêu nhanh — mà vì nó không bao giờ scan 4 tỷ trang đó.
Mỗi thứ Hai, Discover Weekly cho mày 30 bài chưa từng nghe — và ~70% trong số đó đúng trend. Đằng sau là matrix factorization, audio feature vectors, và NLP trên hàng triệu bài blog nhạc.
Mày gõ 'teh' và nó tự đổi thành 'the'. Gõ 'im' thành 'I'm'. Đôi khi sai một cách ngớ ngẩn. Đằng sau là edit distance, keyboard proximity, và một language model chấm điểm từng candidate.
Mày search 'tai nghe' một lần, hôm sau homepage toàn tai nghe. Không phải Shopee đọc được tâm trí — đó là collaborative filtering và real-time session signals phối hợp với nhau.
GoF gặp cấu trúc dạng cây — menu có submenu, category có subcategory, permission có group. Composite cho phép xử lý một node đơn hay toàn bộ cây bằng cùng một cách.
Instagram Reels mất nhiều tháng để hiểu mày. TikTok chỉ cần 10 phút. Không phải magic — đó là implicit signals, cold start strategy, và two-stage recommendation pipeline.
Con số 0–100 của Lighthouse không phải cảm tính — nó là weighted average của các metric đo chính xác trải nghiệm người dùng. LCP, INP, CLS, và lý do con số đó ảnh hưởng đến SEO.
Tải landing page mà phải kéo theo code của admin dashboard — đó là vấn đề bundle khổng lồ. Code splitting, dynamic import, và content hash giải quyết nó như thế nào.
Cùng một ảnh, WebP nhỏ hơn JPEG 30%, AVIF nhỏ hơn 50% — mà mắt người không phân biệt được. Đây là ba lớp kỹ thuật làm cho web năm 2024 load nhanh hơn mà không mờ hơn.
GoF gặp vấn đề với code legacy và third-party library có interface không khớp. Adapter là cái adapter phích cắm của lập trình — không thay đổi gì ở hai đầu, chỉ làm cho chúng vừa nhau.
QR code của MoMo không chứa số tài khoản của mày — nó chứa một session token có chữ ký mật mã. Đây là thứ ngăn kẻ xấu chụp màn hình rồi thanh toán lại.
Hai người gõ vào cùng một đoạn văn cùng lúc mà không ai bị mất chữ. Không phải locking, không phải merge conflict — Operational Transformation là cái trick đứng sau tất cả.
Trong PUBG hay Liên Quân, mày thấy nhân vật đối thủ chạy mượt dù họ ở đầu dây kia của internet. Không phải server gửi data 60 lần/giây — mà client đang tự diễn trên sân khấu của riêng nó, dựa trên dữ liệu 100ms cũ.
Zalo gửi tin nhắn lúc 2 giờ sáng, điện thoại mày rung dù app đã tắt hoàn toàn. Không phải app đang ngầm chạy — mà OS đang duy trì một kết nối TCP thay cho tất cả app. Đây là cách FCM và APNs hoạt động.
Dấu ba chấm '...' xuất hiện chỉ vài mili giây sau khi người kia bắt đầu gõ — nhưng nếu gửi event mỗi lần nhấn phím, server sẽ sập trong vài giây. Trick thật sự là throttle + timeout, và nó đơn giản hơn mày nghĩ.
GoF gặp vấn đề với object có quá nhiều optional field. Builder tách quá trình tạo object thành từng bước. Lombok @Builder thì sao?
Trace ID theo request qua nhiều service, Micrometer Tracing với Spring Boot 3, đọc waterfall diagram để debug.
1 tick, 2 tick, tick xanh — Zalo biết chính xác lúc nào mày mở tin nhắn. Cơ chế đằng sau không phải là server hỏi thăm liên tục, mà là ACK packet đi ngược chiều.
Gói tin từ điện thoại mày đến server Netflix đi qua hàng chục router. Bất kỳ ai ngồi giữa đường đều có thể thấy nó. Vậy tại sao họ không đọc được?
Cái mã 6 số trong Google Authenticator không được tạo ra từ server — điện thoại của mày tự tính nó mà không cần internet. TOTP là thứ đứng sau tất cả.
Gmail, YouTube, Notion, Slack đều dùng tài khoản Google nhưng không app nào biết password của mày. Đây là cách OAuth 2.0, authorization code flow, và session cookie phối hợp để làm điều đó.
Pull-to-refresh không đợi API xong mới chạy animation. Đây là cách touch events, CSS transform, và optimistic UX phối hợp để tạo cảm giác nhanh dù mạng chậm.
YouTube không download cả video — nó chỉ tải đúng mấy giây xung quanh chỗ mày đang xem. Đây là cách DASH, manifest file, và adaptive bitrate làm điều đó.
GoF gặp vấn đề với `new` keyword — nó buộc code phải biết cụ thể class nào sẽ được tạo. Factory cắt đứt sự phụ thuộc đó. Abstract Factory đi thêm một bước.
Load balancer phân phối traffic. Gateway xử lý auth, routing, transformation. Hiểu đúng để design microservices đúng.
Mỗi lần mày gõ một chữ, Google trả về gợi ý trong dưới 50ms. Không phải vì server Google nhanh — mà vì có rất nhiều thứ xảy ra trước khi request đó thậm chí được gửi đi.
Cái blur nhẹ hiện ra ngay lập tức trước khi ảnh load xong — không phải hiệu ứng cho đẹp. Đó là kết quả của 3 kỹ thuật khác nhau, mỗi cái giải quyết một vấn đề mà cái kia không làm được.
Icon xe Grab di chuyển mượt 60fps trên map của mày — nhưng thực ra nó chỉ nhận dữ liệu GPS mỗi 3-5 giây. Cái trick đằng sau là dead reckoning, kỹ thuật dẫn đường mà NASA dùng từ thập niên 60.
GoF tạo ra Singleton để giải quyết một vấn đề cụ thể. Vấn đề bắt đầu khi một pattern bị dùng để giải quyết mọi thứ.
Sidecar proxy pattern, control plane vs data plane, và câu hỏi quan trọng nhất: có phải lúc nào cũng cần không?
3 trạng thái Closed/Open/Half-Open, khi nào trip, khi nào reset, fallback strategy với Resilience4j + Spring Boot.
Reverse proxy, HTTPS, gzip, rate limit ở tầng infra — những thứ Nginx phải làm trước khi request chạm Spring Boot.
Alert 5xx spike: correlation ID, timeline, thay đổi gần đây — trước khi grep exception ngẫu nhiên. Checklist cho lần đầu vào on-call rotation.
Replica async sau primary — read-your-writes, route sau mutation, và khi nào chấp nhận eventual consistency.
Flyway ADD COLUMN NOT NULL một lần = deploy đứng. Thêm nullable → deploy code → backfill → enforce — schema và app lệch pha có kiểm soát.
EXPLAIN Using index, composite index và thứ tự cột — vì sao index đúng tên vẫn không cover query.
SIGTERM, server.shutdown=graceful, readiness fail khi DB down — K8s không route traffic vào pod đang chết.
Redis Lua chặn tranh slot ở cache — nhưng confirm vào DB vẫn có thể trùng nếu thiếu ràng buộc và lock đúng chỗ.
LocalDateTime không có timezone. API contract: Instant + ZoneId, lưu UTC, hiển thị theo clinic. Bug lịch hẹn ngày 15 thành 14.
User tắt email marketing vẫn nhận reminder — check preference trước send, link unsubscribe one-click, audit log.
@Scheduled trên hai pod Spring = email nhắc lịch gửi đôi. Distributed lock Redis/DB — chỉ một instance chạy job mỗi lần.
Import 10k dòng: chunk size, validate từng row, gom lỗi trả report — một dòng sai không rollback cả file.
Gateway gọi ngược POST /webhooks/payment — không tin body, verify HMAC, xử lý trùng eventId. Cặp đôi với idempotency key phía client.
CompletableFuture trên ThreadPoolTaskExecutor, @EnableAsync, self-invocation, và vì sao @Async trong @Transactional thường không còn transaction.
AFTER_COMMIT vẫn fail nếu process chết trước khi gửi mail. Ghi outbox trong cùng transaction DB, worker gửi sau — at-least-once có kiểm soát.
Đổi field response là đổi contract — versioning, deprecation có hạn, và phân biệt breaking vs non-breaking change.
@Version và OptimisticLockException: last-write-wins im lặng vs báo conflict cho user merge. Khác race booking slot nhưng cùng đau production.
Alert phải báo user đang đau, có hành động rõ, và page ít — không phải mọi log ERROR đều ping Slack lúc 3h sáng.
HTTP không tự push. Short polling đốt server, SSE cho one-way realtime, WebSocket khi cần hai chiều — chọn sai là mở connection vô ích hoặc over-engineer notification đơn giản.
Rolling update mặc định trên K8s, blue-green đổi traffic một phát, và vì sao zero-downtime không có nghĩa zero-risk.
LIKE '%nguyen%' không scale. FULLTEXT cho tên bệnh nhân vừa. Elasticsearch khi cần fuzzy, facet, relevance — đừng over-engineer ngày đầu.
Merge liên tục lên main, ship binary hàng ngày, bật tính năng bằng flag — rollout 5% trước khi mở cho toàn bộ clinic.
@Where giúp nhưng native query, JOIN thiếu filter, và admin export quên deleted_at — patient thấy appointment đã hủy. Cách audit và phòng leak.
P10/Bài 02 nói về idempotency inbound. Outbound: RestClient timeout, @Retryable chỉ trên read hoặc request có Idempotency-Key — tránh capture hai lần.
Staging mirror prod về topology và config shape — data masked, secret riêng, gateway sandbox. Integration test 103 fail vì staging khác local, không phải vì test dở.
On-call đã stabilize (bài 121). Postmortem blameless: timeline, root cause hệ thống, action item có owner — không phải biên bản kỷ luật.
Boolean isPaid + isCancelled dễ lệch. Enum trạng thái, transition hợp lệ, và refund chỉ khi payment đã CAPTURED — tránh free slot và double refund.
Global limit 100 req/phút không cứu được POST /payments khi GET /schedules vẫn ổn. Cấu hình limit theo route, tier user, và trả header để client không retry vô hạn.
Ảnh X-quang 15MB không nên base64 qua JSON. Multipart cho upload vừa, presigned URL cho file lớn — backend không làm proxy băng thông.
Reset link mang token ngẫu nhiên có TTL, hash trong DB, invalidate sau dùng. Không bao giờ đặt password mới hoặc password cũ trong query string.
4xx là lỗi client, 5xx là lỗi server. 201 Created vs 200 OK. API trả 200 kèm success:false khiến monitoring và client đều mù.
OncePerRequestFilter, JwtAuthenticationFilter, authorization vs authentication — và tại sao filter chạy trước @PreAuthorize.
Postman chạy được, browser báo CORS error. Same-origin policy, preflight OPTIONS, và cách cấu hình CorsConfigurationSource trong Spring Security.
Gọi this.save() trong cùng class không qua proxy — transaction không mở. Rollback chỉ với RuntimeException. Bug thầm lặng khiến data half-committed.
Password trong application.yml commit lên Git là incident chờ xảy ra. Config theo môi trường, Spring profiles, và cách HMS tách secret khỏi codebase.
Optional không phải cách viết null an toàn cho mọi chỗ. Return type có lý, field và parameter thì gần như luôn sai — đó là lý do cách dùng này thường bị review lại.
Unit test kiểm tra logic trong isolation. Integration test kiểm tra các layer phối hợp đúng không. @DataJpaTest, @WebMvcTest, @SpringBootTest — khi nào dùng cái nào và tại sao cần cả hai.
Log quá nhiều không giúp debug nhanh hơn. Log đúng level, đúng context, đúng thông tin — và biết những gì tuyệt đối không được log. MDC correlation ID để trace request xuyên suốt.
Session lưu state ở server, JWT tự chứa thông tin và server không cần nhớ gì. Anatomy của một JWT, verify flow, access token vs refresh token, và tại sao JWT không thể revoke ngay.
LIMIT/OFFSET đơn giản nhưng chậm khi offset lớn. Cursor pagination nhanh hơn nhưng không nhảy trang được. Trade-off và cách implement cả hai trong Spring Data.
ALTER TABLE tay trên production, ddl-auto=update, hay migration script có version — ba cách thay đổi schema và tại sao chỉ một cái an toàn. Flyway trong Spring Boot từ đầu đến cuối.
Constructor có 8 tham số không tên — đây là dấu hiệu Builder Pattern cần xuất hiện. Từ Java thuần đến Lombok @Builder, cách tổ chức object creation đúng trong Spring Boot.
P01/Bài 06 trong series này nói về technical debt từ góc độ kỹ thuật — bạn đang nợ ai, nợ cái gì. Bài này nói về nó từ góc độ product: khi nào nên chủ động...
Mình từng rất tự hào về một cái booking flow mình viết. Clean architecture đúng sách, separation of concerns rõ ràng, unit test coverage cao, zero redun...
Developer hiểu business model sẽ ưu tiên đúng hơn, estimate chính xác hơn, và đưa ra đề xuất kỹ thuật có business context. Đó là một bước phát triển quan trọng trong tư duy kỹ thuật.
Mỗi tính năng là gánh nặng: cần maintain, cần test, cần document, cần support. Tính năng tốt nhất đôi khi là tính năng bạn quyết định không build.
MVP không phải là product tệ. MVP là version nhỏ nhất có thể validate được hypothesis. Build đúng thứ trước khi build đúng cách.
"Chúng ta cần cache" — thực ra là query đang thiếu index. "Cần thêm field vào DB" — thực ra là UI đang hiển thị sai. Hiểu đúng vấn đề trước khi giải nó.
Developer implement ticket. Product-minded engineer hỏi: ticket này giải quyết vấn đề gì, có cách nào đơn giản hơn không, và đây có phải vấn đề đúng cần giải quyết không?
Một nút bấm đơn giản với người dùng — nhưng phía sau là availability check, concurrency control, payment processing, notification, calendar sync, và audit logging.
isActive, isEnabled, isDeleted — ba field có vẻ tương tự nhưng semantic khác nhau. Khi developer mới hiểu nhầm, logic sai được build lên trên đó, và bug chỉ xuất hiện trong production.
Update profile thành công, reload trang vẫn thấy tên cũ. Cache invalidation là một trong hai vấn đề khó nhất trong computer science — và đây là cách xử lý thực tế.
Tạo user trong DB thành công, tạo user trong Keycloak thất bại — hệ thống ở trạng thái inconsistent. Saga pattern và compensation transaction là cách xử lý distributed failure.
Role-based access control không đủ cho bài toán: Doctor A chỉ xem được bệnh nhân của Doctor A. Attribute-based access control và cách implement đúng trong Spring Security.
Gửi email xác nhận đặt lịch, sau đó transaction rollback vì lỗi — user nhận email nhưng lịch không tồn tại. Transactional outbox pattern giải quyết vấn đề này đúng cách.
Network timeout, user double-click, retry logic — tất cả đều có thể gây ra duplicate payment. Idempotency key đảm bảo cùng một request chỉ được xử lý đúng một lần.
Race condition trong booking system: hai người cùng thấy slot trống, cùng đặt, cả hai thành công — nhưng chỉ có một slot. Redis Lua script và atomic operation là giải pháp.
Mỗi layer thêm vào là một indirection, một điểm failure, và một thứ cần maintain. Layer nên tồn tại vì nó giải quyết vấn đề thật — không phải vì pattern nói phải có.
Premature optimization là evil — premature scaling còn tệ hơn. Phần lớn startup thất bại vì không có user, không phải vì không scale được. Build for now, design for scale.
Queue che đi failure thay vì giải quyết nó. Message tích lũy trong queue, consumer xử lý không kịp, queue overflow — hệ thống sập theo cách chậm hơn nhưng vẫn sập.
Clean Architecture thêm nhiều layer và abstraction. Với CRUD app đơn giản — đây là over-engineering. Với hệ thống phức tạp có nhiều business rule — đây là đầu tư đúng đắn.
Don't Repeat Yourself hướng dẫn về knowledge duplication — không phải code duplication. Đôi khi duplicate code là đúng. Wrong abstraction tệ hơn duplicate code.
Cache thêm complexity, tăng memory usage, và tạo ra stale data problems. Nếu cache hit rate thấp — bạn đang trả chi phí của cache mà không nhận được lợi ích.
PostgreSQL scale tốt đến hàng tỷ row với index và sharding đúng cách. Nhiều công ty chuyển sang NoSQL rồi nhận ra vấn đề không phải ở SQL — mà ở cách họ dùng database.
Microservices giải quyết vấn đề team autonomy và deployment independence — không phải vấn đề performance. Scale nằm ở database, caching, và infrastructure — không phải ở việc chia service.
Consistency, Availability, Partition Tolerance — bạn chỉ có thể chọn hai. Hiểu CAP không phải để thuộc lòng — mà để đưa ra quyết định đúng khi thiết kế distributed system.
Khi service B chậm, service A gọi B sẽ bị block, thread pool cạn kiệt, và A sập theo. Circuit Breaker phát hiện failure và ngắt connection trước khi cascade xảy ra.
Rate limiting kiểm soát số lượng request trong một khoảng thời gian. Token bucket, sliding window, fixed window — mỗi thuật toán có trade-off khác nhau về accuracy và memory.
Queue giải decoupling giữa producer và consumer — nhưng cũng thêm complexity, latency, và failure modes mới. Không phải bài toán nào cũng cần queue.
Load balancer phân phối traffic đến nhiều server — nhưng thuật toán, layer 4 vs layer 7, health check, và sticky session mới là thứ quyết định hệ thống có thật sự reliable không.
API xấu không gây lỗi ngay — nó tạo ra misunderstanding giữa các team, edge case không được handle, và coupling ẩn tích lũy theo thời gian.
Microservices không phải bước tiến hoá tự nhiên của Monolith. Đây là hai lựa chọn kiến trúc khác nhau, giải quyết vấn đề khác nhau — và cái giá của việc chọn sai rất đắt.
Functional: hệ thống làm gì. Non-functional: hệ thống hoạt động tốt thế nào. Bỏ qua non-functional requirements là lý do nhiều hệ thống sập khi có người dùng thật.
Code đúng chưa đủ để hệ thống chạy tốt ở scale lớn. System design là khả năng thiết kế hệ thống đáp ứng được non-functional requirements: scale, availability, latency.
Một số việc không cần làm ngay: gửi email, tạo report, resize ảnh. Queue tách việc nhận request và xử lý request — giúp hệ thống responsive và resilient hơn.
Race condition, lost update, dirty read — đây là những vấn đề xảy ra khi concurrent requests cùng thao tác trên một dữ liệu. Và chúng chỉ xảy ra trong production.
Cache expire, hàng nghìn request cùng lúc hit database, database quá tải và sập — kéo theo cache không thể warm up lại. Thundering herd problem và cách phòng tránh.
Cache là một trong những công cụ mạnh nhất để tăng performance — và cũng là nguồn gốc của những bug khó chịu nhất. Hiểu caching strategy trước khi implement.
Blocking I/O giữ thread chờ. Non-blocking I/O giải phóng thread để làm việc khác. Hiểu sự khác biệt này giúp bạn thiết kế hệ thống xử lý concurrent requests tốt hơn.
Thread-per-request model của Spring Boot truyền thống vs non-blocking Event Loop của Spring WebFlux. Trade-off về complexity, performance, và khi nào nên dùng cái nào.
Từ lúc bạn gõ Enter đến khi nhận được response — hàng chục bước xảy ra. Hiểu luồng này giúp bạn debug performance issue và thiết kế hệ thống tốt hơn.
Tạo database connection tốn kém. Connection pool tái sử dụng connection — nhưng pool quá nhỏ thì bottleneck, pool quá lớn thì database overload.
Thêm cột is_deleted tưởng là dễ — cho đến khi unique constraint bị vỡ, query trở nên phức tạp, và audit log không hoạt động như kỳ vọng.
NoSQL không phải phiên bản mới hơn của SQL. Chúng giải quyết vấn đề khác nhau. Chọn dựa trên access pattern, consistency requirement, và team capability — không phải trend.
Normalize quá thì JOIN nhiều, query chậm. Denormalize quá thì data inconsistency. Không có công thức cố định — chỉ có trade-off dựa trên access pattern thực tế.
Load 100 user, mỗi user trigger thêm 1 query để lấy orders — là 101 queries thay vì 2. N+1 không gây lỗi, chỉ làm app ngày càng chậm cho đến khi không thể chịu được.
Deadlock xảy ra khi hai transaction chờ nhau giải phóng lock. Database phát hiện và kill một transaction — nhưng fix đúng cách mới tránh được tái diễn.
Read Uncommitted, Read Committed, Repeatable Read, Serializable — mỗi level là một trade-off giữa consistency và performance. Chọn sai là data corruption hoặc bottleneck.
Multi-Version Concurrency Control là lý do PostgreSQL và MySQL InnoDB có thể xử lý concurrent reads và writes hiệu quả. Hiểu MVCC là hiểu cách database thật sự hoạt động.
EXPLAIN ANALYZE là công cụ mạnh nhất để debug query chậm. Hiểu execution plan giúp bạn biết database đang làm gì và tại sao query của bạn không dùng index.
Index không phải phép màu. Tạo sai index, query sai cách, hoặc index quá nhiều — đều có thể làm hệ thống chậm hơn thay vì nhanh hơn.
Khi review PR, người có kinh nghiệm không chỉ đọc code — họ hỏi: change này có đúng direction không? Có tạo ra dependency mới không? Có làm hệ thống khó thay đổi hơn không?
Command đóng gói một yêu cầu thành object — cho phép queue, log, undo, và retry. Đây là nền tảng của event sourcing và nhiều hệ thống phức tạp.
Proxy kiểm soát access đến object thật. Logging, caching, security check, lazy loading — tất cả đều có thể implement qua Proxy mà không sửa code gốc.
Kế thừa mở rộng là static và compile-time. Decorator mở rộng là dynamic và runtime. Khi behavior cần thay đổi linh hoạt — Decorator là lựa chọn đúng.
Facade đơn giản hóa interface phức tạp. Thay vì để mọi nơi gọi Keycloak SDK trực tiếp — một Facade che đi sự phức tạp và là điểm thay đổi duy nhất.
Observer Pattern giải coupling giữa event publisher và subscriber. Nhưng gọi notification bên trong database transaction là một trong những lỗi kiến trúc phổ biến nhất.
Strategy và State trông giống nhau về cấu trúc nhưng giải quyết vấn đề khác nhau. Hiểu sai là dùng sai — và code sẽ không express đúng intent.
Bất cứ khi nào bạn có một quy trình cố định nhưng các bước có thể thay đổi — Template Method đang ở đó. Abstract class trong Spring Boot là ví dụ điển hình.
Pattern là giải pháp cho vấn đề đã biết — không phải template để áp vào mọi nơi. Dùng pattern không có vấn đề để giải quyết là over-engineering.
Áp dụng SOLID quá sớm tạo ra over-engineering. Áp dụng quá muộn tạo ra debt. Biết khi nào cần dùng mới là kỹ năng thật sự.
Dependency Inversion: code cấp cao không phụ thuộc code cấp thấp. Cả hai phụ thuộc abstraction. Đây là nền tảng của Dependency Injection trong mọi framework.
Interface Segregation: đừng ép class implement những method nó không cần. Interface phình to là dấu hiệu của coupling ẩn và design thiếu suy nghĩ.
Liskov Substitution Principle: subclass phải thay thế được superclass mà không làm hỏng chương trình. Vi phạm LSP là nguồn gốc của những bug khó tìm nhất.
Open/Closed Principle: mở để mở rộng, đóng để sửa đổi. Khi thêm feature mới mà phải sửa code đang chạy tốt — đó là dấu hiệu cần abstraction.
Single Responsibility không có nghĩa là class chỉ có một method. Có nghĩa là class chỉ thay đổi vì một lý do — một actor duy nhất yêu cầu nó thay đổi.
SOLID không phải checklist để tick vào. Đó là tư duy về cách tổ chức code để nó không sụp đổ khi requirement thay đổi.
Kiến trúc đẹp trên giấy thường sụp đổ trong thực tế vì team không hiểu nó, không theo nó, hoặc không có ai enforce nó.
Microservices giải quyết vấn đề của scale và team autonomy — không phải vấn đề của code xấu. Chuyển sang microservices khi chưa sẵn sàng là tự làm khổ mình.
Big bang rewrite thất bại 90% thời gian. Kiến trúc tốt được xây dựng bằng cách di chuyển dần — từng boundary một — không phải bắt đầu từ đầu.
Khi toàn bộ business logic phụ thuộc vào Spring Boot — bạn không có kiến trúc, bạn có một Spring Boot app. Và khi Spring thay đổi, mọi thứ thay đổi.
Service 2000 dòng là triệu chứng, không phải bệnh. Bệnh là không có ranh giới rõ ràng giữa các responsibility trong hệ thống.
Dùng Entity làm DTO, hay Domain Model làm Entity — đây là sai lầm phổ biến nhất trong Spring Boot và nó phá vỡ boundary một cách thầm lặng.
Không phải database, không phải framework, không phải API. Use case — hành động người dùng thực hiện — là thứ hệ thống thật sự tồn tại để phục vụ.
Khi domain model import JPA annotation — kiến trúc đã bị vi phạm. Business rule không nên biết data được lưu ở đâu hay như thế nào.
Boundary là ranh giới giữa các phần của hệ thống. Vẽ sai một lần — mọi thứ build lên trên đó đều sai theo.
Business logic nằm trong Controller, trong SQL query, hay trong UI — đó là technical debt không thể tránh khỏi. Và nó tích lũy theo thời gian.
Layered architecture giải quyết một vấn đề cụ thể. Khi bạn dùng nó cho bài toán khác — bạn đang tạo ra vấn đề thay vì giải quyết nó.
Pattern 3 layer quen thuộc này không sai — nhưng nếu không hiểu đúng mục đích, nó sẽ biến thành nơi chứa mọi thứ và không ai dọn được.
Deadline không phải lý do để viết code xấu — đó là lý do để viết code đơn giản hơn. Hai thứ này khác nhau hoàn toàn.
Refactor không phải là viết lại, không phải là thêm feature. Đó là cải thiện cấu trúc code mà không thay đổi behavior — và có test để chứng minh.
Khả năng test được là thuộc tính thiết kế, không phải afterthought. Code khó test là code có quá nhiều coupling và quá nhiều responsibility.
catch (Exception e) {} là một trong những đoạn code nguy hiểm nhất có thể tồn tại. Lỗi bị ẩn đi, hệ thống tiếp tục chạy sai mà không ai hay.
Con số 86400 xuất hiện ở 12 chỗ khác nhau trong codebase. Đó là 12 chỗ có thể sai khi business rule thay đổi — và không ai biết.
Boolean parameter trong function signature là mùi code rõ ràng nhất. Nó làm function làm hai việc và caller không hiểu đang gọi cái gì.
Comment không phải lúc nào cũng tốt. Khi bạn cần comment để giải thích code — thường đó là dấu hiệu code cần được viết lại.
SRP nghe có vẻ đơn giản — nhưng hầu hết dev định nghĩa "một việc" sai. Đây là cách hiểu đúng để áp dụng thật sự.
Một cái tên tốt loại bỏ nhu cầu comment. Một cái tên xấu là nguồn gốc của mọi hiểu lầm trong codebase.
Clean Code không phải là code đẹp hay code ngắn. Đó là code mà người khác có thể đọc, hiểu, và thay đổi mà không cần hỏi tác giả.
Debug không phải là đoán mò. Có một quy trình tư duy để tìm lỗi nhanh hơn — và hầu hết dev không ai dạy họ cách đó.
Code phức tạp thường là dấu hiệu của sự chưa chín — không phải sự tinh tế. Người giỏi nhất thường viết code đơn giản nhất.
Mọi quyết định kỹ thuật đều có cái giá. Kinh nghiệm giúp nhìn rõ hơn thứ đang đánh đổi và lý do của lựa chọn đó.
Hệ thống tốt không phải là hệ thống không bao giờ lỗi — mà là hệ thống lỗi một cách có kiểm soát và tự phục hồi được.
Technical debt là công cụ, không phải tội lỗi. Vấn đề là khi bạn nợ mà không biết mình đang nợ — đến lúc trả giá mới hay.
Không phải lúc nào đơn giản cũng là đúng. Under-engineering tạo ra technical debt ngay từ ngày đầu mà không ai nhận ra.
Xây dựng hệ thống phức tạp cho bài toán đơn giản không phải là giỏi — đó là dấu hiệu bạn chưa hiểu vấn đề thật sự.
Viết code nhanh không phải là viết ngay. Dành thời gian hỏi đúng câu hỏi giúp giảm số lần phải sửa lại.
Sự khác biệt không nằm ở kỹ thuật — mà ở cách nhìn vào một yêu cầu: bạn đang giải quyết hôm nay hay đang thiết kế cho tương lai?
Viết code pass test chưa đủ. Một PR có thể bị từ chối không phải vì code sai — mà vì code không thể sống cùng hệ thống theo thời gian.