Skip to main content

หลักเกณฑ์และแนวทางปฏิบัติที่ดีที่สุดสำหรับการออกแบบ RESTful API

· 16 min read
Kongvut Sangkla

Intro

ปัจจุปันการพัฒนา API หรือ Microservice เพิ่มขึ้นอย่างมีนัยสำคัญ ซึ่งมีส่วนสำคัญในการใช้แลกเปลี่ยนข้อมูล กรณีมีแอปพลิเคชันในหลายรูปแบบเช่น Web app หรือ Mobile app และ IoT โดยใช้ Restful API เป็นตัวกลางเชื่อมต่อระหว่างแต่ละอุปกรณ์ซึ่งอนุญาตให้แลกเปลี่ยนข้อมูลระหว่าง Server กับ Client โดย Server รับ Requests จาก Client และส่ง Response ข้อมูลกลับ

API

คำว่า API นั้นย่อมาจาก Application Programming Interface ซึ่ง API จะเป็นตัวกลางที่อนุญาตให้แอปพลิเคชันแลกเปลี่ยนข้อมูลผ่าน Endpoint โดย Client จะใช้ API ส่งคำร้องขอ (Request) และตอบกลับข้อมูล (Response) (คล้าย ๆ การสื่อสารระหว่าง 2 อุปกรณ์โดยมี API เป็นตัวกลาง)

note

Endpoints คืออะไร ? Endpoint คือ Term ของ URL Path เป็นเหมือนภาษาพูดที่ใช้เรียก Path ของ API โดยจะเรียก URLs ทั้งหมดว่า API แต่ละ URL จะเรียกว่า Endpoint

Public endpoints คือ Endpoint ที่สามารถเข้าถึงได้แบบสาธารณะ ไม่จำเป็นต้องมีการยืนยันตัวตนก่อนใช้งาน

Authenticated endpoints คือ Endpoint ที่จำเป็นต้องมีการยืนยันตัวตนก่อนใช้งาน

REST

คำว่า REST นั้นย่อมาจาก Representational State Transfer ซึ่ง Rest คือคำเรียกในเชิงแบบสถาปัตยกรรมที่ใช้ในการพัฒนาเว็บเซอร์วิส โดยนำเสนอครั้งแรกโดย Roy Fielding https://en.wikipedia.org/wiki/Roy_Fielding

RESTful API

RESTful นั้นพัฒนามาจากรากฐานสถาปัตยกรรมรูปแบบของ REST และใช้ HTTP Methods เรียกว่า RESTful web services

โดยทั่วไป API นั้นช่วยให้การแลกเปลี่ยนข้อมูลนั้นง่ายขึ้นมาก ๆ โดยจะอนุญาตให้นักพัฒนา Integrated ฟังก์ชันบางอย่างจาก API ที่เป็น Third-party Services เพื่อสร้างฟังก์ชันอะไรบางอย่างของตัวเอง อย่างกรณีเช่น Uber และ Grab ใช้ Google Map API สำหรับการทำระบบนำทาง (Navigation) โดยช่วยให้ลดเวลาพัฒนาลงอย่างมากในการพัฒนาระบบเองจากศูนย์

Resful-API-cycle-1024x275

รูปภาพ อธิบายการทำงานของ API

RESTful API Design

API นั้นสามารถสร้างได้จากหลากหลายภาษา (Server side script Language Programming) ไม่ว่าจะเป็น PHP Ruby JS Java Python และ Golang เป็นต้น

คำศัพท์

  • Collections Resource เช่น /api/v1/users สำหรับหลาย items
  • Instance Resource เช่น /api/v1/users/123 สำหรับ item เดียว

โครงสร้าง API Endpoint อ้างอิงตามด้านล่างนี้ โดย URL โครงสร้างควรเป็นไปตามแบบลำดับชั้น และสามารถคาดการณ์ได้เพื่อให้ทำความเข้าใจและการใช้งานง่าย

   https://api.abc.com/v1/companies/123/users/?attributes=first_name,last_name
\___/ \_________/\_/\__________________/\_______________________________/
| | | | |
scheme base version path query parameters

รายละเอียด

   https://api.abc.com/v1/shops/3/categories/5/toys/2/parts?material=plastic
\___/ \_________/\_/\____/\_/\________/\_/\__/\_/\___/\_______________/
| | | | | | | | | | |
scheme base 0 1 2 3 4 5 6 7 8

0. API Version
1. All shops
2. Shops with id = 3
3. All categories
4. Category with id = 5
5. All toys
6. Toy with id = 2
7. All parts
8. Query parameters (Optional) using with sorting, filtering, pagination

รูปแบบของ Resource Names ที่ดี

  • ควรเป็นคำนาม อย่าใช้คำกริยาในการอธิบาย
  • ชื่อ Resource ควรเป็นเอกพจน์สำหรับแบบ Instance Resource
  • ชื่อ Resource ควรเป็นพหูพจน์สำหรับแบบ Collections Resource
  • ชื่อ Resource ควรตั้งเป็นตัวพิมพ์เล็ก (Lower-case) และเครื่องหมาย - (Hyphen) สำหรับคั่นคำ

รูปแบบของชื่อ Query Parameters ที่ดี

  • ตัวอักษรใน Query string ควรใช้ _ (Underscore) สำหรับคั่นคำ
  • Query parameters ควรเป็นตัวพิมพ์เล็กทั้งหมด (Lower-case)
  • Query parameters ใช้เท่าที่จำเป็น เช่นการ Filters และ Sorting
  • Query parameters พึงระวังไว้เสมอว่าไม่ควรใช้ตัวอักษรที่เป็นข้อมูล Sensitive

