Ở phần trước, chúng ta đã cùng dựng một mini order system bằng Go, Gin, Keycloak, Temporal, MySQL, MongoDB và gRPC để nhìn toàn cảnh cách một backend nhiều thành phần vận hành với nhau. Từ authentication, xử lý order, gọi service nội bộ đến workflow async, mục tiêu của phần đó là giúp hệ thống chạy được và cho mình một mental model tổng quát về toàn bộ playground.

Nhưng build xong và chạy được mới chỉ là bước đầu. Sang phần này, mình muốn đi sâu hơn vào những lỗi thật phát sinh trong quá trình dựng và test hệ thống: token bị reject, port bị conflict, workflow retry mãi, database state không khớp hoặc Keycloak realm gây nhầm lẫn. Chính những lúc debug như vậy mới giúp mình hiểu rõ từng thành phần đang làm gì, chúng kết nối với nhau ra sao và cần mở đúng màn hình nào để tìm ra vấn đề.

1. Executive Summary

Link repo mình đã chuẩn bị trước: https://github.com/Keruedu/OrderPlayground

Sau khi build playground, phần làm mình hiểu sâu nhất không phải lúc mọi thứ chạy xanh, mà là lúc nó lỗi. Bài này ghi lại cách mình debug các lỗi thật: PowerShell chặn script, Docker/port conflict, MySQL client lỗi public key, Keycloak realm gây nhầm, JWT issuer mismatch và Temporal activity retry.

  • Keycloak nên được xem như nơi debug identity/access, không chỉ là màn hình login.
  • Temporal UI là nơi xem workflow history, activity retry và trạng thái order async.
  • DB state và audit events là bằng chứng để kiểm tra workflow đã đi tới đâu.
  • Debug tốt là giảm uncertainty từng bước, không sửa nhiều thứ cùng lúc.

2. Motivation: hệ thống chạy được chưa chắc đã hiểu

Lúc API trả 200, mình dễ có cảm giác đã hiểu hệ thống. Nhưng chỉ cần token bị reject, order kẹt PENDING, hoặc workflow retry mãi là mình nhận ra: hiểu thật nghĩa là biết mở đúng màn hình, nhìn đúng signal, rồi sửa đúng chỗ.

Vì vậy bài này không liệt kê lại tech stack. Mình đi theo incident: symptom là gì, mình mở đâu, kiểm tra gì, và rút ra mental model nào.

3. Background knowledge cần biết

Thuật ngữMình hiểu đơn giản làNhìn ở đâu
RealmKhông gian riêng để quản lý user, client và role.Keycloak Admin Console
ClientỨng dụng xin token hoặc dùng token. Ở đây là gateway-api.Keycloak > Clients
JWT issuerDòng ghi token do ai phát hành. Sai issuer thì gateway reject.Token payload + gateway config
WorkflowKịch bản nhiều bước xử lý order.Temporal UI
ActivityMột bước cụ thể, ví dụ reserve inventory.Temporal workflow history
Audit eventNhật ký nghiệp vụ để đọc lại order đã đi qua đâu.MongoDB audit_events

Với Keycloak, mình tách authentication và authorization: user là ai, và user được làm gì. Với Temporal, mình tách workflow và activity: quy trình lớn và từng bước nhỏ. Với DB, mình tách business state và audit trail: trạng thái hiện tại và lịch sử đã xảy ra.

4. Mental model: mở đúng màn hình trước

Debug decision tree: gặp lỗi thì mở màn hình nào trước

Hình 1. Debug decision tree: gặp lỗi thì mở màn hình nào trước.

Good debugging trong playground này là mỗi vòng chỉ kiểm chứng một giả thuyết. Nếu API trả 403, mình kiểm tra token/role trước. Nếu order đứng PENDING, mình mở Temporal history trước. Nếu workflow completed nhưng dữ liệu sai, mình kiểm tra MySQL và MongoDB.

PathNó trả lời câu hỏi gì?Tool mình mở
Request pathRequest từ user đi qua gateway, auth, DB và start workflow như thế nào?API response + gateway logs
Control pathTemporal worker đã nhận task chưa, activity nào đang retry?Temporal UI
Debug pathState thật đang nằm ở đâu và có khớp với kỳ vọng không?MySQL, MongoDB, Docker logs
  • Bad: đổi Keycloak config, restart Temporal và sửa repository cùng lúc.
  • Good: ghi symptom, chọn một signal, test lại, rồi mới sửa đúng chỗ.

5. Deep dive by incident

Các lỗi thật trong quá trình dựng playground

Hình 2. Các lỗi thật trong quá trình dựng playground.

Mỗi incident bên dưới mình đọc theo cùng một format: symptom xuất hiện ở đâu, mình mở tool nào trước, signal nào xác nhận giả thuyết, và bài học rút ra là gì. Format này giúp bài không biến thành danh sách lỗi rời rạc, mà vẫn giữ được mạch debug -> learn.

