01Goroutine, Channel และ Mutex

Goroutine คือฟังก์ชันที่ถูก schedule โดย Go runtime บน OS threads จำนวนหนึ่ง การสร้าง goroutine ราคาถูกกว่า thread แต่ไม่ฟรี ทุกตัวใช้ stack, scheduler time และอาจถือ reference จน memory ไม่ถูกเก็บคืน ดังนั้น “สร้าง goroutine ต่อ request ได้” ไม่ได้แปลว่า “สร้างได้ไม่จำกัด”

Channel เหมาะกับการส่ง ownership ของข้อมูลหรือประสาน workflow ระหว่าง goroutine ส่วน Mutex เหมาะกับการปกป้อง shared state ที่มีเจ้าของชัดเจน อย่าเลือก channel เพียงเพราะดูเป็น Go กว่า—ถ้าโจทย์คือ cache map ที่อ่านบ่อยและเขียนน้อย `RWMutex` อาจตรงกว่า

Unbuffered channel

ผู้ส่งและผู้รับต้อง rendezvous กัน เหมาะเมื่ออยากได้ synchronization ที่ชัดเจน

Buffered channel

แยกจังหวะ producer/consumer ได้ชั่วคราว แต่ buffer ไม่ใช่วิธีแก้ consumer ช้าถาวร

Bounded concurrency

ใช้ semaphore หรือ worker pool จำกัดงานพร้อมกัน ป้องกัน DB และ downstream ล่มตาม

02Context คือสัญญาณควบคุมอายุงาน

`context.Context` พา deadline, cancellation และ request-scoped metadata ข้ามขอบเขตฟังก์ชัน เมื่อ client ตัดการเชื่อมต่อหรือ request หมดเวลา งาน DB และ HTTP call ด้านล่างควรหยุดตาม ไม่ใช่ทำต่อจนสิ้นเปลือง resource

  • รับ `ctx` เป็น argument แรก และอย่าเก็บไว้ใน struct ระยะยาว
  • derive จาก parent context เพื่อรักษา cancellation chain
  • เรียก `defer cancel()` ทุกครั้งที่ใช้ `WithTimeout` หรือ `WithCancel`
  • ใช้ value เฉพาะ metadata ที่เดินทางกับ request เช่น correlation ID
handler.go
func GetPolicy(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond)
    defer cancel()

    policy, err := service.FindPolicy(ctx, r.PathValue("id"))
    if err != nil {
        switch {
        case errors.Is(err, context.DeadlineExceeded):
            writeError(w, 504, "upstream timeout")
        case errors.Is(err, domain.ErrNotFound):
            writeError(w, 404, "policy not found")
        default:
            logger.ErrorContext(ctx, "find policy", "err", err)
            writeError(w, 500, "internal error")
        }
        return
    }

    writeJSON(w, 200, policy)
}

03Error handling และ HTTP boundary

ใน Go error เป็น value จึงควรเพิ่มบริบทขณะส่งต่อด้วย `%w` แล้วตรวจด้วย `errors.Is` หรือ `errors.As` แทนการเทียบข้อความ ชั้น service ควรคืน domain error ส่วน HTTP handler เป็นคน map เป็น status code เพื่อไม่ให้ domain ผูกกับ transport

ควรทำ

fmt.Errorf("load policy %s: %w", id, err) รักษา cause และเพิ่มบริบทสำหรับ debug

หลีกเลี่ยง

log error ทุกชั้นจนข้อความเดียวกันซ้ำหลายครั้ง หรือคืนรายละเอียด database ให้ client

04API ที่รับ traffic สูง

เริ่มจากกำหนด timeout budget ทั้ง request แล้วแบ่งให้แต่ละ downstream จากนั้นใช้ connection pool ที่สัมพันธ์กับความสามารถของ DB, จำกัด concurrency, ทำ pagination และรองรับ graceful shutdown เพื่อหยุดรับงานใหม่แต่ให้งานเดิมจบภายใน deadline

สำหรับคำสั่งที่ retry ได้ เช่น “ส่งคำขอซื้อกรมธรรม์” ให้รับ idempotency key แล้วบันทึก key กับผลลัพธ์ใน transaction เดียวกัน หาก request เดิมมาอีกให้คืนผลเดิม ไม่สร้างรายการซ้ำ

อ่านเอกสารทางการ: Go context และ cancellation