รูปแบบของชื่อ Fields หรือ Attribute Names ที่ดี

  • รูปแบบของชื่อควรสอดคล้องกับรูปแบบของข้อมูล JSON
  • ควรเป็นตัวพิมพ์เล็กทั้งหมด (Lower-case) ใช้ _ (Underscore) สำหรับคั่นคำ
  • ไม่ควรใช้ Prefix อย่างเช่น is หรือ has สำหรับ keys ประเภทแบบบูลีน
  • Fields ที่เป็นรูปแบบ Arrays ควรใช้ชื่อที่เป็นคำนามพหูพจน์ ตัวอย่าง
    • authenticators-contains หรือ authenticators
    • products-contains หรือ products

ตัวอย่างที่ดี

พนักงานหลายคน:

GET https://api.abc.com/v1/employees

ใช้ Plural หรือ Singular nouns (จะเลือกใช้อะไรก็ตาม ก็ควรให้มันสอดคล้องกับ APIs ทั้งหมดที่มี)

แบบการกรอง:

GET https://api.abc.com/v1/employees?year=2011&sort=desc

GET https://api.abc.com/v1/employees?section=economy&year=2011

พนักงานคนเดียว:

GET https://api.abc.com/v1/employees/1234

สถานที่ปฏิบัติงานทั้งหมดของพนักงานของ 1234:

GET https://api.abc.com/v1/employees/1234/locations

ระบุพารามิเตอร์ตัวเลือกสำหรับแสดงผลในรูปแบบคอมม่าลิส:

GET https://api.abc.com/v1/employees/1234?fields=job_title,start_date

เพิ่มสถานที่ปฏิบัติงานสำหรับพนักงาน 1234:

POST https://api.abc.com/v1/employees/1234/locations

ตัวอย่างที่ควรปรับปรุง

แบบไม่เป็นพหูพจน์:

GET https://api.abc.com/v1/employee

GET https://api.abc.com/v1/employee/1234

GET https://api.abc.com/v1/employee/1234/location

มีคำกริยาใน URL:

POST https://api.abc.com/v1/employee/1234/create

การระบุข้อมูลแบบ URL แทนที่จะเป็นการเขียนแบบ Query string

GET https://api.abc.com/v1/employee/1234/desc

ตัวอย่างการออกแบบ CRUD API ที่ดี

ทีนี้มาดูตัวอย่างรายละเอียดสำหรับออกแบบ Article Resource API ที่ดีที่ควรจะเป็นโดยตัวอย่างจะเป็นแบบ CRUD (Create-Read-Update-Delete)

เมื่อต้องการขอข้อมูล (Read)

ที่ควรปรับปรุง

MethodURIDescription
GET/FetchArticleสำหรับขอข้อมูลทั้งหมด
GET/getAllArticles/12สำหรับขอข้อมูลทบางรายการ
  • อย่าใช้คำกริยาหรือพฤติกรรมเพื่ออธิบาย APIs

ที่ควรจะเป็น

MethodURIDescription
GET/articlesสำหรับขอข้อมูลทั้งหมด
GET/articles/12สำหรับขอข้อมูลทบางรายการ
  • HTTP method ควรใช้ GET
  • ใช้ Plural หรือ Singular nouns (จะเลือกใช้อะไรก็ตาม ก็ควรให้มันสอดคล้องกับ APIs ทั้งหมดที่มี)

เมื่อเพิ่มข้อมูล (Create)

ที่ควรปรับปรุง

MethodURIDescription
POST/createarticleสำหรับสร้างรายการข้อมูล
GET/createrecordforartilceสำหรับสร้างรายการข้อมูลแบบ GET
  • URL ของ Endpoint ควรประกอบไปด้วย HTTP method ทีอธิบายความสามารถของ APIs นั้น

ที่ควรจะเป็น

MethodURIDescription
POST/articlesสำหรับสร้างรายการข้อมูล
  • HTTP method ควรใช้ POST
  • ใช้ Plural หรือ Singular nouns (จะเลือกใช้อะไรก็ตาม ก็ควรให้มันสอดคล้องกับ APIs ทั้งหมดที่มี)

เมื่ออัปเดตข้อมูล (Update)

ที่ควรปรับปรุง

MethodURIDescription
PUT/updatearticle/12สำหรับการอัปเดตข้อมูลบางรายการ
POST/12/modifyarticleสำหรับการอัปเดตข้อมูลบางรายการ

ที่ควรจะเป็น

MethodURIDescription
PATCH/articles/12สำหรับการอัปเดตข้อมูลบางรายการ
PUT/articles/12สำหรับการอัปเดตข้อมูลทุกรายการ

เมื่อลบข้อมูล (Delete)

ที่ควรปรับปรุง

MethodURIDescription
PUT/deletearticle/12สำหรับลบข้อมูลบางรายการ
POST/12/deletearticleสำหรับลบข้อมูลบางรายการ
PATCH/delete-article/12สำหรับลบข้อมูลบางรายการ

ที่ควรจะเป็น

MethodURIDescription
DELETE/articles/12สำหรับลบข้อมูลบางรายการ
  • HTTP method ควรใช้ DELETE
  • ใช้ Plural หรือ Singular nouns (จะเลือกใช้อะไรก็ตาม ก็ควรให้มันสอดคล้องกับ APIs ทั้งหมดที่มี)

สรุป Routes Resource

MethodURIFunctionRoute NameDescription
GET/articlesindex()articles.indexall articles
POST/articlesstore(req)articles.storecreate
GET/articles/{id}show(id)articles.showshow by id
PUT/PATCH/articles/{id}update(id)articles.updateedit/replace
DELETE/articles/{id}remove(id)articles.removeremove

