Skip to main content

แกนกลาง 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

  1. request เข้าสู่ gin engine แล้ววิ่งผ่าน middleware ตามลำดับเดียวกับ NestJS เดิม: CLSSecHeadersCORSLoggingErrorsGlobalThrottle
  2. CLS (Continuation Local Storage) สร้าง context ประจำ request สำหรับเก็บ userId, selfId, organizationId, lineOaId, lineOaHash, roleId, isSuperAdmin และ lang ค่าเหล่านี้ถูกเติมโดย JWT guard และทุก service อ่านจากที่นี่เพื่อ scope query ตาม tenant
  3. Errors middleware ทำหน้าที่เทียบเท่า HttpExceptionFilter ของ NestJS คือแปลง error ทุกชนิด เป็น envelope {"code": "...", "message": "...", "data": ...} โดย map status เป็น code ดังนี้
    • 401APP_001
    • 500APP_000
    • validation error ที่เป็น array → APP_006
    • ข้อความที่มีอยู่ในตาราง ErrorResponse → ใช้ key ของข้อความนั้น
    • กรณีอื่น → APP_CUSTOM
  4. Throttle จำกัดอัตราการเรียกทั้งในระดับ global และเฉพาะ endpoint (เช่น reset-password จำกัด 3 ครั้งต่อ 10 วินาที)
  5. route ทั้งหมดถูกจัดกลุ่มใต้ prefix /api แล้วแยกออกเป็นสอง group
    • public — ไม่ผ่าน JWT guard (เทียบเท่า @Public() ของ NestJS)
    • authed — ผ่าน global JWT guard
  6. path ที่ไม่ตรงกับ route ใดจะได้ 404 พร้อมข้อความรูปแบบ Cannot <METHOD> <url> เลียนแบบ Express
  7. เมื่อตอบกลับสำเร็จ จะใช้ helper ใน httpx/success.go เพื่อคงลำดับ key และข้อความ localized ให้ตรงกับของเดิม

ไฟล์และฟังก์ชันหลัก

ไฟล์หน้าที่
cmd/api/main.gobootstrap: โหลด config, สร้าง deps, สร้าง auth module, ประกอบ router, start scheduler และ graceful shutdown
cmd/api/modules.goทะเบียน featureModules ทุกโมดูล (RegisterRoutes) และ registerSchedulers
internal/server/router.goNew() ประกอบ gin engine, middleware chain, group /api และ group public/authed
internal/core/middleware/cls.goสร้าง CLS context ประจำ request
internal/core/middleware/errors.goexception filter แปลง error เป็น JSON error envelope
internal/core/middleware/throttle.goGlobalThrottle() และ Throttle(limit, ttlMs) แบบราย route
internal/core/middleware/cors.go, secheaders.go, logging.goCORS, security headers และ access log
internal/core/httpx/codes.goตาราง ErrorResponse แม็ป error code เป็นข้อความไทย/อังกฤษ
internal/core/httpx/apperror.goAppError พร้อม constructor NotFound, BadRequest, Unauthorized, Forbidden, InternalServerError
internal/core/httpx/success.go, respond.goรูปแบบ success response และ AbortWithError
internal/core/httpx/jstime.goJSTime จำลองการ serialize Date ของ JavaScript ให้ได้ byte ตรงกับ TypeScript
internal/core/pagination/pagination.goโครงสร้าง {data, total} และ {data, total, page, limit, totalPages}
internal/core/validation/validation.govalidate DTO เลียนแบบ class-validator (error เป็น array แล้วแปลงเป็น APP_006)
internal/core/clsctx/clsctx.gogetter/setter ของ CLS เช่น SelfID, OrganizationID, LineOaID, RoleID, IsSuperAdmin
internal/core/logger/logger.gostructured logger ที่ทุก service ใช้ร่วมกัน

Health check: GET /api และ GET /api/ ตอบ 200 พร้อมข้อความ Hello World! โดยมี Content-Type เป็น text/html; charset=utf-8

จุดเชื่อมต่อกับ Service อื่น

  • Permission — ไม่ต้องมีสิทธิ์ใด ๆ เพราะเป็นชั้นล่างสุดที่ทุก feature ใช้ร่วมกัน
  • Dependency containerinternal/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