5.1 PowerShell chặn .ps1

Symptom: chạy script lấy token bị báo running scripts is disabled. Đây không phải lỗi Keycloak. Đây là policy của Windows. Cách xử lý nhanh là chạy PowerShell với ExecutionPolicy Bypass hoặc dùng Invoke-RestMethod inline.

5.2 Docker daemon và port conflict

Symptom: container không start hoặc báo port đã được allocate. Lần test thật của mình có MongoDB khác giữ 27017 và Temporal khác giữ 7233. Bài học: trước khi sửa code, kiểm tra docker ps và port mapping.

5.3 MySQL Public Key Retrieval is not allowed

Symptom: GUI/JDBC client báo Public Key Retrieval is not allowed. Container MySQL không chết. Lỗi nằm ở cách client kết nối MySQL 8. Fix nhanh là thêm allowPublicKeyRetrieval=true&useSSL=false vào JDBC URL khi dùng client dev.

5.4 Keycloak master vs order-playground

Mental model: master realm khác realm của app

Hình 3. Mental model: master realm khác realm của app.

Keycloak master có client order-playground-realm, không phải business client của gateway

Hình 4. Keycloak master có client order-playground-realm, không phải business client của gateway.

Chỗ này dễ nhầm. master là realm quản trị Keycloak. order-playground là realm của app demo. Client gateway-api trong realm order-playground mới là client business dùng để lấy token. Client order-playground-realm trong master là client quản trị nội bộ do Keycloak tạo.

5.5 JWT issuer mismatch

Symptom: token lấy từ localhost nhưng gateway trong Docker lại dùng hostname keycloak để fetch JWKS. Nếu dùng sai issuer, token hợp lệ vẫn bị reject. Fix là tách KEYCLOAK_ISSUER_URL cho issuer public và KEYCLOAK_JWKS_BASE_URL cho đường gọi nội bộ trong Docker.

Đây là lỗi làm mình nhớ lâu nhất vì nhìn qua tưởng token sai. Thực ra token đúng, nhưng người kiểm tra token đang kỳ vọng một issuer khác. Trong local Docker, cùng một Keycloak có thể được nhìn bằng hai cái tên: localhost từ máy mình, và keycloak từ container.

5.6 Temporal namespace và activity mismatch

Temporal auto-setup cần thời gian để namespace default sẵn sàng. Nếu worker start quá sớm, gateway nên retry thay vì chết ngay. Một lỗi khác là activity name mismatch: workflow gọi tên activity không khớp với tên worker register, làm activity pending/retry mãi. Temporal UI giúp mình thấy điều này rõ hơn log thường.

Điểm hay của Temporal là lỗi không biến mất sau một dòng log. Nó nằm trong workflow history. Mình có thể mở lại từng event để biết activity nào được scheduled, activity nào started, activity nào failed. Với một người mới học như mình, UI này làm workflow bớt trừu tượng hơn nhiều.

Temporal UI: nhìn được Running, Completed và Failed workflows.

Hình 5. Temporal UI: nhìn được Running, Completed và Failed workflows.

6. Trade-offs

Chủ đềĐiểm tốtTrade-off
Stateless JWTVerify nhanh, gateway không phải gọi auth server mỗi request.Revoke không tức thì nếu token còn hạn.
TemporalCó retry, history và trạng thái workflow rõ.Workflow code cần versioning cẩn thận.
gRPC internalContract rõ, hợp service-to-service.Debug thủ công khó hơn REST nếu thiếu tooling.
Docker Compose localDễ dựng lab và học dependency giữa service.Không phản ánh đầy đủ production.

7. Troubleshooting table

LỗiMở ở đâuKiểm tra gìBài học
401/403Keycloak + gateway logsrealm, client, token, roleAuth là identity + permission.
Order PENDING lâuTemporal UIworkflow history, pending activity, retryWorkflow state không nằm trong HTTP request.
Không thấy auditMongoDBaudit_events theo orderIdAudit trail giúp đọc lại flow.
Không thấy orderMySQLorders, order_items, workflow_runsBusiness state cần source of truth rõ.
Container không lênDocker logsport conflict, healthcheck, startup retryDebug hạ tầng trước khi sửa code.

8. Wrap-up

Sau khi debug playground này, mình thấy Keycloak và Temporal không chỉ là hai màn hình xa lạ nữa. Keycloak giúp mình trả lời ai vào hệ thống và có quyền gì. Temporal giúp mình trả lời order đang kẹt ở bước nào. MySQL và MongoDB giúp mình kiểm chứng state thật thay vì chỉ tin response API.

Học không chỉ dừng lại ở việc backend trả "OK" là xong mà cần phải xác nhận lại xem là kết quả trả ra nó có đúng hay chưa nữa. Khi hiểu được cách debug này sẽ giúp mình giải quyết được rất nhiều race condition trong tương lai