HTTP methods

HTTP methods (RFC) คือ กริยา (Verb) หรือบางทีเรียก HTTP Verbs โดยเป็นการดำเนินการบนของมาตรฐาน HTTP Request ดังนี้

HTTP Methodคำอธิบาย
GETสำหรับร้องขอ Resource
POSTสำหรับเพิ่ม Resource ใหม่ หรือ สำหรับสั่งเพื่อการดำเนินการบางอย่าง เช่น POST ส่งข้อความ
PUTสำหรับแก้ไขข้อมูลใน Resource (ควรใช้เฉพาะกรณีต้องการ Replace ทุก Fields เท่านั้น)
PATCHสำหรับแก้ไขข้อมูลใน Resource (ควรใช้แก้ไขข้อมูลแค่บาง Fields เท่านั้น)
DELETEสำหรับลบข้อมูลใน Resource
HEADสำหรับร้องขอ metadata เกี่ยวกับ Headers the request การร้องขอการตอบรับจาก Resource จะคล้ายกับ GET แต่จะไม่มีส่วนเนื้อหา (Payload) ตอบกลับ คำสั่งนี้ใช้ประโยชน์ในการตรวจสอบข้อมูลส่วนหัว โดยไม่จำเป็นต้องส่งเนื้อหาเต็มมาทั้งหมด
OPTIONSสำหรับร้องขอเพื่อเรียกดู HTTP method ที่สามารถใช้งานได้ จากการกำหนด CORS (cross-origin resource sharing)

HTTP response status code

คำอธิบายและความหมายของ HTTP/1.1: Status Code Definitions (RFC)

  • 2xx successful – เมื่อ Request ได้รับสำเร็จ เข้าใจ และยอมรับแล้ว
  • 3xx redirection – เส้นทางมีการเปลี่ยนแปลง และต้องได้รับ Action อื่น ๆ เพื่อให้ดำเนินการสำเร็จ
  • 4xx client error – เมื่อ request มีไวยากรณ์ที่ไม่ถูกต้อง หรือไม่สามารถทำให้สมบูรณ์
  • 5xx server error – เมื่อเซิร์ฟเวอร์ไม่สามารถตอบสนองคำขอที่ถูกต้องได้
GETPOSTPUTDELETEPATCHAll
CodeStatusCodeStatusCodeStatusCodeStatusCodeStatusCodeStatus
200OK201Created202Accepted202Accepted202Accepted408Request Timeout
400Bad Request202Accepted204No content204No content204No content501Method Not Implemented
401Unauthorised400Bad Request400Bad Request400Bad Request400Bad Request
403Forbidden401Unauthorised401Unauthorised401Unauthorised401Unauthorised
404Not found403Forbidden403Forbidden403Forbidden403Forbidden
405Not Allowed404Not found404Not found404Not found404Not found
415Unsupported Media Type405Not Allowed405Not Allowed405Not Allowed405Not Allowed
500Internal Server error415Unsupported Media Type415Unsupported Media Type415Unsupported Media Type415Unsupported Media Type
422Unprocessable Entity422Unprocessable Entity500Internal Server error422Unprocessable Entity
500Internal Server error500Internal Server error500Internal Server error

Single Resource

รหัสสถานะการตอบกลับต่อไปนี้แสดงถึงการตอบกลับต่าง ๆ เมื่อการดำเนินการที่แตกต่างกันโดยสามารถทำได้บน Single resource

Request MethodResource PathStatusCode
POST/resourcesCreated201
Accepted202
Bad Request400
Unauthorised401
Forbidden403
Not found404
Not Allowed405
Unsupported Media Type415
Unprocessable Entity422
Internal Server Error500
PUT/resources/{id}OK200
Accepted202
No content204
Bad Request400
Unauthorised401
Forbidden403
Not found404
Not Allowed405
Unsupported Media Type415
Unprocessable Entity422
Internal Server error500
DELETE/resources/{id}Accepted202
No Content204
Bad Request400
Unauthorised401
Forbidden403
Not found404
Not Allowed405
Internal Server error500
PATCH/resources/{id}Accepted202
No content204
Bad Request400
Unauthorised401
Forbidden403
Not found404
Not Allowed405
Unsupported Media Type415
Unprocessable Entity422
Internal Server error500

Collection of Resources

รหัสสถานะการตอบกลับต่อไปนี้แสดงถึงการตอบกลับต่าง ๆ เมื่อการดำเนินการที่แตกต่างกันโดยสามารถทำได้บน Collection resource (All)

Request MethodResource PathStatusCode
GET/resourcesOK200
Bad Request400
Unauthorised401
Forbidden403
Not found404
Not Allowed405
Unsupported Media Type415
Internal Server error500

Response data format

Result Messages

สำหรับการตอบกลับของข้อมูลนั้นจะมีรูปแบบดังนี้

GET /employees จะ Returns ลิสของ Objects ในกรอบของ data:

{
"data": [
{ "id": 1, "name": "Larry" },
{ "id": 2, "name": "Peter" }
]
}

GET /employees/1 จะ Returns แบบ Single object ในกรอบของ data:

{
"data": { "id": 1, "name": "Larry" }
}

คำแนะนำใน Request Payload หากเป็นไปได้ทั้งแบบ PUT POST และ PATCH ควรจะมีกรอบของ data เพื่อบอกให้ทราบในส่วนรูปแบบของข้อมูล

