ข้อผิดพลาดส่วนใหญ่ของ API ไม่ใช่ความผิดพลาดในการเขียนโค้ด แต่เป็นความผิดพลาดในการตกลงกัน Frontend คาดหวัง userId ในขณะที่ Backend ส่ง user_id และไม่มีใครสังเกตเห็นจนกว่าจะถึงขั้นตอน QA การพัฒนา API แบบ Spec-first แก้ปัญหานี้โดยการทำให้สัญญา (contract) เป็นสิ่งแรกที่คุณเขียน ไม่ใช่สิ่งสุดท้ายที่คุณจะจัดทำเอกสาร
ในคู่มือนี้ คุณจะได้เขียน OpenAPI spec ขนาดเล็กด้วยตนเอง จากนั้นใช้ไฟล์เดียวนี้เพื่อสร้าง mock, tests และ docs ก่อนที่จะมีโค้ดเซิร์ฟเวอร์ใดๆ วิธีการเดียวกันนี้มีหลายชื่อเรียก: spec-driven development, design-first หรือ contract-first ทั้งหมดนี้ชี้ไปที่แนวคิดเดียวกัน คือ ตกลงกันในส่วนเชื่อมต่อ (interface) ก่อน จากนั้นจึงสร้างตามนั้น
ปุ่ม
การพัฒนา API แบบ Spec-first คืออะไร
การพัฒนา API แบบ Spec-first หมายถึงการที่คุณสร้างสัญญาที่เครื่องอ่านได้ (machine-readable contract) ซึ่งโดยปกติคือเอกสาร OpenAPI ก่อนที่คุณจะนำ endpoint ไปใช้งาน สัญญานั้นจะอธิบายทุก path, parameter, request body, response shape และ status code เมื่อมีอยู่แล้ว มันจะกลายเป็นแหล่งความจริงสำหรับทุกคนที่เกี่ยวข้องกับ API
Spec ไม่ใช่เอกสารที่ตามหลังโค้ด แต่เป็นผู้นำ ทีม Frontend สร้างจาก mock ที่สร้างขึ้นจาก spec ทีม QA เขียน test โดยอ้างอิงจาก spec ส่วน Backend จะนำไปใช้งานให้เป็นไปตาม spec เมื่อทั้งสามฝ่ายตกลงกันด้วยไฟล์เดียวกัน การรวมระบบ (integration) ก็จะไม่ใช่การคาดเดาอีกต่อไป
นี่เป็นการพลิกกลับลำดับปกติ ในการพัฒนาแบบ code-first คุณจะเขียน handler แล้วจึงอธิบายในภายหลัง ซึ่งมักจะทำด้วยมือ และมักจะล้าสมัยภายในหนึ่งสปรินต์ ในเวิร์กโฟลว์แบบ design-first คำอธิบายจะมาก่อน และโค้ดจะตอบสนองต่อคำอธิบายนั้น การเปลี่ยนแปลงเพียงครั้งเดียวนั้นจะย้ายปัญหาการรวมระบบส่วนใหญ่ไปที่จุดเริ่มต้นของโปรเจกต์ ซึ่งแก้ไขได้ง่ายและประหยัดค่าใช้จ่าย
วงจรชีวิตแบบ Spec-first เทียบกับ Code-first
ทั้งสองแนวทางสร้าง endpoint เดียวกัน ความแตกต่างอยู่ที่เมื่อปัญหาปรากฏขึ้นและใครสามารถเริ่มทำงานพร้อมกันได้

