แกนกลาง HTTP และ Middleware Pipeline
ภาพรวม
ชั้นแกนกลาง (core) ของ cms-api-go เป็นตัวกำหนดสองสิ่งที่ทุกโมดูลในระบบต้องยึดร่วมกัน คือ รูปร่างของ response ทุกตัว และ ลำดับ middleware ของทุก request
โมดูล feature ไม่เขียน JSON ตอบกลับเอง แต่เรียกผ่าน helper กลางใน internal/core/httpx
ผลคือ endpoint กว่า 280 ตัวทั้ง service ใช้ envelope เดียวกัน มีชุด error code เดียวกัน (APP_xxx)
และรองรับข้อความสองภาษา (ไทย/อังกฤษ) โดยอัตโนมัติ
service นี้ถูก port มาจาก NestJS เดิมแบบหนึ่งต่อหนึ่งในระดับ method จึงตั้งใจรักษาพฤติกรรมเดิมไว้ครบถ้วน รวมถึงพฤติกรรมที่เพี้ยนบางจุดของโค้ด TypeScript ซึ่งมีคอมเมนต์กำกับไว้ในโค้ดว่าไม่ควรแก้ เพื่อไม่ให้ cms-web ที่พึ่งพารูปแบบ response เดิมทำงานผิดพลาด
Business Flow
- request เข้าสู่ gin engine แล้ววิ่งผ่าน middleware ตามลำดับเดียวกับ NestJS เดิม:
CLS→SecHeaders→CORS→Logging→Errors→GlobalThrottle - CLS (Continuation Local Storage) สร้าง context ประจำ request สำหรับเก็บ
userId,selfId,organizationId,lineOaId,lineOaHash,roleId,isSuperAdminและlangค่าเหล่านี้ถูกเติมโดย JWT guard และทุก service อ่านจากที่นี่เพื่อ scope query ตาม tenant - Errors middleware ทำหน้าที่เทียบเท่า
HttpExceptionFilterของ NestJS คือแปลง error ทุกชนิด เป็น envelope{"code": "...", "message": "...", "data": ...}โดย map status เป็น code ดังนี้401→APP_001500→APP_000- validation error ที่เป็น array →
APP_006 - ข้อความที่มีอยู่ในตาราง
ErrorResponse→ ใช้ key ของข้อความนั้น - กรณีอื่น →
APP_CUSTOM
- Throttle จำกัดอัตราการเรียกทั้งในระดับ global และเฉพาะ endpoint (เช่น reset-password จำกัด 3 ครั้งต่อ 10 วินาที)
- route ทั้งหมดถูกจัดกลุ่มใต้ prefix
/apiแล้วแยกออกเป็นสอง grouppublic— ไม่ผ่าน JWT guard (เทียบเท่า@Public()ของ NestJS)authed— ผ่าน global JWT guard
- path ที่ไม่ตรงกับ route ใดจะได้ 404 พร้อมข้อความรูปแบบ
Cannot <METHOD> <url>เลียนแบบ Express - เมื่อตอบกลับสำเร็จ จะใช้ helper ใน
httpx/success.goเพื่อคงลำดับ key และข้อความ localized ให้ตรงกับของเดิม
ไฟล์และฟังก์ชันหลัก
| ไฟล์ | หน้าที่ |
|---|---|
cmd/api/main.go | bootstrap: โหลด config, สร้าง deps, สร้าง auth module, ประกอบ router, start scheduler และ graceful shutdown |
cmd/api/modules.go | ทะเบียน featureModules ทุกโมดูล (RegisterRoutes) และ registerSchedulers |
internal/server/router.go | New() ประกอบ gin engine, middleware chain, group /api และ group public/authed |
internal/core/middleware/cls.go | สร้าง CLS context ประจำ request |
internal/core/middleware/errors.go | exception filter แปลง error เป็น JSON error envelope |
internal/core/middleware/throttle.go | GlobalThrottle() และ Throttle(limit, ttlMs) แบบราย route |
internal/core/middleware/cors.go, secheaders.go, logging.go | CORS, security headers และ access log |
internal/core/httpx/codes.go | ตาราง ErrorResponse แม็ป error code เป็นข้อความไทย/อังกฤษ |
internal/core/httpx/apperror.go | AppError พร้อม constructor NotFound, BadRequest, Unauthorized, Forbidden, InternalServerError |
internal/core/httpx/success.go, respond.go | รูปแบบ success response และ AbortWithError |
internal/core/httpx/jstime.go | JSTime จำลองการ serialize Date ของ JavaScript ให้ได้ byte ตรงกับ TypeScript |
internal/core/pagination/pagination.go | โครงสร้าง {data, total} และ {data, total, page, limit, totalPages} |
internal/core/validation/validation.go | validate DTO เลียนแบบ class-validator (error เป็น array แล้วแปลงเป็น APP_006) |
internal/core/clsctx/clsctx.go | getter/setter ของ CLS เช่น SelfID, OrganizationID, LineOaID, RoleID, IsSuperAdmin |
internal/core/logger/logger.go | structured logger ที่ทุก service ใช้ร่วมกัน |
Health check: GET /api และ GET /api/ ตอบ 200 พร้อมข้อความ Hello World!
โดยมี Content-Type เป็น text/html; charset=utf-8
จุดเชื่อมต่อกับ Service อื่น
- Permission — ไม่ต้องมีสิทธิ์ใด ๆ เพราะเป็นชั้นล่างสุดที่ทุก feature ใช้ร่วมกัน
- Dependency container —
internal/app/deps.go(Deps) รวม dependency ที่ inject ให้ทุกโมดูล ได้แก่ Postgres (GORM), Redis, RabbitMQ publisher, Storage (S3/MinIO), LINE API, Redirects, Mailer, CSVEngine (DuckDB แบบ pure-Go), BigQuery และ Scheduler - พฤติกรรมตอน boot — Postgres เชื่อมต่อไม่ได้ถือเป็น fatal (retry 10 ครั้ง ห่างกัน 3 วินาที แล้วหยุดทำงาน) ส่วน Redis และ RabbitMQ จะ degrade แบบเงียบโดยไม่ block การ boot