รู้จัก Callable Function ใน Cloud Functions for Firebase ตั้งแต่ Zero จนเป็น Hero
พูดถึงการรับส่งข้อมูลระหว่าง client และ server ในยุคนี้ เชื่อว่าหลายๆคนก็เลือกใช้บริการที่เป็น BaaS(Backend-as-a-Service) เพราะมันช่วยประหยัดเรื่องของ resource แถมลดเวลาในการพัฒนาได้เยอะ และผมเชื่อว่า Cloud Functions for Firebase ก็เป็นหนึ่งตัวเลือกที่นักพัฒนาไทยนิยมใช้กัน
โดยวิธีที่นักพัฒนาส่วนมากใช้ในการพัฒนาเพื่อรับส่งข้อมูลระหว่าง client และ server ด้วย Cloud Functions ที่ผมได้เจอมา ก็จะเป็นการสร้าง API แบบที่เรียกว่า HTTP Function ซึ่งรองรับ GET
, POST
, PUT
, DELETE
และ OPTIONS
แต่…คุณรู้หรือไม่ว่า มันมีวิธีที่ง่ายกว่าในการพัฒนา ที่รู้จักกันในชื่อว่า Callable Function
Callable Function
คือฟังก์ชันที่ให้ client อย่าง Web
, Android
, iOS
, Unity
และ C++
สามารถเรียกใช้งานได้ตรงๆ โดยการ deploy ฟังก์ชันแบบ Callable Function คุณจะไม่ได้ Endpoint ของ API ออกมา แต่คุณสามารถเชื่อมต่อ client กับ server ได้ง่ายกว่าเดิมผ่าน SDK โดยความพิเศษใน Callable Function มีดังนี้
- Firebase Auth token, FCM token และ App Check token จะแนบมากับ request ให้อัตโนมัติ(ถ้ามีการ implement บริการเหล่านี้ไว้ที่ฝั่ง client)
- ถ้ามี Firebase Auth token หรือ App Check token แนบมากับ request มันจะถูก validate ให้อัตโนมัติที่ฝั่ง server ซึ่งแปลว่า ถ้า token ถูก revoke หรือ expire มา ตัว request นั้นจะถูก reject กลับไป
เอาหละครับ บทความนี้ผมจะพาทุกคนไปเรียนรู้เรื่องราวของ Callable Function ตามหัวข้อดังต่อไปนี้
- การพัฒนาฟังก์ชันแบบ Callable Function
- การเชื่อมต่อ Client เพื่อรับส่งข้อมูล
- Tip สำหรับคนอยาก Run ใน Local ด้วย Firebase Emulator Suite
1. การพัฒนาฟังก์ชันแบบ Callable Function
1.1 เตรียมความพร้อม
ขั้นตอนนี้นักพัฒนาจะต้องมีโปรเจค Firebase, ต้องติดตั้ง Firebase CLI และ Cloud Functions ให้เรียบร้อยก่อน โดยใครที่ยังไม่เคยพัฒนาฟังก์ชันใน Cloud Functions มาก่อน ผมแนะนำให้อ่านและทำตามหัวข้อ “การติดตั้ง” ในบทความด้านล่างนี้
แต่สำหรับใครที่มีประสบการณ์การพัฒนาฟังก์ชันใน Cloud Functions มาแล้วให้ข้ามไปข้อถัดไปได้เลย
1.2 เริ่มต้นเขียนฟังก์ชัน
โครงสร้างของ Callable Function จะมีรูปแบบที่ขึ้นต้นด้วย functions.https.onCall
และจะมี params คืนมา 2 ตัวคือ data และ context โดยโค้ดที่จะเขียนลงไปใน index.js ก็จะเป็นประมาณนี้
const functions = require("firebase-functions")exports.myCallable = functions.https.onCall((data, context) => {
// ...
})
โดยใน data คุณสามารถดึงค่าต่างๆที่ถูกส่งมาจากฝั่ง client ได้
const msg = data.msg
ส่วนใน context คุณก็สามารถดึงข้อมูลจาก Firebase Auth, FCM และ Firebase App Check ได้(ข้อมูลจะแนบมาอัตโนมัติ กรณีที่มีการใช้งานที่ฝั่ง client)
// Firebase Auth
const uid = context.auth.uid
const name = context.auth.token.name || null
const picture = context.auth.token.picture || null
const email = context.auth.token.email || null// FCM
const fcmToken = context.instanceIdToken || null// Firebase App Check
if (context.app) {
const rawToken = context.rawRequest.header['X-Firebase-AppCheck']
}
1.3 การตอบกลับข้อมูล
การ return ข้อมูลกลับไปที่ client เราสามารถส่งข้อมูลกลับไปเป็นรูปแบบของ text หรือจะเป็นแบบ JSON ก็ได้ง่ายๆแบบนี้เลย
return {
result: data.msg + ', Feb 14th, 2022'
}
1.4 การจัดการ Error
เราสามารถ throw error ไปพร้อมกับข้อความ กรณีที่ต้องการ return promise reject ได้ด้วย functions.https.HttpsError(FunctionsErrorCode, ErrorMessage)
เพื่อให้ฝั่ง client สามารถเข้าใจ error และนำไปแสดงผลได้ เช่น
if (!context.auth) {
throw new functions.https.HttpsError(
'failed-precondition',
'You must be authenticated'
)
}
1.5 เลือก Location เพื่อเพิ่มความเร็ว
เมื่อคุณ deploy ฟังก์ชัน โค้ดของคุณจะถูกส่งไปทำงานที่เมกา(us-central1) เป็นค่าเริ่มต้น ซึ่งแน่นอนว่าการรับส่งข้อมูลจากไทยไปเมกาจะมี network latency เกิดขึ้น
ดังนั้นเพื่อลด latency ที่อาจเกิดขึ้น นักพัฒนาสามารถเลือก location ที่จะทำการ deploy โดยคำนึงถึงระยะทาง และบริการอื่นๆที่เกี่ยวข้องในฟังก์ชันนั้น เพื่อให้ได้ประสิทธิภาพดีที่สุด ซึ่งวิธีการหลังจากเลือก location ได้แล้วก็เพียงเพิ่ม region
ไปในตอนประกาศฟังก์ชันแบบนี้
exports.myCallable = functions.region('asia-southeast1').https.onCall((data, context) => {
// ...
})
1.6 Deploy
เมื่อเขียนโค้ดได้ตามต้องการแล้ว ก็ให้เปิด Terminal แล้วเข้าไปที่โฟลเดอร์ /functions ของโปรเจคจากนั้นก็เรียกคำสั่ง deploy ได้เลย
firebase deploy
2. การเชื่อมต่อ Client เพื่อรับส่งข้อมูล
การเชื่อมต่อเพื่อรับส่งข้อมูลระหว่าง client กับ Callable Function เราจะใช้ Firebase client SDK เข้ามาเป็นตัวกลางในการเชื่อมต่อ โดย minimum SDK ที่รองรับมีดังนี้
- Firebase SDK for Apple platforms 8.11.0
- Firebase SDK for Android 20.0.1
- Firebase JavaScript SDK 8.10.1
- Firebase Modular Web SDK v. 9.0
สำหรับตัวอย่างในบทความนี้ ผมจะทำการเชื่อมต่อด้วย Firebase Modular Web SDK นะครับ
สำหรับ JavaScript SDK v8, Android, iOS, Unity และ C++ ดูวิธีการเชื่อมต่อที่นี่
2.1 ลงทะเบียน Web app
ให้เข้าไปที่ Firebase console เลือกโปรเจคที่เราสร้างไว้ โดยเมื่อเข้าสู่หน้า Overview ให้คลิก Add app
ตามรูปด้านล่าง
ต่อไปให้คลิกเลือก Web
ตั้งชื่อ Web app ของเราให้ง่ายต่อการจดจำ แล้วกดปุ่ม Register app
ขั้นตอนถัดไป ให้เลือกวิธีการพัฒนาที่ถนัด โดยจะมีแบบ npm และ <script> (ตัวอย่างจะเลือกแบบ <script>) จากนั้นให้ copy ชุด JavaScript เอาไว้ แล้วกด Continue to console
ได้เลย
2.2 เชื่อมต่อ Callable Function กับ SDK
จากโค้ดที่เรา copy มาจากขั้นตอนที่ 2.1 ให้เราเพิ่มโค้ดตามขั้นตอนทั้ง 4 นี้ในไฟล์ HTML ที่ต้องการ
สำคัญมาก: กรณี Callable Function ที่สร้างในข้อ 1 มีการระบุ location คุณจำเป็นต้องกำหนด location ให้ฝั่ง client ด้วยโดยการแก้ไขโค้ดด้านบนในบรรทัดที่ 24 เป็นดังนี้
const functions = getFunctions(app, "asia-southeast1");
สำหรับ JavaScript SDK v8, Android, iOS, Unity, C++ ดูวิธีกำหนด location ที่นี่
2.3 Deploy และ Demo
ถ้าโค้ดพร้อมแล้ว ก็ deploy โลด ปล.ถ้าใครยังไม่มีโฮสฟรีไว้ใช้ ผมนี่แนะนำ Firebase Hosting เลยครับ :)
เสร็จแล้วมาลองเอา URL ที่ได้ไปเปิดใน browser ดูผลลัพธ์กัน
3. Tip สำหรับคนอยาก Run ใน Local ด้วย Firebase Emulator Suite
เชื่อว่านักพัฒนาหลายคนที่ใช้ Firebase ต่างก็ใช้ Firebase Emulator Suite เพื่อทดสอบโปรเจคของตัวเองใน local เพราะมันรวดเร็ว แก้ปุ๊บเห็นผลปั๊บไม่ต้อง deploy ไม่กระทบข้อมูลใน Production
สำหรับ Callable Function ก็สามารถพัฒนาใน local ได้เช่นกัน เพียงแต่การ run ฝั่ง client ที่เชื่อมต่อ เราจะต้องมีการเขียนโค้ดเพิ่มเข้าไปนิสนึง โดยโค้ดจะอ้างอิงจากข้อ 2.2 บรรทัดที่ 13 ให้คุณเพิ่ม connectFunctionsEmulator
เข้าไป
import { getFunctions, httpsCallable, connectFunctionsEmulator } from "https://www.gstatic.com/firebasejs/9.6.6/firebase-functions.js";
และเพิ่มคำสั่งด้านล่างนี้ ต่อจากบรรทัดที่ 24
const functions = getFunctions(app);
connectFunctionsEmulator(functions, "localhost", 5001);
สำหรับ JavaScript SDK v8, Android และ iOS ดูวิธีกำหนด config ที่นี่
สรุป
เป็นอย่างไรบ้างครับสำหรับ Callable Function นอกจากจะพัฒนาง่ายแล้ว ส่วนตัวผมยังชอบที่เราไม่ต้องระบุ Endpoint ของ API ลงไปในโค้ดของฝั่ง client เลย(ดูเท่ และปลอดภัยขึ้นไปอีกนิสนึง)
ก่อนจากกันไป หากคุณชอบบทความนี้ก็ฝากกด Clap เป็นกำลังใจให้ผม หากมันเป็นประโยชน์ก็ฝากกด Share ให้เพื่อนของคุณ และหากคุณไม่อยากพลาดบทความถัดๆไปของซีรี่ย์ Zero to Hero ก็ฝากกด Follow: Firebase Thailand ไว้ด้วยนะครับ สำหรับวันนี้ต้องขอตัวลาไปก่อน แล้วพบกันใหม่บทความหน้าครับ