ถ้าหากมีการข้ามของข้อมูลก็ควรมีการกำหนดค่าเริ่มต้น (เช่น offset=0 และ limit=100) และไม่ควรส่งออกข้อมูลทั้งหมด ถ้าข้อมูลมีจำนวนเยอะควรจะมีการจำกัด Limit เป็นค่าเริ่มต้น (Pagination)

GET /employees  # returns the employees 0 to 100

เมื่อมีการกำหนด offset และ limit

GET /employees?offset=20&limit=10
Result
{
"pagination": {
"offset": 20,
"limit": 10,
"total": 3465,
},
"data": [
//...
],
"links": {
"next": "https://api.abc.com/v1/employees?offset=30&limit=10",
"prev": "https://api.abc.com/v1/employees?offset=10&limit=10"
}
}

ถ้าเป็นไปได้ควรมี Links ที่สามารถไปหน้าถัดไปและหน้าก่อนหน้า เมื่อ URL นั้นมีการกำหนด offset และ limit

Error Messages

เป็นส่วนที่เกี่ยวกับ Errors เมื่อมีเหตุการณ์บางอย่างที่เกิดขึ้นไม่ถูกต้องโดยในส่วนของ Body ของการตอบกลับ HTTP มีตัวอย่างนี้

Request:

GET /employees?state=super

Response:

// 400 Bad Request
{
"errors": [
{
"status": 400,
"code": "INVALID_STATE",
"message": "Invalid state. Valid values are 'internal' or 'external'",
"links": {
"about": "https://api.abc.com/v1/rest/errorcode/352"
}
}
]
}

Filtering, Sorting, Pagination

Filtering

Filtering คือความสามารถในการ Filter และ Sort ของ Collections ใน API เพื่อที่จะอนุญาตให้ควบคุมการแสดงผลข้อมูลใน API ให้มีความยืดหยุ่น

  • การกำหนดจำนวนการแสดงผลด้วยวิธีต่าง ๆ (แต่วิธีนี้ไม่แนะนำ) โดยเป็นส่วนหนึ่งของ API URI (ดัวอย่าง /employees/age/from/20/to/30)
  • Filter parameters ไม่เป็นส่วนหนึ่งของการกำหนด Resource ให้ใช้ Query parameters แทนเพื่อ Filter และ Sort ตามความต้องการ

ข้างล่างนี้จะเป็นความแตกต่างของเทคนิคที่ใช้กับ Filter และ Sort:

Output Selection

ผู้ใช้ API สามารถระบุ Attributes ที่เขาต้องการสำหรับการตอบกลับในส่วนของ Response payload ด้วยการระบุ Attributes ใน Query parameters

ตัวอย่างการตอบกลับสำหรับ first_name และ last_name เท่านั้น

?attributes=first_name,last_name

Simple Filtering

Attributes สามารถใช้ Filter กับ collection ของ resources

ตัวอย่าง:

?last_name=Johnson

จะ Filter ออกจาก Collection ของ resources ด้วย attribute last_name ที่เท่ากับ Johnson

ตัวอย่าง:

?last_name=Johnson&date_of_birth=1999-12-31

จะ Filter ข้อมูลออกจาก Collection ของ resources ด้วย attribute last_name ที่เท่ากับ Johnson และ date_of_birth เท่ากับ 1999-12-31

ใช้สัญลักษณ์เท่ากับ (=) สำหรับเทคนิคที่ใช้เปรียบเทียบข้อมูลที่เท่ากัน และสำหรับการดำเนินการอื่น ๆ ดูในหัวข้อ Advanced Filtering

Advanced Filtering

มีบางสถานการณ์ที่การ Filter อย่างง่ายนั้นไม่ตรงกับความต้องการและจำเป็นต้องมีแนวทางอื่นที่ครอบคลุมมากขึ้น อย่างเช่นใช้ตัวกรองสัญลักษณ์ดังต่อไปนี้เพื่อกำหนดตรรกะการกรองที่ซับซ้อนมากขึ้น

สามารถกำหนดแอตทริบิวต์หลายรายการที่มีตัวดำเนินการและเงื่อนไขต่างกันได้ในพารามิเตอร์ตัวกรองการค้นหา

รองรับตัวดำเนินการต่อไปนี้:

  1. >= มากกว่าหรือเท่ากับ
  2. > มากกว่า
  3. < น้อยกว่า
  4. <= น้อยกว่าหรือเท่ากับ
  5. = เท่ากับ
  6. != ไม่เท่ากับ

หรือไม่ว่าจะเป็นเงื่อนไข AND, OR ก็กำหนดได้

ตัวอย่าง:

?filters=creation_date >= 2001-09-20T13:00:00 and creation_date <= 2001-09-21T13:00:00 and first_name like 'Fred' and post_code=3000

จะแสดง collection ของ resources เมื่อ creation_date อยู่ระหว่าง 2001-09-20 1pm และ 2001-09-21 1pm และ first-name คือ Fred และ post_code คือ 3000

Match Case Sensitivity

  • ตามคำแนะนำทั่วไปการ Return ข้อมูลที่คล้ายกันจะดีกว่าการไม่ส่งคืนรายการที่ไม่ตรงกันเลย
  • ค่าที่กำหนดของการกรองควรพิจารณาจะไม่คำนึงถึงขนาดตัวพิมพ์
  • ไม่ว่าคุณจจะไม่คำนึงถึงขนาดตัวพิมพ์ แต่ก็ควรจัดทำเป็นเอกสารไว้อย่างชัดเจน

Sorting

การที่ข้อมูลสามารถกำหนดตามลำดับการเรียงที่เฉพาะเจาะจงได้มักเป็นความต้องการจากแอปพลิเคชันไคลเอนต์ ดังนั้นจึงเป็นเรื่องสำคัญที่จะต้องให้ความยืดหยุ่นแก่ผู้ใช้ในการดึงข้อมูลตามลำดับที่พวกเขาต้องการ

