เคยไหมครับ ที่ต้องเชื่อมต่อระบบของตัวเองเข้ากับบริการภายนอก เช่น ระบบบัญชี ระบบจัดส่งสินค้า หรือ Payment Gateway แล้วต้องมานั่งเขียนโค้ดเรียก API ด้วย cURL เอง ตั้งค่า Header เอง แปลง JSON เอง จัดการ Error เอง... ทำซ้ำแบบนี้ทุกครั้งที่มี Endpoint ใหม่ จนโค้ดในโปรเจกต์เต็มไปด้วยฟังก์ชันเรียก API ที่หน้าตาคล้ายกันแต่กระจัดกระจายอยู่ทั่วทุกที่
ปัญหานี้พบได้บ่อยมากในงานพัฒนาเว็บแอปด้วย PHP โดยเฉพาะตอนที่ต้องเชื่อมต่อกับบริการอย่าง QuickBooks สำหรับงานบัญชี หรือระบบอีคอมเมิร์ซต่าง ๆ ที่มี API ให้เรียกใช้จำนวนมาก ถ้าทุกอย่างต้องเขียนเองด้วย cURL ดิบ ๆ โอกาสที่โค้ดจะซ้ำซ้อนและบั๊กแอบซ่อนอยู่นั้นสูงมาก
บทความนี้จะพาไปดูว่า "SDK" (Software Development Kit) คืออะไร ทำไมการใช้ SDK ที่มีคนทำไว้แล้ว หรือการสร้าง SDK ของตัวเอง ถึงช่วยลดโค้ดได้เป็นร้อยบรรทัด พร้อมตัวอย่างการนำไปใช้งานจริงใน CodeIgniter 4 และ Laravel สำหรับมือใหม่ที่อยากเขียนโค้ดให้สะอาดและดูแลง่ายขึ้น
SDK คืออะไร แล้วต่างจากการเรียก API ตรง ๆ ยังไง
SDK ก็คือชุดไลบรารีที่ห่อหุ้ม (wrap) การเรียก API ของบริการหนึ่ง ๆ ไว้ในรูปแบบของคลาสและฟังก์ชันที่ใช้งานง่าย แทนที่เราจะต้องจำ Endpoint, Header, รูปแบบ JSON เอง SDK จะเตรียมเมธอดสำเร็จรูปให้เรียกใช้ตรง ๆ เช่น แทนที่จะต้องยิง cURL ไปที่ /v3/company/123/customer พร้อมแนบ Token เอง เราอาจแค่เรียก $client->customers()->create($data) เพียงบรรทัดเดียว
💡 มุมมองง่าย ๆลองนึกภาพว่า API คือ "ปลั๊กไฟดิบ" ที่ต้องต่อสายเอง ระมัดระวังเรื่องไฟดูด ส่วน SDK คือ "ปลั๊กพ่วงพร้อมสวิตช์" ที่ต่อแล้วใช้ได้เลย ปลอดภัยกว่า และมีคนทดสอบมาให้แล้วว่าใช้งานได้จริง
ตัวอย่างปัญหา: เรียก API ด้วย cURL ดิบ ๆ
ลองดูตัวอย่างการเรียก API เพื่อสร้างลูกค้าใหม่ในระบบบัญชีแบบ QuickBooks ด้วย cURL ตรง ๆ ดูครับ จะเห็นว่าต้องเขียนโค้ดจัดการ Header, Token, และ Error เองทั้งหมด
<?php
function createCustomerRaw($accessToken, $realmId, $customerData)
{
$url = "https://sandbox-quickbooks.api.intuit.com/v3/company/{$realmId}/customer";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$accessToken}",
"Content-Type: application/json",
"Accept: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($customerData));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL Error: {$error}");
}
curl_close($ch);
if ($httpCode >= 400) {
throw new Exception("API Error ({$httpCode}): {$response}");
}
return json_decode($response, true);
}
// ทุกครั้งที่ต้องเรียก Endpoint ใหม่ ต้องเขียนซ้ำแบบนี้อีก...
function getCustomerRaw($accessToken, $realmId, $customerId)
{
$url = "https://sandbox-quickbooks.api.intuit.com/v3/company/{$realmId}/customer/{$customerId}";
// ... โค้ด cURL เหมือนเดิมอีกชุด
}
จะเห็นว่าแค่ 2 ฟังก์ชันก็ปาไปแล้วเกือบ 40 บรรทัด และถ้าระบบมี Endpoint ที่ต้องใช้สัก 10-20 จุด โค้ดส่วนที่ทำซ้ำ ๆ (ตั้ง Header, เช็ค Error, แปลง JSON) จะเกิดขึ้นนับร้อยบรรทัด ซึ่งถ้าวันหนึ่งบริการเปลี่ยนวิธี Authentication เราต้องไปแก้ทุกจุดที่เขียนซ้ำไว้
ใช้ SDK แทน: ลดโค้ดเหลือไม่กี่บรรทัด
ถ้าบริการนั้นมี SDK สำหรับ PHP ให้ใช้ (เช่นโหลดผ่าน Composer) เราสามารถลดงานข้างต้นทั้งหมดให้เหลือเพียงไม่กี่บรรทัด เพราะ SDK จัดการเรื่อง Authentication, Header, และการแปลงข้อมูลให้เราหมดแล้ว
<?php
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Data\IPPCustomer;
$dataService = DataService::Configure([
'auth_mode' => 'oauth2',
'ClientID' => env('QB_CLIENT_ID'),
'ClientSecret' => env('QB_CLIENT_SECRET'),
'accessTokenKey' => $accessToken,
'refreshTokenKey' => $refreshToken,
'baseUrl' => 'development',
]);
$customer = new IPPCustomer();
$customer->DisplayName = $customerData['name'];
$customer->PrimaryEmailAddr = ['Address' => $customerData['email']];
$result = $dataService->Add($customer);
$error = $dataService->getLastError();
if ($error) {
throw new Exception($error->getResponseBody());
}
จะเห็นว่าเราไม่ต้องไปยุ่งกับ cURL, Header หรือ URL ของ Endpoint เลย SDK จัดการให้หมด สิ่งที่เราทำคือ "ตั้งค่า" และ "เรียกใช้เมธอด" เท่านั้น โค้ดสั้นลง อ่านง่ายขึ้น และลดความเสี่ยงเรื่องบั๊กจากการเขียน HTTP Request เองไปได้มาก
นำ SDK มาใช้ใน CodeIgniter 4
ใน CodeIgniter 4 เราสามารถสร้าง Service หรือ Library Class แยกไว้สำหรับห่อหุ้มการเรียกใช้ SDK อีกชั้นหนึ่ง เพื่อให้ Controller เรียกใช้งานได้สะดวก และถ้าวันหนึ่งต้องเปลี่ยน SDK ก็แก้แค่จุดนี้ที่เดียว
<?php
namespace App\Libraries;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Data\IPPCustomer;
class AccountingService
{
protected DataService $dataService;
public function __construct()
{
$this->dataService = DataService::Configure([
'auth_mode' => 'oauth2',
'ClientID' => getenv('QB_CLIENT_ID'),
'ClientSecret' => getenv('QB_CLIENT_SECRET'),
'accessTokenKey' => getenv('QB_ACCESS_TOKEN'),
'refreshTokenKey' => getenv('QB_REFRESH_TOKEN'),
'baseUrl' => 'development',
]);
}
public function createCustomer(array $data): array
{
$customer = new IPPCustomer();
$customer->DisplayName = $data['name'];
$customer->PrimaryEmailAddr = ['Address' => $data['email']];
$result = $this->dataService->Add($customer);
$error = $this->dataService->getLastError();
if ($error) {
throw new \RuntimeException($error->getResponseBody());
}
return (array) $result;
}
}
<?php
namespace App\Controllers;
use App\Libraries\AccountingService;
class CustomerController extends BaseController
{
public function store()
{
$accounting = new AccountingService();
try {
$result = $accounting->createCustomer([
'name' => $this->request->getPost('name'),
'email' => $this->request->getPost('email'),
]);
return $this->response->setJSON([
'status' => 'success',
'data' => $result,
]);
} catch (\RuntimeException $e) {
return $this->response->setStatusCode(500)
->setJSON(['status' => 'error', 'message' => $e->getMessage()]);
}
}
}
สังเกตว่า Controller ไม่รู้เรื่องรายละเอียดการเชื่อมต่อ API เลยแม้แต่นิดเดียว มันแค่เรียก createCustomer() แล้วรับผลลัพธ์กลับมา ถ้าวันหนึ่งเปลี่ยนผู้ให้บริการบัญชีจาก QuickBooks เป็นระบบอื่น เราแก้แค่ใน AccountingService เท่านั้น ไม่ต้องไปไล่แก้ทุก Controller
นำ SDK มาใช้ใน Laravel
ใน Laravel แนวคิดเดียวกันนี้ทำได้สะดวกขึ้นด้วยการใช้ Service Container ผ่านการสร้าง Service Provider แล้ว Bind คลาส Service ของเราไว้ เพื่อให้สามารถ Inject เข้าไปใช้ใน Controller ผ่าน Dependency Injection ได้เลย
<?php
namespace App\Services;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Data\IPPCustomer;
class AccountingService
{
protected DataService $dataService;
public function __construct()
{
$this->dataService = DataService::Configure([
'auth_mode' => 'oauth2',
'ClientID' => config('services.quickbooks.client_id'),
'ClientSecret' => config('services.quickbooks.client_secret'),
'accessTokenKey' => config('services.quickbooks.access_token'),
'refreshTokenKey' => config('services.quickbooks.refresh_token'),
'baseUrl' => 'development',
]);
}
public function createCustomer(array $data): array
{
$customer = new IPPCustomer();
$customer->DisplayName = $data['name'];
$customer->PrimaryEmailAddr = ['Address' => $data['email']];
$result = $this->dataService->Add($customer);
if ($error = $this->dataService->getLastError()) {
throw new \RuntimeException($error->getResponseBody());
}
return (array) $result;
}
}
<?php
namespace App\Http\Controllers;
use App\Services\AccountingService;
use Illuminate\Http\Request;
class CustomerController extends Controller
{
public function __construct(protected AccountingService $accounting) {}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
]);
try {
$result = $this->accounting->createCustomer($validated);
return response()->json(['status' => 'success', 'data' => $result]);
} catch (\RuntimeException $e) {
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
}
}
}
เพราะ Laravel มี Dependency Injection มาในตัว เราจึงไม่ต้อง new AccountingService() เองในทุก Controller เพียงแค่ใส่ Type-hint ใน Constructor ระบบจะสร้าง Object ให้อัตโนมัติ ทำให้การ Mock สำหรับ Unit Test ก็ทำได้ง่ายขึ้นด้วย
เปรียบเทียบ: เรียก API ตรง ๆ vs ใช้ SDK
| หัวข้อ | เรียก API ด้วย cURL ตรง ๆ | ใช้ SDK |
|---|
| จำนวนโค้ดต่อ Endpoint | 20-40 บรรทัด ต่อจุด | 3-10 บรรทัด ต่อจุด |
| การจัดการ Authentication | ต้องเขียนเอง ทุกจุด | ตั้งค่าครั้งเดียว ใช้ได้ทุกที่ |
| การจัดการ Error | เขียนเอง เสี่ยงพลาด | มี Method ตรวจสอบให้ |
| การอัปเดตเมื่อ API เปลี่ยน | ต้องไล่แก้ทุกจุดในโค้ด | อัปเดต Package ผ่าน Composer |
| ความเหมาะสมกับมือใหม่ | เข้าใจหลักการ HTTP ดี แต่ยุ่งยาก | เริ่มต้นได้เร็ว เน้นที่ Logic ธุรกิจ |
ถ้าบริการไม่มี SDK ให้ ทำยังไง
ไม่ใช่ทุกบริการที่จะมี SDK สำหรับ PHP ให้พร้อมใช้งาน บางครั้งมีแค่เอกสาร API แบบ REST เปล่า ๆ ในกรณีนี้ เราสามารถสร้าง "Mini SDK" ของตัวเองขึ้นมาได้ง่าย ๆ โดยใช้หลักการเดียวกัน คือห่อหุ้มการเรียก HTTP ไว้ในคลาสเดียว แล้วเปิดเมธอดให้ใช้งานเฉพาะที่จำเป็น
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class SimpleApiClient
{
public function __construct(
protected string $baseUrl,
protected string $apiKey
) {}
public function get(string $endpoint, array $query = []): array
{
$response = Http::withToken($this->apiKey)
->get("{$this->baseUrl}/{$endpoint}", $query);
return $this->handleResponse($response);
}
public function post(string $endpoint, array $data = []): array
{
$response = Http::withToken($this->apiKey)
->post("{$this->baseUrl}/{$endpoint}", $data);
return $this->handleResponse($response);
}
protected function handleResponse($response): array
{
if ($response->failed()) {
throw new \RuntimeException(
"API request failed: {$response->status()} - {$response->body()}"
);
}
return $response->json();
}
}
เพียงเท่านี้ เราก็มี "SDK เล็ก ๆ" ของตัวเองที่ใช้ซ้ำได้ทั่วทั้งโปรเจกต์ ไม่ว่าจะเป็นการเรียก API ของระบบจัดส่งสินค้า ระบบแจ้งเตือน หรือ Payment Gateway ก็สามารถสร้างคลาสลูกที่ Extend จาก SimpleApiClient นี้ได้ ทำให้โค้ดในโปรเจกต์เป็นระเบียบและทดสอบได้ง่าย
⚠️ ข้อควรระวังไม่ว่าจะใช้ SDK ของผู้ให้บริการ หรือสร้างเอง อย่าเก็บ API Key หรือ Token ไว้ใน Source Code ตรง ๆ ให้ใช้ไฟล์ .env และเรียกผ่าน config() หรือ getenv() เสมอ เพื่อความปลอดภัยและสะดวกในการสลับค่าระหว่าง Development กับ Production
สรุป
การเรียก API ด้วย cURL ดิบ ๆ ไม่ใช่เรื่องผิด แต่ถ้าโปรเจกต์มีการเชื่อมต่อกับบริการภายนอกหลายจุด การใช้ SDK ที่มีคนทำไว้แล้ว หรือสร้าง SDK เล็ก ๆ ของตัวเองในรูปแบบ Service Class จะช่วยให้โค้ดสะอาดขึ้น ลดความซ้ำซ้อน และทำให้การดูแลรักษาในระยะยาวง่ายขึ้นมาก ทั้งใน CodeIgniter 4 ที่ใช้ Library Class และใน Laravel ที่ใช้ Service Provider กับ Dependency Injection หลักการสำคัญคือ "แยกรายละเอียดการเชื่อมต่อ API ออกจาก Business Logic" เพื่อให้ทั้งสองส่วนเปลี่ยนแปลงได้อย่างเป็นอิสระจากกัน
สำหรับมือใหม่ ขอแนะนำให้เริ่มจากการสร้าง Service Class ง่าย ๆ แบบในตัวอย่างนี้ก่อน แล้วค่อย ๆ ขยายเมธอดเพิ่มตามที่โปรเจกต์ต้องการ เมื่อคุ้นเคยแล้วจะพบว่าการจัดการ API หลาย ๆ ตัวในโปรเจกต์ใหญ่ ๆ ไม่ได้น่ากลัวอย่างที่คิด