คอลัมน์ด้านขวาคือผลลัพธ์ เมื่อสัญญา (contract) มีอยู่ก่อน ทีมทั้งสามก็หยุดการกีดขวางซึ่งกันและกันและเริ่มสร้างโดยอิงตามคำจำกัดความที่ใช้ร่วมกัน
ตัวอย่าง OpenAPI ที่ใช้งานได้จริง
มาออกแบบ `/users` endpoint ขนาดเล็กทีละขั้นตอนกัน เราจะสร้างมันเป็นส่วนๆ เพื่อให้แต่ละส่วนชัดเจน จากนั้นจึงประกอบเป็นไฟล์ที่สมบูรณ์
เริ่มต้นด้วยส่วนหัวของเอกสาร สิ่งนี้จะประกาศเวอร์ชัน OpenAPI และข้อมูลเมตาพื้นฐาน
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
servers:
- url: https://api.example.com/v1
ถัดไป กำหนด `User` schema ภายใต้ `components` การกำหนดเพียงครั้งเดียวช่วยให้คุณอ้างอิงได้ทุกที่ ทำให้โครงสร้างสอดคล้องกันตลอดทั้งคำขอและการตอบกลับ
components:
schemas:
User:
type: object
required: [id, email, createdAt]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
ตอนนี้เพิ่ม `GET /users` path ซึ่งแสดงรายการผู้ใช้และรองรับพารามิเตอร์คิวรี `limit` สังเกตว่าการตอบกลับนำ `User` schema กลับมาใช้ซ้ำด้วย `$ref` แทนที่จะกำหนดใหม่
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
เพิ่มการดำเนินการ `POST /users` เพื่อสร้างผู้ใช้ request body กำหนดสิ่งที่ไคลเอ็นต์ต้องส่ง และการตอบกลับ `201` จะคืนค่าบันทึกที่สร้างขึ้น
post:
summary: Create a user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email:
type: string
format: email
name:
type: string
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Invalid request body
นั่นคือสัญญาที่สมบูรณ์และถูกต้อง มันระบุชื่อทุกฟิลด์ กำหนดให้ `email` เป็นสิ่งที่จำเป็น กำหนด `limit` สูงสุดที่ 100 และประกาศการตอบกลับทั้งที่สำเร็จและเกิดข้อผิดพลาด ยังไม่มีใครเขียนโค้ดเซิร์ฟเวอร์แม้แต่บรรทัดเดียว แต่ข้อตกลงได้ถูกกำหนดไว้แล้ว
สร้าง mocks, tests และ docs จาก spec
เหตุผลที่เขียน spec ก่อนคือเพื่อใช้ประโยชน์ ไฟล์เดียวขับเคลื่อนสิ่งประดิษฐ์สามอย่างที่ทีมมักจะสร้างแยกกัน
Mocks. Mock server จะอ่าน spec ของคุณและส่งคืนการตอบกลับตัวอย่างที่ตรงกับทุก schema คำใบ้ `format: uuid` และ `format: email` ช่วยให้เครื่องมือสร้างข้อมูลตัวอย่างที่สมจริงได้ Frontend ของคุณเรียก `GET /users` และได้รับอาร์เรย์ผู้ใช้ที่จัดรูปแบบอย่างดีตั้งแต่วันแรก ก่อนที่ handler จริงจะพร้อมใช้งานนาน เมื่อ spec เปลี่ยน mock ก็จะเปลี่ยนตามไปด้วย
Tests. เนื่องจาก spec ประกาศฟิลด์ที่จำเป็น, ประเภทข้อมูล และรหัสสถานะ จึงทำหน้าที่เป็น test oracle ด้วย Contract tests ยืนยันว่าการตอบกลับจริงสำหรับ `POST /users` ส่งคืน `201` พร้อม body ที่ตรงกับ `User` schema และ `400` เมื่อ `email` หายไป คุณไม่ได้สร้าง assertion ขึ้นมาเอง คุณกำลังตรวจสอบว่าการใช้งานตรงกับสิ่งที่คุณตกลงไว้แล้ว
Docs. เอกสารอ้างอิง API จะถูกเรนเดอร์โดยตรงจาก spec ทุก endpoint, parameter และตัวอย่างที่คุณเห็นในเอกสารมาจากไฟล์ YAML เดียวกัน ไม่มีการคัดลอกสำเนาที่สองเพื่อให้ข้อมูลตรงกัน ดังนั้นเอกสารจึงไม่สามารถคลาดเคลื่อนไปจากสัญญาได้
นี่คือสิ่งที่ทำให้ spec-first เข้ากันได้ดีกับ เวิร์กโฟลว์ API ที่เป็น Git-native: spec เป็นไฟล์ข้อความธรรมดา ดังนั้นการเปลี่ยนแปลงใดๆ ในสัญญาจะปรากฏเป็นการเปรียบเทียบ (diff) ที่สามารถตรวจสอบได้ใน pull request ผู้ตรวจสอบสามารถเห็นได้ว่ามีคนเปลี่ยนชื่อ `email` หรือลบฟิลด์ที่จำเป็นออกไปก่อนที่จะนำไปใช้งานจริง
ทำได้ใน Apidog
Apidog รองรับสิ่งนี้แบบครบวงจรผ่าน โหมด Spec-First แทนที่จะถือว่าไฟล์ OpenAPI เป็นเพียงไฟล์ส่งออก มันถือว่าไฟล์นี้คือโปรเจกต์ คุณแก้ไข YAML โดยตรง และส่วนที่เหลือของ workspace ก็จะอัปเดตตามไป