พารามิเตอร์สองตัวที่ใช้ในการเรียงลำดับมีดังนี้:

Query ParameterDescription
sortในการจัดเรียงข้อมูลเช่น asc หรือ desc
sort_fieldsพารามิเตอร์ที่ใช้จัดเรียงเช่น id หรือ name

sort_fields เป็นพหูพจน์เนื่องจากมีทั้งตัวเลือกต่อไปนี้:

ตัวอย่าง:

  • ?sort=asc&sort_fields=name,last_modified
  • ?sort=desc&sort_fields=name&sort_fields=last_modified

คำค้นหาทั้งสองนี้ควรเรียงลำดับตามชื่อจากนั้นตามวันที่แก้ไขล่าสุด

Pagination

ระบบเลขหน้าทำตามคำ แนะนำ ดังนี้:

  1. ส่งคำขอในเวลาที่เหมาะสม (เช่น Request < 2 วินาที)
  2. ตรวจสอบให้แน่ใจว่าปริมาณข้อมูลที่ตอบกลับยังคงอยู่ในส่วนที่จัดการได้ (เช่น < 500kb)
  3. ตรวจสอบปริมาณข้อมูลในการตอบกลับสามารถจัดการได้ง่ายเพื่อรองรับประสบการณ์ของผู้ใช้ที่ดี (UX)

Parameters

เมื่อใช้การแบ่งหน้าให้ใช้พารามิเตอร์ต่อไปนี้ (ควรจะ):

Query ParameterDescriptionExample
pageหน้าเพจที่ผู้ใช้ต้องการpage=1 (กำหนดค่าเริ่มต้น: 1)
limitจำนวนผลลัพธ์ต่อหน้าที่ผู้ใช้ต้องการlimit=10 (กำหนดค่าเริ่มต้น: 10)

ตัวอย่างสร้างโครงสร้าง URI ดังนี้:

Page 1: /customers?page=1&limit=10
Page 2: /customers?page=2&limit=10
...
Page 50: /customers?page=50&limit=10

โครงสร้างนี้เข้าใจง่ายเนื่องจากมีความเรียบง่าย และสามารถรวมไว้ใน UI เช่น ลิงก์ต่าง ๆ ได้โดยตรง

Common Terms

พารามิเตอร์ต่อไปนี้เป็นพารามิเตอร์ทั่วไปใน REST APIs แต่ห้ามใช้ในข้อกำหนดดังนี้

  • offset และ limit การใช้สองสิ่งนี้เป็นเรื่องปกติในฐานข้อมูล SQL และเป็นตัวเลือกที่ดีเมื่อคุณต้องการให้ผลลัพธ์ออกมาอย่างต้องการ อย่างไรก็ตามการใช้งานพารามิเตอร์ offset อาจเป็นเรื่องยากดังนั้นจึงแนะนำให้ใช้ page แทน
  • since และ limit ดึงข้อมูลทุกอย่าง "ตั้งแต่" โดยสิ่งเหล่านี้มีประโยชน์เมื่อต้องการให้ไคลเอนต์สามารถ "ซิงค์" กับข้อมูลได้อย่างมีประสิทธิภาพ อย่างไรก็ตามโดยทั่วไปแล้วต้องมีการกำหนดการเรียงลำดับเริ่มต้นไว้ให้ชุดผลลัพธ์เพื่อให้มีความสำคัญอย่างมีนัย

Metadata

เมื่อมีการแบ่งหน้าให้กำหนดสิ่งนี้ไว้ แนะนำอย่างยิ่ง สำหรับ API เพื่อที่จะให้คำแนะนำผู้ใช้ในเนื้อหาการตอบกลับ (response body) ว่าพวกเขาอยู่ในหน้าใด ถ้าหากไม่กำหนดไว้ผู้ใช้จะต้องกำหนดให้ตรวจสอบคำขอซึ่งมักจะต้องมีโค้ดอย่างไม่จำเป็นเพื่อตรวจสอบพารามิเตอร์การสืบค้น

ตัวอย่าง:

