เรียนรู้วิธีสร้าง OpenAPI จากพฤติกรรมด้วย Claude Code แล้วเปรียบเทียบกับการใช้งาน API ของคุณ พร้อมตัวอย่างการตรวจสอบฝั่งลูกค้าและเซิร์ฟเวอร์แบบง่ายๆ

สัญญา OpenAPI คือคำอธิบายร่วมของ API ของคุณ: มี endpoint อะไรบ้าง, ส่งอะไรเข้าไป, ได้อะไรกลับมา, และข้อผิดพลาดเป็นอย่างไร มันคือข้อตกลงระหว่างเซิร์ฟเวอร์กับใครก็ตามที่เรียกมัน (เว็บแอป โมบายแอป หรือบริการอื่น)
ปัญหาคือการเบี่ยง (drift) — API ที่รันเปลี่ยนไป แต่สเปกไม่เปลี่ยน หรือสเปกถูก “ทำให้สวย” ให้ดูดีกว่าความเป็นจริง ขณะที่การใช้งานยังคงคืนฟิลด์แปลกๆ หายสถานะโค้ด หรือลักษณะข้อผิดพลาดไม่สอดคล้อง ไปเรื่อยๆ ผู้คนก็หยุดไว้ใจไฟล์ OpenAPI และมันกลายเป็นเอกสารอีกชิ้นที่ทุกคนไม่อ่าน
การเบี่ยงมักมาจากแรงกดดันปกติ: แก้ด่วนแล้วไม่อัปเดตสเปก, เพิ่มฟิลด์ตัวเลือกแบบ "ชั่วคราว", การแบ่งหน้าพัฒนาไป, หรือทีมอัปเดต "แหล่งความจริง" ต่างกัน (โค้ดแบ็กเอนด์, คอลเลกชัน Postman, และไฟล์ OpenAPI)
การรักษาความซื่อสัตย์หมายความว่าสเปกต้องตรงกับพฤติกรรมจริง หาก API บางครั้งคืน 409 สำหรับความขัดแย้ง นั่นควรอยู่ในสัญญา ถ้าฟิลด์ nullable ก็ระบุไว้ หากต้องยืนยันตัวตน ก็อย่าทิ้งให้คลุมเครือ
เวิร์กโฟลว์ที่ดีกลุ่มหนึ่งจะให้คุณได้:
ข้อสุดท้ายสำคัญเพราะสัญญาช่วยได้ก็ต่อเมื่อมันถูกบังคับใช้ สเปกที่ซื่อสัตย์บวกกับการตรวจสอบที่ทำซ้ำได้ จะเปลี่ยน "เอกสาร API" ให้เป็นสิ่งที่ทีมพึ่งพาได้จริงๆ
ถ้าคุณเริ่มจากการอ่านโค้ดหรือคัดเส้นทางมา สเปก OpenAPI ของคุณจะบรรยายสิ่งที่มีวันนี้ รวมทั้งความพิสดารที่คุณอาจไม่อยากสัญญาไว้ แทนที่จะทำแบบนั้น ให้บรรยายว่า API ควรทำอะไรสำหรับผู้เรียก แล้วใช้สเปกเพื่อยืนยันว่าแอปพลิเคชันตรงตาม
ก่อนเขียน YAML หรือ JSON ให้เก็บข้อเท็จจริงเล็กๆ ต่อ endpoint:
จากนั้นเขียนพฤติกรรมเป็นตัวอย่าง ตัวอย่างบังคับให้คุณระบุชัดเจนและช่วยให้ร่างสัญญาที่สอดคล้องง่ายขึ้น
สำหรับ Tasks API เส้นทางปกติอาจเป็น: “สร้างงานด้วย title และได้กลับ id, title, status, และ createdAt.” เพิ่มความล้มเหลวทั่วไป: “ขาด title คืน 400 พร้อม {\"error\":\"title is required\"}” และ “ไม่มี auth คืน 401.” ถ้าคุณรู้กรณีขอบ ให้ใส่: อนุญาตชื่อซ้ำไหม และเกิดอะไรเมื่อ id งานไม่มีอยู่
จับกฎเป็นประโยคสั้นๆ ที่ไม่ขึ้นกับรายละเอียดโค้ด:
title จำเป็นและมีความยาว 1-120 ตัวอักษร.”limit (สูงสุด 200).”dueDate เป็น ISO 8601 date-time.”สุดท้าย ตัดสินขอบเขต v1 ถ้าไม่แน่ใจ ให้ทำ v1 เล็กและชัด (create, read, list, update status). เก็บการค้นหา อัปเดตเป็นจำนวนมาก และฟิลเตอร์ซับซ้อนไว้ทีหลังเพื่อให้สัญญาน่าเชื่อถือ
ก่อนขอให้ Claude Code เขียนสเปก ให้เขียนบันทึกพฤติกรรมในรูปแบบสั้นๆ และทำซ้ำได้ เป้าหมายคือทำให้ยากที่จะ "เติมช่องว่าง" ด้วยการเดา
เทมเพลตที่ดีต้องสั้นพอที่จะใช้งานจริง แต่สอดคล้องพอที่สองคนจะอธิบาย endpoint เดียวกันได้แบบใกล้เคียงกัน เก็บโฟกัสที่สิ่งที่ API ทำ ไม่ใช่วิธีที่มันถูกทำ
ใช้บล็อกหนึ่งบล็อกต่อ endpoint:
METHOD + PATH:
Purpose (1 sentence):
Auth:
Request:
- Query:
- Headers:
- Body example (JSON):
Responses:
- 200 OK example (JSON):
- 4xx example (status + JSON):
Edge cases:
Data types (human terms):
เขียนอย่างน้อยหนึ่งคำขอที่เป็นรูปธรรมและสองการตอบกลับ รวมสถานะโค้ดและตัวอย่าง JSON ที่สมจริง ถ้าฟิลด์เป็นตัวเลือก ให้แสดงตัวอย่างที่มันหายไป
ชี้จุด edge cases ชัดเจน นี่คือที่ที่สเปกเงียบๆ มักจะไม่เป็นจริงต่อไปเพราะทุกคนสมมติคนละอย่าง: ผลลัพธ์ว่าง, ID ไม่ถูกต้อง (400 vs 404), ซ้ำ (409 vs พฤติกรรม idempotent), คำขอไม่ผ่านการตรวจสอบ และขอบเขตการแบ่งหน้า
ยังระบุประเภทข้อมูลด้วยคำง่ายๆ ก่อนคิดถึง schema: string vs number, รูปแบบ date-time, boolean, และ enum (รายการค่าที่อนุญาต) นี่ป้องกันสเปกที่ "สวย" แต่ไม่ตรงกับ payload จริง
Claude Code ทำงานได้ดีเมื่อคุณปฏิบัติต่อมันเหมือนคนจดบันทึกที่ระมัดระวัง ให้บันทึกพฤติกรรมและกฎที่เข้มงวดสำหรับวิธีเขียน OpenAPI ถ้าคุณแค่บอกว่า “เขียนสเปก OpenAPI” มันมักจะเดา ชื่อนามไม่สอดคล้อง และขาดกรณีข้อผิดพลาด
วางบันทึกพฤติกรรมไว้ก่อน แล้วเพิ่มบล็อกคำสั่งเข้มงวด ตัวอย่าง prompt ที่ใช้ได้จริง:
You are generating an OpenAPI 3.1 YAML spec.
Source of truth: the behavior notes below. Do not invent endpoints or fields.
If anything is unclear, list it under ASSUMPTIONS and leave TODO markers in the spec.
Requirements:
- Include: info, servers (placeholder), tags, paths, components/schemas, components/securitySchemes.
- For each operation: operationId, tags, summary, description, parameters, requestBody (when needed), responses.
- Model errors consistently with a reusable Error schema and reference it in 4xx/5xx responses.
- Keep naming consistent: PascalCase schema names, lowerCamelCase fields, stable operationId pattern.
Behavior notes:
[PASTE YOUR NOTES HERE]
Output only the OpenAPI YAML, then a short ASSUMPTIONS list.
หลังได้ร่าง ให้สแกนส่วน ASSUMPTIONS ก่อน นั่นคือที่ที่ความซื่อสัตย์จะชนะหรือแพ้ อนุมัติสิ่งที่ถูกต้อง แก้สิ่งที่ผิด แล้วรันใหม่พร้อมบันทึกที่อัปเดต
เพื่อให้ชื่อนิ่ง ให้ระบุข้อตกลงการตั้งชื่อล่วงหน้าและยึดตาม เช่น pattern ของ operationId, ชื่อ tag ที่เป็นคำนามเท่านั้น, ชื่อ schema แบบเอกพจน์, schema Error เดียวที่ใช้ร่วม, และชื่อ auth scheme เดียวที่ใช้ทุกที่
ถ้าคุณทำงานใน workspace แบบ vibe-coding อย่าง Koder.ai จะช่วยเก็บ YAML เป็นไฟล์จริงตั้งแต่ต้นและ iterate ทีละ diff คุณจะเห็นการเปลี่ยนแปลงมาจากการตัดสินใจพฤติกรรมที่อนุมัติแล้วหรือจากสิ่งที่โมเดลเดา
ก่อนเทียบกับโปรดักชัน ให้แน่ใจว่าไฟล์ OpenAPI ภายในสอดคล้อง นี่คือที่เร็วที่สุดที่จะจับความคิดหวังและคำพูดคลุมเครือ
อ่านแต่ละ endpoint ราวกับว่าคุณเป็นนักพัฒนาไคลเอนต์ โฟกัสที่สิ่งที่ผู้เรียกต้องส่งและสิ่งที่พวกเขาสามารถพึ่งพาได้รับ
การตรวจทานเชิงปฏิบัติ:\n
201 ไม่ใช่ 200) เลือก 400 vs 422 แล้วใช้ให้สม่ำเสมอ\n- Auth: ระบุว่าจำเป็นต่อ endpoint ใดบ้าง และบทบาทมีผลต่อการเข้าถึงหรือไม่\n
ข้อผิดพลาดต้องใส่ใจเป็นพิเศษ เลือกรูปร่างเดียวและใช้ซ้ำทั่ว หากทีมต้องการง่ายมาก ({ error: string }) หรือแบบวัตถุ ({ error: { code, message, details } }) ทั้งสองทำงานได้ แต่อย่าผสมกันใน endpoint ต่างๆ เพราะโค้ดไคลเอนต์จะมีกรณีพิเศษเต็มไปหมด\n
สถานการณ์เชิงตรรกะสั้นๆ ช่วยได้ ถ้า POST /tasks ต้อง title สคีมาควรทำเครื่องหมายว่าจำเป็น การตอบกลับความล้มเหลวควรแสดง body ข้อผิดพลาดที่คุณคืนจริง และ operation ควรกำหนดชัดเจนว่าต้องมี auth ไหมเมื่อสเปกอ่านเหมือนพฤติกรรมที่ตั้งใจ ให้ถือว่า API ที่รันเป็นความจริงของสิ่งที่ไคลเอนต์เจอวันนี้ เป้าหมายไม่ใช่ "ชนะ" ระหว่างสเปกกับโค้ด แต่เพื่อค้นหาความต่างตั้งแต่ต้นและตัดสินใจชัดเจนในแต่ละจุด
สำหรับการผ่านแรก ตัวอย่างคำขอ/การตอบกลับจริงมักง่ายที่สุด บันทึกและเทสต์อัตโนมัติถ้ามั่นใจได้ก็ใช้ได้เช่นกัน
มองหาความไม่ตรงกันทั่วไป: endpoint ที่อยู่ในที่หนึ่งแต่ไม่อยู่ในอีกที่, ชื่อฟิลด์หรือรูปร่างต่างกัน, รหัสสถานะต่างกัน (200 vs 201, 400 vs 422), พฤติกรรมที่ไม่มีในเอกสาร (pagination, sorting, filtering), และความต่างเรื่อง auth (สเปกบอกสาธารณะแต่โค้ดต้องใช้โทเค็น)
ตัวอย่าง: สเปกบอกว่า POST /tasks คืน 201 พร้อม {id,title} คุณเรียก API แล้วได้ 200 พร้อม {id,title,createdAt} นั่นไม่ใช่ "ใกล้เคียง" หากคุณสร้าง SDK อัตโนมัติจากสเปก
ก่อนแก้ไขอะไร ให้ตัดสินใจวิธีแก้ความขัดแย้ง:\n
เมื่อมีสเปกที่วางใจได้ ให้แปลงมันเป็นตัวอย่างการตรวจสอบเล็กๆ นี่คือสิ่งที่หยุด drift ไม่ให้กลับมา
ฝั่งเซิร์ฟเวอร์คือการล้มเหลวอย่างรวดเร็วเมื่อคำขอไม่ตรงสัญญา และคืนข้อผิดพลาดชัดเจน นั่นช่วยปกป้องข้อมูลและทำให้บั๊กหาง่ายขึ้น
วิธีง่ายๆ ในการแสดงตัวอย่างการตรวจสอบฝั่งเซิร์ฟเวอร์คือตั้งเป็นเคสที่มีสามส่วน: input, expected output, และ expected error (รหัสหรือรูปแบบข้อความ ไม่ใช่ข้อความเป๊ะๆ)
ตัวอย่าง (สัญญาบอกว่า title จำเป็นและมีความยาว 1 ถึง 120 ตัวอักษร):
{
"name": "Create task without title returns 400",
"request": {"method": "POST", "path": "/tasks", "body": {"title": ""}},
"expect": {"status": 400, "body": {"error": {"code": "VALIDATION_ERROR"}}}
}
ฝั่งไคลเอนต์คือการตรวจจับ drift ก่อนผู้ใช้ หากเซิร์ฟเวอร์เริ่มคืนรูปร่างต่างไป หรือฟิลด์จำเป็นหาย เทสต์ของคุณควรแจ้งเตือน
เก็บเช็กฝั่งลูกค้าโฟกัสที่สิ่งที่คุณพึ่งพาจริง เช่น “งานมี id, title, status.” หลีกเลี่ยงการยืนยันทุกฟิลด์ตัวเลือกหรือการเรียงลำดับที่แน่นอน คุณอยากได้ล้มเมื่อมีการเปลี่ยนแปลงที่ทำให้พัง ไม่ใช่เมื่อมีฟิลด์เสริมที่ไม่เป็นอันตราย
แนวทางที่ทำให้เทสต์อ่านง่าย:\n
นึกภาพ API เล็กๆ มีสาม endpoint: POST /tasks สร้างงาน, GET /tasks แสดงรายการ, และ GET /tasks/{id} คืนงานหนึ่งรายการ
เริ่มจากเขียนตัวอย่างคอนกรีตสำหรับหนึ่ง endpoint เหมือนอธิบายให้ผู้ทดสอบฟัง
สำหรับ POST /tasks พฤติกรรมที่ตั้งใจอาจเป็น:
{ "title": "Buy milk" } แล้วได้ 201 พร้อมวัตถุ task ใหม่ รวม id, title, และ done:false\n- ล้มเหลว 1: ส่ง {} แล้วได้ 400 พร้อมข้อผิดพลาดแบบ { "error": "title is required" }\n- ล้มเหลว 2: ส่ง { "title": "x" } (สั้นเกินไป) แล้วได้ 422 กับ { "error": "title must be at least 3 characters" }\n
เมื่อ Claude Code ร่าง OpenAPI ส่วนนี้ ควรจับ schema, รหัสสถานะ, และตัวอย่างที่สมจริง:paths:
/tasks:
post:
summary: Create a task
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTaskRequest'
examples:
ok:
value: { "title": "Buy milk" }
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
examples:
created:
value: { "id": "t_123", "title": "Buy milk", "done": false }
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
missingTitle:
value: { "error": "title is required" }
'422':
description: Unprocessable Entity
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
tooShort:
value: { "error": "title must be at least 3 characters" }
ความไม่ตรงกันที่พบบ่อยมักละเอียด: API ที่รันคืน 200 แทนที่จะเป็น 201, หรือคืน { "taskId": 123 } แทน { "id": "t_123" } นั่นคือความต่างแบบ “เกือบเหมือน” ที่ทำให้ไคลเอนต์ที่สร้างอัตโนมัติพัง
แก้โดยเลือกความจริงแหล่งเดียว ถ้าพฤติกรรมที่ตั้งใจถูกต้อง ให้เปลี่ยนการใช้งานให้คืน 201 และรูปแบบ Task ที่ตกลงไว้ ถ้าการใช้งานในโปรดักชันถูกพึ่งพาอยู่แล้ว ให้อัปเดตสเปก (และบันทึกพฤติกรรม) ให้ตรงกับความเป็นจริง แล้วเพิ่มการตรวจสอบและการตอบข้อผิดพลาดที่ขาดไป
สัญญาจะไม่ซื่อสัตย์เมื่อมันหยุดอธิบายกฎ และเริ่มอธิบายสิ่งที่ API คืนในวันดีๆ วันเดียว ทดสอบง่ายๆ: implementation ใหม่สามารถผ่านสเปกนี้โดยไม่ต้องคัดลอกความพิสดารของวันนี้หรือไม่?
กับดักหนึ่งคือ overfitting คุณจับการตอบกลับเดียวแล้วทำให้เป็นกฎ ตัวอย่าง: API ปัจจุบันคืน dueDate: null สำหรับทุกงาน ดังนั้นสเปกบอกว่าฟิลด์ nullable แต่กฎจริงอาจเป็น “จำเป็นเมื่อ status เป็น scheduled.” สัญญาควรแสดงกฎ ไม่ใช่แค่ dataset ปัจจุบัน
ข้อผิดพลาดคือส่วนที่ซื่อสัตย์มักพัง มักอยากสเปกเฉพาะการตอบกลับสำเร็จเพราะดูสะอาด แต่ไคลเอนต์ต้องการพื้นฐาน: 401 เมื่อโทเค็นหาย, 403 เมื่อไม่ได้สิทธิ์, 404 สำหรับ ID ไม่รู้จัก, และข้อผิดพลาดการตรวจสอบที่สม่ำเสมอ (400 หรือ 422) อย่าละเลย
รูปแบบอื่นที่ก่อปัญหา:\n
taskId ที่ route หนึ่งแต่ id ในอีกที่หนึ่ง, หรือ priority เป็น string ที่ตอบกลับหนึ่งและเป็น number อีกที่)\n- ตัวอย่างขัดแย้งกับสคีมา (enum ไม่ตรง, ตัวอย่าง date-time ไม่ใช่ ISO 8601)\n- ชนิดถูกขยายกว้างเพื่อเลี่ยงการตัดสินใจ (ทุกอย่างกลายเป็น string, ทุกอย่างเป็น optional)\n- สเปกอ่านเหมือนคัดลอกการตลาด (“เร็ว”, “ปลอดภัย”) แทนที่จะเป็นสัญญาที่ทดสอบได้สัญญาที่ดีต้องทดสอบได้ ถ้าคุณเขียนเทสต์ที่ล้มจากสเปกไม่ได้ แปลว่ายังไม่ซื่อสัตย์พอ
ก่อนส่งไฟล์ OpenAPI ให้ทีมอื่น (หรือวางในเอกสาร) ให้ทำผ่านด่วนว่า “คนอื่นใช้ได้โดยไม่ต้องอ่านหัวใจฉันไหม?”
เริ่มจากตัวอย่าง สเปกอาจถูกต้องแต่ยังไร้ประโยชน์ถ้าคำขอและการตอบกลับทุกอันเป็นนามธรรม สำหรับแต่ละ operation ให้มีอย่างน้อยหนึ่งตัวอย่างคำขอจริงและหนึ่งตัวอย่างการตอบกลับสำเร็จ สำหรับข้อผิดพลาด ให้มีตัวอย่างหนึ่งตัวอย่างต่อความล้มเหลวยอดนิยม (auth, validation)
แล้วตรวจความสอดคล้อง หาก endpoint หนึ่งคืน { "error": "..." } และอีก endpoint คืน { "message": "..." } ไคลเอนต์จะมีโลจิกแยกทางมากเกินไป เลือกรูปร่างข้อผิดพลาดเดียวและใช้ซ้ำ พร้อมรหัสสถานะที่คาดเดาได้
เช็คลิสต์สั้นๆ:\n
ทริคปฏิบัติ: เลือก endpoint หนึ่ง แกล้งเป็นคนไม่เคยเห็น API แล้วตอบว่า “ฉันส่งอะไร ได้อะไรกลับ และอะไรพัง?” ถ้า OpenAPI ตอบไม่ได้ แปลว่ายังไม่พร้อม
เวิร์กโฟลว์นี้ให้ผลเมื่อทำเป็นประจำ ไม่ใช่แค่ตอน release ให้วิธีง่ายๆ และยึดถือมัน: รันเมื่อ endpoint เปลี่ยน และรันอีกครั้งก่อนเผยแพร่สเปกที่อัปเดต
เก็บความเป็นเจ้าของให้ง่าย คนที่เปลี่ยน endpoint เป็นผู้แก้บันทึกพฤติกรรมและร่างสเปก ผู้ตรวจทานอีกคนหนึ่งรีวิว diff ระหว่างสเปกกับการใช้งานเหมือนการรีวิวโค้ด QA หรือทีมซัพพอร์ตมักเป็นผู้ตรวจที่ดีเพราะเห็นการตอบกลับที่ไม่ชัดเจนและกรณีขอบได้เร็ว
ปฏิบัติต่อการแก้สัญญาเหมือนแก้โค้ด ถ้าคุณใช้ตัวสร้างที่ขับด้วยแชทอย่าง Koder.ai ให้ถ่าย snapshot ก่อนแก้เสี่ยงและใช้ rollback หากจำเป็น Koder.ai ยังรองรับการส่งออกซอร์ส ซึ่งช่วยให้เก็บสเปกและการใช้งานให้อยู่ด้วยกันในรีโปได้ง่ายขึ้น
กิจวัตรที่มักได้ผลโดยไม่ทำให้ทีมช้าลง:\n
OpenAPI drift คือกรณีที่ API ที่รันจริงไม่ตรงกับไฟล์ OpenAPI ที่แชร์กัน Spec อาจขาดฟิลด์ใหม่ๆ รหัสสถานะ (status codes) หรือกฎการยืนยันตัวตน (auth) หรืออาจอธิบายพฤติกรรม "ในอุดมคติ" ที่เซิร์ฟเวอร์ไม่ได้ทำตาม\n\nเรื่องนี้สำคัญเพราะไคลเอนต์ (แอป บริการอื่น SDK ที่สร้างอัตโนมัติ หรือเทสต์) ตัดสินใจตามสัญญา ไม่ใช่ตามสิ่งที่เซิร์ฟเวอร์ของคุณ "มักจะ" ทำ
การแตกต่างจะปรากฏในการใช้งานจริงอย่างสุ่มและยากต่อการดีบัก: แอปมือถือคาดหวัง 201 แต่ได้ 200, SDK ไม่สามารถ deserialize ตอบกลับเพราะฟิลด์ถูกเปลี่ยนชื่อ, หรือการจัดการข้อผิดพลาดล้มเหลวเพราะรูปแบบข้อผิดพลาดไม่ตรงกัน\n\nแม้เมื่อไม่มีการชน แอปทีมก็จะเริ่มไม่ไว้ใจสเปก และหยุดใช้งานไฟล์นั้น ซึ่งทำให้ระบบเตือนล่วงหน้าหายไป
เพราะโค้ดสะท้อนพฤติกรรมปัจจุบัน รวมทั้งความผิดพลาดหรือรายละเอียดเล็กๆ ที่คุณอาจไม่อยากสัญญาต่อไป\n\nวิธีที่ดีกว่า: เขียนพฤติกรรมที่ต้องการก่อน (อินพุต เอาต์พุต ข้อผิดพลาด) แล้วตรวจสอบให้การใช้งานตรงตามสเปก แบบนี้จะได้สัญญาที่คุณบังคับใช้ได้ แทนที่จะเป็นภาพชั่วขณะของ routes วันนี้
สำหรับแต่ละ endpoint ให้เก็บ:\n\n- Purpose: ทำอะไรในหนึ่งประโยค\n- Auth: ต้องใช้โทเค็น/บทบาทหรือสาธารณะ\n- Request: query/path params, headers, และตัวอย่าง body เป็น JSON\n- Responses: อย่างน้อยหนึ่งตัวอย่างความสำเร็จและหนึ่งหรือสองตัวอย่างข้อผิดพลาดจริงที่มีรหัสสถานะ\n- Edge cases: ID หาย/ไม่ถูกต้อง ซ้ำ (409?) ผลลัพธ์ว่าง ขอบเขตการแบ่งหน้า\n\nถ้าคุณเขียนคำร้องขอที่ชัดเจนและสองตัวอย่างตอบกลับ คุณมักมีข้อมูลพอที่จะร่างสเปกที่เป็นความจริงได้
เลือกหนึ่งรูปแบบของ body ข้อผิดพลาดและนำมาใช้ซ้ำทั่วทั้งสเปก\n\nค่าพื้นฐานที่เรียบง่ายหนึ่งในสองแบบคือ:\n\n- { "error": "message" } หรือ\n- { "error": { "code": "...", "message": "...", "details": ... } }\n\nจากนั้นใช้แบบเดียวกันในทุก endpoint และในตัวอย่าง ความสม่ำเสมอสำคัญกว่าความซับซ้อนเพราะไคลเอนต์มักโค้ดรูปแบบนี้ไว้
ให้ Claude Code ได้รับบันทึกพฤติกรรมของคุณและกฎเข้มงวด และบอกให้มันอย่าคิดขึ้นเอง แนวทางปฏิบัติที่ใช้ได้จริง:\n\n- “Source of truth is the behavior notes. Do not guess.”\n- “If unclear, add TODO in the spec and list it under ASSUMPTIONS.”\n- “Include reusable schemas (like Error) and reference them.”\n- “Use consistent naming (PascalCase schemas, lowerCamelCase fields).”\n\nหลังการสร้าง ให้ตรวจสอบรายการ ASSUMPTIONS ก่อน นั่นคือที่ที่ความซื่อสัตย์อาจพังถ้าคุณยอมรับการเดา
ตรวจสอบสเปกก่อนเทียบกับโปรดักชัน:\n\n- ฟิลด์ที่จำเป็น vs ที่ไม่จำเป็น ถูกต้องหรือไม่\n- รูปแบบระบุชัดเจน (UUID, email, ISO 8601 date-time)\n- ตัวอย่างสอดคล้องกับ schemas (enums, types, nullability)\n- รหัสสถานะตั้งใจและสอดคล้อง (เช่น create มักเป็น 201)\n- ข้อกำหนดการยืนยันตัวตนระบุไว้ต่อ endpoint\n\nการตรวจสอบเหล่านี้จะจับความหวังลมๆ แล้งๆ ใน OpenAPI ก่อนที่คุณจะเทียบกับการทำงานจริง
มองการใช้งานจริงเป็นสิ่งที่ผู้ใช้เจอวันนี้ แล้วตัดสินแต่ละความขัดแย้ง:\n\n- ถ้าพฤติกรรมถูกแต่ไม่มีในเอกสาร: อัปเดตสเปก\n- ถ้าสเปกเป็นสัญญาที่ตกลงกันไว้: แก้โค้ดให้ตรง\n- ถ้าไม่มีอันไหนถูก: เปลี่ยนพฤติกรรมที่ต้องการก่อน, แล้วอัปเดตทั้งสองฝั่ง\n\nเก็บการเปลี่ยนแปลงให้เล็ก (หนึ่ง endpoint หรือการปรับ schema ครั้งละหนึ่งอย่าง) เพื่อทดสอบซ้ำได้ง่าย
ฝั่งเซิร์ฟเวอร์: ปฏิเสธคำขอที่ละเมิดสัญญาและส่งข้อผิดพลาดที่ชัดเจนและสม่ำเสมอ (status + error code/shape)\n\nฝั่งไคลเอนต์: ตรวจจับการเปลี่ยนรูปแบบการตอบกลับก่อนผู้ใช้จะพบ โดยยืนยันเฉพาะสิ่งที่คุณพึ่งพาจริงๆ:\n\n- ฟิลด์ที่จำเป็นมีอยู่และชนิดถูกต้อง\n- รหัสสถานะตรงตามโฟลว์ที่คาดไว้\n- ข้อผิดพลาดมีรูปแบบที่ตกลงกัน\n\nหลีกเลี่ยงการยืนยันทุกฟิลด์ที่เป็นตัวเลือกเพื่อให้เทสต์ล้มเมื่อมีการเปลี่ยนแปลงที่ทำให้เกิดปัญหา ไม่ใช่เพราะเพิ่มฟิลด์ใหม่ที่ไม่เป็นอันตราย
กิจวัตรที่ใช้ได้จริงคือ:\n\n- อัปเดตบันทึกพฤติกรรมและสเปกเมื่อเพิ่ม endpoint ใหม่\n- ตรวจสอบ OpenAPI (schemas, ตัวอย่าง, รหัสสถานะ, auth) ก่อน merge\n- เปรียบเทียบตัวอย่าง request/response จริงกับสเปกก่อน release\n- เก็บตัวอย่างการตรวจสอบเล็กๆ ในเทสต์เพื่อจับ drift อัตโนมัติ\n\nถ้าคุณใช้ Koder.ai คุณสามารถเก็บไฟล์ OpenAPI ไว้กับโค้ด ใช้ snapshot ก่อนแก้เสี่ยง และ rollback ได้เมื่อจำเป็น