คุณเขียนหรือวาง `/users` spec ลงใน editor Apidog จะแยกวิเคราะห์และสร้าง mock server สำหรับการดำเนินการทั้งสองทันที เพื่อให้ frontend ของคุณสามารถเริ่มเรียกใช้งานได้ เอกสารที่สร้างขึ้นจะอัปเดตตามที่คุณพิมพ์ เมื่อคุณพร้อมที่จะตรวจสอบการใช้งาน คุณสามารถเรียกใช้การดำเนินการของ spec เป็น test case กับ backend จริงของคุณและยืนยันว่าการตอบกลับตรงกับ schemas
ส่วนที่ทำให้สิ่งนี้ซื่อสัตย์คือการซิงค์ Git แบบสองทาง Spec ของคุณอยู่ใน repository และการเปลี่ยนแปลงจะไหลไปในทั้งสองทิศทาง แก้ไข YAML ใน editor ของคุณแล้ว push, Apidog จะรับไป แก้ไขใน Apidog และการเปลี่ยนแปลงจะปรากฏเป็น commit ที่ทีมของคุณสามารถตรวจสอบได้ สัญญาจะไม่เคยอยู่ในสองที่พร้อมกัน หากคุณต้องการเปรียบเทียบเชิงลึกว่าสิ่งนี้แตกต่างจากแนวทาง design-first แบบบริสุทธิ์อย่างไร โปรดดู spec-first vs design-first ใน Apidog
รายการตรวจสอบ Spec-first
ใช้สิ่งนี้ก่อนที่คุณจะเรียก spec ว่าพร้อมที่จะนำไปสร้าง:
- Spec ได้รับการตรวจสอบตาม OpenAPI schema โดยไม่มีข้อผิดพลาด
- ทุก endpoint ประกาศการตอบกลับที่สำเร็จและอย่างน้อยหนึ่งการตอบกลับที่ผิดพลาด
- รูปแบบที่ใช้ร่วมกันอยู่ใน
components/schemasและถูกอ้างอิงด้วย$refไม่ใช่การคัดลอก - ฟิลด์ที่จำเป็นถูกทำเครื่องหมาย
requiredและกำหนดรูปแบบ (uuid,email,date-time) ในส่วนที่เกี่ยวข้อง - ไฟล์ spec ถูก commit เข้าสู่ระบบควบคุมเวอร์ชันและได้รับการตรวจสอบใน pull request
- Mock server ทำงานจาก spec และ frontend สามารถเรียกใช้งานได้
- Contract tests ตรวจสอบการตอบกลับจริงเทียบกับ schemas ที่ประกาศไว้
- เอกสารที่เผยแพร่ถูกเรนเดอร์จากไฟล์เดียวกัน โดยไม่มีการคัดลอกที่ต้องดูแลด้วยตนเอง
หากตรวจสอบครบทุกข้อ ทีมของคุณก็สามารถสร้างพร้อมกันได้โดยอิงจากข้อตกลงเดียว แทนที่จะต้องคาดเดาถึงสามครั้ง
คำถามที่พบบ่อย (FAQ)
การพัฒนา API แบบ Spec-first เหมือนกับการพัฒนาแบบ Design-first หรือไม่?
ส่วนใหญ่แล้วใช่ "Design-first" และ "contract-first" อธิบายหลักการเดียวกัน: เขียนส่วนเชื่อมต่อ (interface) ก่อนการนำไปใช้งาน "Spec-first" เป็นชื่อที่ตรงตัวที่สุด เพราะไฟล์ OpenAPI spec เป็นสิ่งประดิษฐ์ที่เป็นรูปธรรมที่คุณเริ่มต้นด้วย ในทางปฏิบัติแล้ว คำศัพท์เหล่านี้ใช้แทนกันได้
ฉันต้องเขียน YAML ด้วยตนเองหรือไม่?
ไม่ คุณสามารถสร้าง spec ใน visual editor และให้มันสร้าง YAML ออกมา หรือจะเขียน YAML โดยตรงก็ได้หากคุณต้องการ จุดสำคัญไม่ได้อยู่ที่รูปแบบที่คุณพิมพ์ แต่อยู่ที่ว่าสัญญา (contract) มีอยู่จริงและได้รับการตกลงกันก่อนที่จะเขียนโค้ด ทีมงานจำนวนมากใช้ทั้งสองวิธี ผสมผสานการร่างแบบเห็นภาพและปรับปรุงในรูปแบบ YAML ระหว่างการตรวจสอบ
จะป้องกันไม่ให้ spec และโค้ดคลาดเคลื่อนจากกันได้อย่างไร?
ทำให้ spec เป็นแหล่งความจริงและบังคับใช้มัน เก็บ spec ไว้ใน Git เพื่อให้การเปลี่ยนแปลงถูกตรวจสอบเป็นการเปรียบเทียบ (diffs) เรียกใช้ contract tests ใน CI เพื่อให้ build ล้มเหลวเมื่อการตอบกลับไม่ตรงกับ schema ด้วยการซิงค์แบบสองทาง การแก้ไขในทั้งสองที่ยังคงสอดคล้องกัน ซึ่งช่วยขจัดสาเหตุที่พบบ่อยที่สุดของการคลาดเคลื่อน
การพัฒนา API แบบ Spec-first คือการเปลี่ยนแปลงลำดับเล็กน้อยที่นำไปสู่ผลลัพธ์ที่ยิ่งใหญ่ เขียนสัญญา ตกลงกัน แล้วจึงสร้างตามนั้น หากคุณต้องการลองใช้งานแบบเต็มวงจร ให้เปิด โหมด Spec-First ใน Apidog และชี้ไปที่ repo ของคุณ
ปุ่ม