{
"total_records" : 2340,
"_meta": {
"processing_time" : 120,
"page": 4,
"limit": 20
},
"_links" : {
"_self": "/namespace/v1/employees?page=2&limit=1",
"_prev": "/namespace/v1/employees?page=1&limit=1",
"_next": "/namespace/v1/employees?page=3&limit=1"
},
"_embedded" : {
"count" : 20,
"data" : [//...actual data here...]
}
}

API Versioning

API Versions คือส่วนที่บอกว่า APIs มีรุ่นและการเปลี่ยนแปลงอย่างไร และปัจจุบันเป็นรุ่นอะไร

ทำไมต้องมี API Versions

ทำไมต้องมี API Versions เพราะเมื่อคุณเผยแพร่ API เป็น Public แล้วดังนั้น API ของคุณจะต้องไม่เกิดการ Breaking (อยู่ดี ๆ ใช้งานไม่ได้) ของ API โดยไม่แจ้งผู้ใช้ให้ทราบล่วงหน้า ดังนั้น API Versioning จะช่วยให้ป้องกันการเกิด Request ที่ไม่มีอยู่ เช่น จากการปรับปรุง Endpoints ดังตัวอย่างนี้

ตัวอย่าง ตอนแรกมี API นี้อยู่

https://api.abc.com/articles

//Response result
[
{
"title": "...",
"text": "..."
}
...
]

วันดีคืนดีแก้ไข API นี้ใหม่

API เดิมแต่แก้ไขใหม่

https://api.abc.com/articles

//Response result
{
"data": [
{
"title": "...",
"detail": "..."
}
...
]
}

จะเห็นว่า API https://api.abc.com/articles ส่วนปรับปรุงใหม่มีส่วนที่แตกต่างจากเดิมซึ่งจะทำให้เกิดปัญหากับ Clients ที่ใช้ API นี้อยู่โดยถ้าไม่ได้รับการปรับปรุงตาม API ใหม่ ดังนั้นเมื่อนำ API Versions มาช่วยแก้ปัญหาจะได้ดังนี้

นำ API Versions มาช่วยแก้ปัญหา
#API Version ก่อนการแก้ไขสามารถใช้กับ Clients ได้ไม่มีปัญหา
https://api.abc.com/v1/articles

#API Version หลังการแก้ไข และรอการปรับปรุง Clients เพื่อความเข้ากันได้
https://api.abc.com/v2/articles

คำแนะนำการออกแบบ API Versions

โดยที่:

  • โดย API version ควรจะ กำหนดเป็นส่วนหนึ่งใน URI
  • โดย API version ควรจะ กำหนดไว้อยู่เสมอ (เช่น ค่าเริ่มเป็นเวอร์ชันล่าสุด)
  • โดย API version ควรจะ ระบุด้วย 'v'
  • ถ้าผู้ใช้งานพยายามเข้าถึง API ด้วยการไม่ระบุเวอร์ชัน ดังนั้นเขาก็ควรได้รับข้อความผิดพลาดตอบกลับ 404
  • ถ้าผู้ใช้งานพยายามเข้าถึง API ด้วยการไม่ระบุ resource (เช่น GET .../api/v1) การตอบกลับอาจจะประกอบด้วยข้อมูลพื้นฐานเกี่ยวกับ API (เช่น ชื่อ API, และรายละเอียดอื่น ๆ)

โดยอาจจะมี API version หลายอันได้ตัวอย่าง:

  • .../api/v1
  • .../api/v2
note

ไม่ทำโครงสร้าง version ที่คล้ายกัน (ตัวอย่าง ไม่ควรมี v1.x or v1.x.y) นั่นหมายความว่าคุณมีเวอร์ชันโดยละเอียดมากเกินไป ที่บ่งบอกถึงความละเอียดของการกำหนดเวอร์ชัน แต่ใช้งานไม่ได้กับ API!

รายละเอียดของ APIs Version ควร เป็นไปตาม semantic versioning:

{MAJOR}.{MINOR}.{PATCH}

เวอร์ชันแรกของ API ต้อง เริ่มต้นด้วยเวอร์ชัน MAJOR เป็น 1 เสมอ

ใช้แนวทางต่อไปนี้เมื่อเพิ่มหมายเลขเวอร์ชัน API:

  • MAJOR เวอร์ชัน เมื่อคุณทำการ incompatible หรือ breaking ของการแก้ไข API
  • MINOR เวอร์ชัน เมื่อคุณเพิ่มฟังก์ชันใน backwards-compatible หรือคุณสมบัติอื่น ๆ
  • PATCH เวอร์ชัน เมื่อคุณทำการ backwards-compatible การแก้ไขข้อพกพร่อง
Major Version

APIs ทั้งหมด ควร รวมเวอร์ชัน MAJOR เป็นส่วนหนึ่งของ URI ในรูปแบบของ 'v{MAJOR}', ตัวอย่าง https://api.example.com/namespace/v1/employees

note

ไม่จำเป็นต้องใช้เวอร์ชัน Minor และ Patch ใน URI เนื่องจากการเปลี่ยนแปลงเหล่านี้ จะเข้ากันได้กับรุ่น Major อยู่แล้ว

โดยให้หมายเลขเวอร์ชันอื่น ๆ (Minor, Patch) จะแสดงในหน้าเอกสารประกอบ API หรือเป็นส่วนหนึ่งของการเรียกการจัดการพิเศษไปยัง URI ของ API เอง

Metadata

เพื่อให้การสนับสนุน API ของคุณ ควร ให้มีการตอบกลับคำขอ GET ไปยัง Base URI ของ API และส่งคืนรูปแบบข้อมูล Metadata ต่อไปนี้การตอบกลับ:

  • api_name: ชื่อ API
  • api_version: API Version กับ Major และ minor versions
  • api_released: วันที่ล่าสุดที่ปรับปรุง API
  • api_documentation: ลิงก์สำหรับ API Documentation
  • api_status: สถานะที่บ่งบอกว่า API นั้น Active หรือ Deprecated

สามารถเพิ่มข้อมูล Metadata อื่น ๆ สำหรับการตอบกลับ (ถ้าต้องการ)

ตัวอย่าง:

GET /namespace/v1

//HTTP 200 OK

{
"api_name": "namespace",
"api_version": "1.1.3"
"api_released": "2021-04-06"
"api_documentation": "https://api.example.com/namespace/v1/docs"
"api_status": "active"
}

คำแนะนำอื่น ๆ

แนวทางและหลักการบางประการในการตัดสินใจว่า เมื่อใดจึงจำเป็นต้องใช้ API เวอร์ชันใหม่:

  • คุณไม่ควรสร้างเวอร์ชัน API ใหม่ เมื่อคุณเพิ่มฟิลด์ / แอตทริบิวต์ให้กับ Resources ที่มีอยู่ ถ้าหากผู้ใช้สามารถเพิกเฉยต่อสิ่งเหล่านั้นได้ (กล่าวคือหากผู้ใช้ต้องไม่เพิ่มสิ่งเหล่านั้นในคำขอ)
  • คุณไม่ควรสร้างเวอร์ชัน API ใหม่ ถ้าหากคุณเพิ่ม Resources ใหม่ โดยที่ลูกค้าไม่จำเป็นต้องใช้มันโดยตรง
  • คุณต้องสร้างเวอร์ชัน API ใหม่ ถ้าหากคุณแก้ไขทรัพยากรที่มีอยู่ในลักษณะที่จะส่งผลกระทบต่อไคลเอนต์ (เช่นฟิลด์บังคับใหม่การลบฟิลด์ ... )
  • คุณต้องสร้างเวอร์ชัน API ใหม่ ถ้าหากคุณมี Resources ใหม่ โดยหากผู้ใช้ต้องได้ใช้มันด้วย
  • คุณต้องสร้างเวอร์ชัน API ใหม่ ถ้าหากคุณสร้างประเภทข้อผิดพลาดใหม่
note

💡 Maintain เวอร์ชันอย่างน้อยหนึ่งเวอร์ชัน เพื่อให้แอปพลิเคชันอื่น ๆ ที่ใช้ API ให้มีเวลาอัปเกรดเป็นเวอร์ชันใหม่แทนที่จะทำลายทิ้งทันที

💡 สร้าง และ Maintain บันทึกการเปลี่ยนแปลง (Changelog) และคู่มือแนะนำการ Migration สำหรับ API ของคุณเพื่อให้ผู้ใช้เห็นสิ่งที่เปลี่ยนแปลงและวิธีอัปเกรดได้อย่างง่ายดาย

Documentation

เอกสาร (API Docs) เป็นสิ่งที่สำคัญสำหรับนักพัฒนาในการใช้ API โดย API ที่แตกต่างกันจะมีลักษณะการทำงานที่แตกต่างกัน ซึ่งต้องใช้พารามิเตอร์ที่แตกต่างกัน เช่น วิธีการ HTTPS การ Response data ของ API ดังนั้นนักพัฒนาส่วนใหญ่จะชอบเอกสารที่ดีสำหรับใช้อ้างอิงการใช้งาน API

มีเครื่องมือดี ๆ มากมาย เพื่อที่จะช่วยนักพัฒนาในการสร้างเอกสาร API ดังนี้:

  • Swagger: ออกแบบและสร้างโมเดล API ตามมาตรฐานตามข้อกำหนด เพิ่มประสบการณ์ (UX) ของนักพัฒนาด้วยเอกสาร API แบบโต้ตอบได้
  • Slate: Slate ช่วยให้คุณสร้างเอกสาร API ที่สวยงาม ฉลาด ความสามารถการตอบสนอง ที่สำคัญ Slate คือโครงการโอเพ่นซอร์สและใช้งานได้ฟรี

Security Features

เป็นที่นิยมในการใช้คุณสมบัตินโยบายความปลอดภัยที่มีอยู่ในแพลตฟอร์ม API Gateway แทนที่จะใช้นโยบาย (Policy) การเข้าถึงใน back-end API ตรง ๆ แต่วิธีใดก็ตามควรจะมีรูปแบบความปลอดภัยดังนี้

ความปลอดภัยเหตุการณ์ของ APIรายละเอียดความจำเป็นในการ Implementation
HTTP verbsListeners->PathHTTP verbs ต่าง ๆ สามารถเลือกใช้ได้จาก Path ของ API ที่กำหนดไว้ควร
Input ValidationFilter -> Content Filteringสามารถ Filters สำหรับการ Validation ข้อมูล Input ตัวอย่างเช่น การตรวจสอบขนาดของข้อความ การตรวจสอบ Schema การตรวจสอบ Headers หรือการตรวจสอบ Query strings เป็นต้นควร
SSLListenersใช้ SSL protocol (เช่น TLS 1.2)ควร
Digital CertificateFilter -> Integrityสามารถ Filters สำหรับใช้ Signature เพื่อการตรวจสอบตัวอย่างเช่น API Token หรือ JWT signเป็น Optional หรือขึ้นอยู่กับ Business requirements
JWTFilter -> Integrityข้อความสามารถเข้ารหัสให้อยู่ในรูปแบบ JWTเป็น Optional หรือขึ้นอยู่กับ Business requirements
API KeysFilter -> Authenticationสามารถ Filters สำหรับการ Authentication ผู้ใช้งาน เช่น API Keys หรือ API Tokenควร
OAuthFilter -> OAuthสามารถใช้ OAuth สำหรับมอบสิทธิ์เข้าถึง Data resources สำหรับผู้ใช้งานเป็น Optional หรือขึ้นอยู่กับ Business requirements
CORSListeners->PathCORS สามารถจำกัดการเข้าถึงในระดับ Path levelควร
note

ความปลอดภัยของ API เป็นประเด็นสำคัญ การมีช่องโหว่ในระบบเป็นการเปิดช่องทางให้ผู้โจมตีดำเนินกิจกรรมบางอย่างที่เป็นอันตรายได้

ประเด็นอื่น ๆ เกี่ยวกับความปลอดภัย

ก่อนการพัฒนา API นักพัฒนาต้องระบุช่องโหว่และการแก้ไขข้อบกพร่องด้านความปลอดภัยเป็นไปได้ที่อาจเกิดขึ้น (Threat Modeling) มิฉะนั้นอาจจะโดนคุกคามฐานข้อมูลและระบบ

  • ใช้ SSL เสมอสำหรับทุก APIs
  • ใช้มาตรฐานสำหรับการ Authentication และ Authorization เช่น JWT หรือ OAuth2 สำหรับการ Authenticate API ก่อนการ Responding ข้อมูลใด ๆ ที่สำคัญในขั้นตอน Request
  • อย่าเก็บข้อมูลที่สำคัญ (Sensitive data) ใน JWT payload ที่ง่ายสำหรับการ Decoding
  • ใช้การ Encryption ทั้งหมดสำหรับข้อมูลที่สำคัญ (Sensitive data) อย่าเก็บ Raw password ใน databases โดยให้ Encrypt Password ก่อนการบันทึกเสมอ
  • ใช้วิธีการกำหนด Rate Limiting สำหรับการโจมตีแบบ DDoS attacks/brute-force
    • Return 429 “Too Many Requests” ใช้สำหรับการแจ้งเกิดเหตุการณ์ Too many requests ที่มาจาก Origin เดียวกันอย่างรวดเร็ว
    • เพิกถอน (Revoke) ข้อมูล Client credential หรือทำ Blacklist ได้เสมอหากไคลเอ็นต์ละเมิดการใช้งาน API
  • Validate inputs request ทั้งหมดก่อน Responding คำขอใด ๆ เพราะการปล่อยให้ข้อมูลที่ไม่ถูกต้องนี้เข้าสู่แอปพลิเคชันอาจทำให้เกิดผลลัพธ์ที่ไม่ดีที่ไม่สามารถคาดเดาได้
  • อย่าส่งผ่านข้อมูลที่ละเอียดอ่อน (Sensitive data) ใน URL เช่น รหัสผ่าน JWT token หรือ API keys เนื่องจากข้อมูลนี้อาจจะเก็บไว้ในบันทึกของเบราว์เซอร์และเซิร์ฟเวอร์

Characteristics of Good Restful APIs

  • พิจารณาขนาดความยาวของ Parameter lists ด้วย
  • สามารถใช้ Pagination และ Sorting ด้วย date กำหนดตัวเลขขนาดของ Records ต่อหน้าได้
  • กำหนด Versioning ของ API อย่างเหมาะสม
  • อ่านง่ายและใช้งานง่ายสำหรับ ควรทำตามที่ชื่อและโปรโตคอลที่แนะนำ
  • เพื่อประสิทธิภาพพยามให้การทำงานไม่มีวิธีใดขึ้นอยู่กับผลลัพธ์ของอีกวิธีหนึ่ง (Stateless)
  • สามารถจัดการข้อผิดพลาดได้ด้วย HTTP Status codes
  • ความเหมาะสมของ Parameter names ความเหมาะสมของ Naming conventions ความเหมาะสมของ Lowercase letters เพื่อใช้ใน URI paths (อย่าใช้คำย่อ)
  • ความพร้อมใช้งานและปลอดภัยสูง
  • มีความสามารถของ API Versioning Pagination และ Sorting
  • มีเอกสาร API Docs ที่ดีเพราะ Developer ส่วนใหญ่นั้นชอบเอกสารอธิบายที่ดี
  • ออกแบบหรือทำให้เรียบง่ายเข้าไว้ K.I.S.S. [keep it simple stupid] 😅

How to scale the API

  • สามารถกำหนด Rate Limiting ได้
  • สถาปัตยกรรม Microservice ซึ่งแต่ละบริการได้รับการออกแบบมาเพื่อให้ทำงานแยกฟังก์ชันของการทำงานออกเป็นบริการส่วน ๆ ซึ่งหากฟังก์ชันใดเกิดมีปัญหาหรือใช้งานไม่ได้ ระบบอื่น ๆ ก็จะยังสามารถทำงานต่อได้ ดั้งนั้นควรใช้บริการหลายตัวบนอินสแตนซ์ที่ทำงานบนเครื่องหลายเครื่อง ด้วยด้านหลังตัวโหลดบาลานเซอร์
  • การทำ Caching static files
  • ใช้ CDN (Content Delivery Network)
  • ทำ indexing กับบางข้อมูล
  • ทำ Filtering และ Pagination แทนที่จะแสดงข้อมูลทั้งหมด
  • หากใช้ RDB ให้ทำ Normalized database และใช้ SQL joins ให้น้อยที่สุด
  • นำ API Gateway มาช่วยในการจัดการ Microservice เช่น Kong

Conclusion

ประเด็นทั้งหมดนี้เป็นเพียงความเห็นส่วนตัว โดยสิ่งเหล่านี้ไม่ใช่กฎที่ตายตัว แต่เป็นเพียงเคล็ดลับจากประสบการณ์ส่วนตัว 😅  เพราะเนื่องจากการออกแบบและการกำหนดรูปแบบมาตรฐาน API นั้นไม่ได้มีมาตรฐานตายตัว โดยสิ่งที่สำคัญควรอธิบายและตกลงกับทีมพัฒนาให้ชัดเจนนั้นเป็นสิ่งสำคัญ ไม่ว่าจะเป็นการตั้งชื่อ URL Path การตั้งชื่อ Attributes การกำหนด JSON format เป็นต้น

แต่สิ่งที่สำคัญสูงสุดเมื่อ API ได้ Public แล้ว สำหรับความปลอดภัยในบาง Endpoints นั้นก็เป็นเรื่องที่สำคัญ การ Revoke ต้องสามารถทำได้หากไคลเอ็นต์ละเมิดการใช้งาน API และพิจารณาการใช้งาน API แบบ Stateless และ Stateful ควบคู่กันได้ในบางกรณี

นอกจากนี้ยังมีข้อมูลที่น่าสนใจสามารถเอามาปรับใช้ร่วมกับการออกแบบและใช้อ้างอิงเพื่อนำมาปรับใช้เกี่ยวกับการออกแบบสเปกของ JSON รายละเอียดดังนี้ JSON API Specification

References

Loading...