อัปเกรดฟังก์ชัน Node.js รุ่นที่ 1 เป็นรุ่นที่ 2

แอปที่ใช้ฟังก์ชันรุ่นที่ 1 ควรพิจารณาการย้ายข้อมูลไปยังรุ่นที่ 2 โดยใช้วิธีการในคำแนะนำนี้ ฟังก์ชันรุ่นที่ 2 ใช้ Cloud Run เพื่อให้Cloud Runประสิทธิภาพ การกำหนดค่า การตรวจสอบ และอื่นๆ ที่ดียิ่งขึ้น

ตัวอย่างในเอกสารนี้ถือว่าคุณใช้ JavaScript กับโมดูล CommonJS (การนำเข้าสไตล์ require) แต่หลักการเดียวกันนี้ใช้ได้กับ JavaScript ที่มี ESM (การนำเข้าสไตล์ import … from) และ TypeScript

กระบวนการย้ายข้อมูล

ฟังก์ชันรุ่นที่ 1 และรุ่นที่ 2 สามารถอยู่ร่วมกันในไฟล์แหล่งที่มาเดียวกันได้ ซึ่งจะช่วยให้คุณย้ายฐานของโค้ดทีละส่วนได้เมื่อพร้อม โปรดทราบว่าการผสมแพ็กเกจนี้ใช้ไม่ได้ภายในฟังก์ชันเดียวที่แยกกัน

เราขอแนะนำให้ย้ายข้อมูลฟังก์ชันทีละรายการ ทำการทดสอบและ ยืนยันก่อนดำเนินการต่อ

ยืนยันเวอร์ชัน Firebase CLI และ firebase-functions

ตรวจสอบว่าคุณใช้ Firebase CLI เวอร์ชัน 12.00 และ firebase-functions เวอร์ชัน 4.3.0 เป็นอย่างน้อย ส่วนเวอร์ชันใหม่กว่าจะรองรับทั้งรุ่นที่ 2 และรุ่นที่ 1

อัปเดตการนำเข้า

นำเข้าฟังก์ชันรุ่นที่ 2 จากแพ็กเกจย่อย v2 ใน SDK firebase-functions เส้นทางการนำเข้าที่แตกต่างกันนี้คือสิ่งที่ Firebase CLI ต้องใช้ในการพิจารณา ว่าจะติดตั้งใช้งานโค้ดฟังก์ชันเป็นฟังก์ชันรุ่นที่ 1 หรือรุ่นที่ 2

v2 Subpackage เป็นแบบแยกส่วน และเราขอแนะนำให้นำเข้าเฉพาะโมดูลที่ต้องการ เท่านั้น

ก่อน: รุ่นที่ 1

const functions = require("firebase-functions/v1");

หลัง: รุ่นที่ 2

// explicitly import each trigger
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

อัปเดตคำจำกัดความของทริกเกอร์

เนื่องจาก SDK รุ่นที่ 2 จะใช้การนำเข้าแบบแยกส่วน ให้อัปเดตคำจำกัดความของทริกเกอร์เพื่อ แสดงการนำเข้าที่เปลี่ยนแปลงจากขั้นตอนก่อนหน้า

อาร์กิวเมนต์ที่ส่งไปยังการเรียกกลับสำหรับทริกเกอร์บางรายการมีการเปลี่ยนแปลง ใน ตัวอย่างนี้ โปรดสังเกตว่าอาร์กิวเมนต์ของแฮนเดิล onDocumentCreated ได้รับการ รวมเป็นออบเจ็กต์ event เดียว นอกจากนี้ ทริกเกอร์บางรายการยังมี ฟีเจอร์การกำหนดค่าใหม่ที่สะดวก เช่น ตัวเลือก cors ของonRequestทริกเกอร์

ก่อน: รุ่นที่ 1

const functions = require("firebase-functions/v1");

exports.date = functions.https.onRequest((req, res) => {
  // ...
});

exports.uppercase = functions.firestore
  .document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

หลัง: รุ่นที่ 2

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

exports.date = onRequest({cors: true}, (req, res) => {
  // ...
});

exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  /* ... */
});

ลดความพยายามในการเขียนใหม่ด้วยการแยกโครงสร้าง JavaScript

หากฟังก์ชันมีเนื้อหาที่ซับซ้อนซึ่งต้องอาศัยบริบทรุ่นที่ 1 หรือพารามิเตอร์เฉพาะผู้ให้บริการ (เช่น message หรือ snapshot) เป็นอย่างมาก คุณสามารถใช้ตัวช่วยความเข้ากันได้กับรุ่นที่ 1 ซึ่งมีอยู่ใน SDK รุ่นที่ 2

SDK รุ่นที่ 2 จะแก้ไขออบเจ็กต์เหตุการณ์โดยอัตโนมัติด้วยตัวรับที่ตรงกับ ลายเซ็นรุ่นที่ 1 ซึ่งจะช่วยให้คุณใช้การแยกโครงสร้าง JavaScript เพื่อดึงพร็อพเพอร์ตี้เหล่านี้ได้โดยตรงในลายเซ็นของตัวแฮนเดิล ซึ่งจะช่วยลดความจำเป็นในการเขียนตรรกะของฟังก์ชันใหม่

ข้อมูลอ้างอิงการแมปผู้ให้บริการ

ผู้ให้บริการ อาร์กิวเมนต์รุ่นที่ 1 การแยกโครงสร้างเหตุการณ์ที่แก้ไขแล้วรุ่นที่ 2
Pub/Sub (message, context) ({ message, context }) => { ... }
Cloud Firestore (snapshot, context) ({ snapshot, context }) => { ... }
Cloud Storage (object, context) ({ object, context }) => { ... }
Realtime Database (snapshot, context) ({ snapshot, context }) => { ... }
Remote Config (version, context) ({ version, context }) => { ... }
Cloud Scheduler (context) ({ context }) => { ... }
คิวงาน (data, context) ({ data, context }) => { ... }

ก่อน (รุ่นที่ 1):

export const myPubSubV1 = functions.pubsub.topic("my-topic").onPublish((message, context) => {
  const data = message.json;
  const eventId = context.eventId;
  // ... rest of the logic
});

ทางเลือกใหม่ (รุ่นที่ 2 ที่มีการแยกโครงสร้าง):

import { onMessagePublished } from "firebase-functions/v2/pubsub";

export const myPubSubV2 = onMessagePublished("my-topic", ({ message, context }) => {
  // No need to change the function body!
  const data = message.json;      // Uses v1 Message wrapper
  const eventId = context.eventId; // Uses v1 EventContext map
  // ... rest of the logic
});

ใช้การกำหนดค่าแบบพารามิเตอร์

ฟังก์ชันรุ่นที่ 2 จะเลิกการรองรับ functions.config เพื่อให้มีอินเทอร์เฟซที่ปลอดภัยยิ่งขึ้นสำหรับการกำหนดพารามิเตอร์การกำหนดค่าแบบประกาศภายในฐานของโค้ด โมดูล params ใหม่จะบล็อกการติดตั้งใช้งาน CLI เว้นแต่พารามิเตอร์ทั้งหมด จะมีค่าที่ถูกต้อง เพื่อให้มั่นใจว่าจะไม่มีการติดตั้งใช้งานฟังก์ชันที่มีการกำหนดค่า ที่ขาดหายไป

ก่อน: รุ่นที่ 1

const functions = require("firebase-functions/v1");

exports.getQuote = functions.https.onRequest(async (req, res) => {
  const quote = await fetchMotivationalQuote(functions.config().apiKey);
  // ...
});

หลัง: รุ่นที่ 2

const {onRequest} = require("firebase-functions/v2/https");
const {defineSecret} = require("firebase-functions/params");

// Define the secret parameter
const apiKey = defineSecret("API_KEY");

exports.getQuote = onRequest(
  // make the secret available to this function
  { secrets: [apiKey] },
  async (req, res) => {
    // retrieve the value of the secret
    const quote = await fetchMotivationalQuote(apiKey.value());
    // ...
  }
);

หากมีการกำหนดค่าสภาพแวดล้อมที่มีอยู่ด้วย functions.config ให้ย้ายข้อมูล การกำหนดค่านี้เป็นส่วนหนึ่งของการอัปเกรดเป็นรุ่นที่ 2

functions.config API เลิกใช้งานแล้วและจะหยุดให้บริการในเดือนมีนาคม 2027 หลังจากวันที่ดังกล่าว การติดตั้งใช้งานที่มี functions.config จะล้มเหลว

หากต้องการป้องกันไม่ให้การติดตั้งใช้งานล้มเหลว ให้ย้ายข้อมูลการกำหนดค่าไปยัง Cloud Secret Manager โดยใช้ Firebase CLI เราขอแนะนำอย่างยิ่งให้ใช้วิธีนี้เนื่องจากเป็นวิธีที่มีประสิทธิภาพและปลอดภัยที่สุดในการย้ายข้อมูลการกำหนดค่า

  1. การกำหนดค่าการส่งออกด้วย Firebase CLI

    ใช้คำสั่ง config export เพื่อส่งออกการกำหนดค่าสภาพแวดล้อมที่มีอยู่ไปยัง Secret ใหม่ใน Cloud Secret Manager

    $ firebase functions:config:export
    i  This command retrieves your Runtime Config values (accessed via functions.config())
       and exports them as a Secret Manager secret.
    
    i  Fetching your existing functions.config() from your project...     Fetched your existing functions.config().
    
    i  Configuration to be exported:
    ⚠  This may contain sensitive data. Do not share this output.
    
    {
       ...
    } What would you like to name the new secret for your configuration? RUNTIME_CONFIG
    
    ✔  Created new secret version projects/project/secrets/RUNTIME_CONFIG/versions/1```
    
  2. อัปเดตโค้ดฟังก์ชันเพื่อเชื่อมโยงข้อมูลลับ

    หากต้องการใช้การกำหนดค่าที่จัดเก็บไว้ใน Secret ใหม่ใน Cloud Secret Manager ให้ใช้ defineJsonSecret API ในแหล่งที่มาของฟังก์ชัน นอกจากนี้ โปรดตรวจสอบว่าได้ เชื่อมโยงข้อมูลลับกับฟังก์ชันทั้งหมดที่ต้องการใช้ข้อมูลลับ

    ก่อน

    const functions = require("firebase-functions/v1");
    
    exports.myFunction = functions.https.onRequest((req, res) => {
      const apiKey = functions.config().someapi.key;
      // ...
    });
    

    หลัง

    const { onRequest } = require("firebase-functions/v2/https");
    const { defineJsonSecret } = require("firebase-functions/params");
    
    const config = defineJsonSecret("RUNTIME_CONFIG");
    
    exports.myFunction = onRequest(
      // Bind secret to your function
      { secrets: [config] },
      (req, res) => {
        // Access secret values via .value()
        const apiKey = config.value().someapi.key;
        // ...
    });
    
  3. ทำให้ฟังก์ชันใช้งานได้

    ทำให้ฟังก์ชันที่อัปเดตใช้งานได้เพื่อใช้การเปลี่ยนแปลงและเชื่อมโยงสิทธิ์ของ Secret

    firebase deploy --only functions:<your-function-name>
    

ตั้งค่าตัวเลือกของรันไทม์

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

ก่อน: รุ่นที่ 1

const functions = require("firebase-functions/v1");

exports.date = functions
  .runWith({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  })
  // locate function closest to users
  .region("asia-northeast1")
  .https.onRequest((req, res) => {
    // ...
  });

exports.uppercase = functions
  // locate function closest to users and database
  .region("asia-northeast1")
  .firestore.document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

หลัง: รุ่นที่ 2

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");
const {setGlobalOptions} = require("firebase-functions/v2");

// locate all functions closest to users
setGlobalOptions({ region: "asia-northeast1" });

exports.date = onRequest({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  }, (req, res) => {
  // ...
});

exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  /* ... */
});

อัปเดตบัญชีบริการเริ่มต้น (ไม่บังคับ)

ในขณะที่ฟังก์ชันรุ่นที่ 1 ใช้Google App Engineบัญชีบริการเริ่มต้นเพื่อให้สิทธิ์เข้าถึง API ของ Firebase ฟังก์ชันรุ่นที่ 2 จะใช้Compute Engineบัญชีบริการเริ่มต้น ความแตกต่างนี้อาจทำให้เกิดปัญหาเกี่ยวกับสิทธิ์สำหรับ ฟังก์ชันที่ย้ายข้อมูลไปยังรุ่นที่ 2 ในกรณีที่คุณให้สิทธิ์พิเศษ แก่บัญชีบริการรุ่นที่ 1 หากไม่ได้เปลี่ยนสิทธิ์ของบัญชีบริการ ให้ข้ามขั้นตอนนี้

วิธีแก้ปัญหาที่แนะนำคือการกำหนดApp Engineบัญชีบริการเริ่มต้นรุ่นที่ 1 ที่มีอยู่ให้กับฟังก์ชันที่คุณต้องการย้ายข้อมูลไปยังรุ่นที่ 2 อย่างชัดเจน โดยการลบล้างค่าเริ่มต้นของรุ่นที่ 2 คุณทำได้โดยตรวจสอบว่าฟังก์ชันที่ย้ายข้อมูลแต่ละรายการ กำหนดค่าที่ถูกต้องสำหรับ serviceAccountEmail ดังนี้

const {onRequest} = require("firebase-functions/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");
const {setGlobalOptions} = require("firebase-functions");

// Use the App Engine default service account for all functions
setGlobalOptions({serviceAccountEmail: '<my-project-number>@<wbr>appspot.gserviceaccount.com'});

// Now I use the App Engine default service account.
exports.date = onRequest({cors: true}, (req, res) => {
  // ...
});

// I do too!
exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  // ...
});

หรือคุณอาจต้องตรวจสอบว่าได้แก้ไขรายละเอียดบัญชีบริการให้ตรงกับสิทธิ์ที่จำเป็นทั้งหมดในทั้งApp Engineบัญชีบริการเริ่มต้น (สำหรับรุ่นที่ 1) และCompute Engineบัญชีบริการเริ่มต้น (สำหรับรุ่นที่ 2)

ใช้การทำงานพร้อมกัน

ข้อได้เปรียบที่สำคัญของฟังก์ชันรุ่นที่ 2 คือความสามารถของอินสแตนซ์ฟังก์ชันเดียว ในการให้บริการคำขอมากกว่า 1 รายการพร้อมกัน ซึ่งจะช่วยลดจำนวน Cold Start ที่ผู้ใช้ปลายทางพบได้อย่างมาก โดยค่าเริ่มต้น ระบบจะตั้งค่าการทำงานพร้อมกันไว้ที่ 80 แต่คุณสามารถตั้งค่าใดก็ได้ตั้งแต่ 1 ถึง 1,000

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // set concurrency value
    concurrency: 500
  },
  (req, res) => {
    // ...
});

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

ตรวจสอบการใช้ตัวแปรส่วนกลาง

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

ขณะอัปเกรด คุณสามารถตั้งค่า CPU ของฟังก์ชันเป็น gcf_gen1 และตั้งค่า concurrency เป็น 1 เพื่อคืนค่าลักษณะการทำงานของรุ่นที่ 1 ได้โดยทำดังนี้

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // TEMPORARY FIX: remove concurrency
    cpu: "gcf_gen1",
    concurrency: 1
  },
  (req, res) => {
    // ...
});

อย่างไรก็ตาม เราไม่แนะนำให้ใช้วิธีนี้เป็นการแก้ไขระยะยาว เนื่องจากจะทำให้เสียข้อได้เปรียบด้านประสิทธิภาพของฟังก์ชันรุ่นที่ 2 แต่ให้ตรวจสอบการใช้ตัวแปรส่วนกลางในฟังก์ชัน และนำการตั้งค่าชั่วคราวเหล่านี้ออกเมื่อพร้อม

ย้ายการรับส่งข้อมูลไปยังฟังก์ชันรุ่นที่ 2 ใหม่

เช่นเดียวกับเมื่อเปลี่ยนภูมิภาคหรือประเภททริกเกอร์ของฟังก์ชัน คุณจะต้องตั้งชื่อใหม่ให้กับฟังก์ชันรุ่นที่ 2 และค่อยๆ ย้ายข้อมูล ไปยังฟังก์ชันดังกล่าว

คุณไม่สามารถอัปเกรดฟังก์ชันจากรุ่นที่ 1 เป็นรุ่นที่ 2 โดยใช้ชื่อเดียวกัน และเรียกใช้ firebase deploy ได้ การดำเนินการดังกล่าวจะทำให้เกิดข้อผิดพลาดต่อไปนี้

Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.

กลยุทธ์การย้ายข้อมูลจะขึ้นอยู่กับประเภททริกเกอร์ที่ฟังก์ชันใช้

ย้ายข้อมูลทริกเกอร์ที่เรียกใช้ได้ คิวงาน และ HTTP

ทริกเกอร์เหล่านี้เป็นการเรียกใช้โดยตรง เนื่องจากฟังก์ชันรุ่นที่ 2 จะมีชื่อใหม่ (และ URL ใหม่สำหรับทริกเกอร์ HTTP) คุณจึงย้ายข้อมูลการเข้าชมได้โดยการอัปเดตไคลเอ็นต์

  1. เปลี่ยนชื่อฟังก์ชันในโค้ด (เช่น เปลี่ยนชื่อ myCallable เป็น myCallableV2)
  2. ทำให้ฟังก์ชันใช้งานได้ ตอนนี้ฟังก์ชันทั้งรุ่นที่ 1 และรุ่นที่ 2 ทำงานแล้ว
  3. อัปเดตโค้ดไคลเอ็นต์หรือผู้โทรให้ชี้ไปยังชื่อหรือ URL ของฟังก์ชันรุ่นที่ 2 ใหม่
  4. เมื่อการรับส่งข้อมูลทั้งหมดเปลี่ยนไปใช้ฟังก์ชันใหม่แล้ว ให้ลบฟังก์ชันรุ่นที่ 1 โดยใช้คำสั่ง firebase functions:delete ของ Firebase CLI

ย้ายข้อมูลทริกเกอร์พื้นหลัง

ทริกเกอร์เบื้องหลัง (เช่น ทริกเกอร์ Pub/Sub, Cloud Firestore และ Cloud Storage) จะตอบสนองต่อเหตุการณ์ในโปรเจ็กต์ คุณต้องเรียกใช้ฟังก์ชันรุ่นที่ 1 และรุ่นที่ 2 ควบคู่กันชั่วคราวเพื่อไม่ให้พลาดเหตุการณ์ใดๆ ในระหว่างการเปลี่ยนผ่าน

ในช่วงเปลี่ยนผ่าน ฟังก์ชันทั้ง 2 จะทริกเกอร์ในเหตุการณ์เดียวกัน ซึ่งหมายความว่าตรรกะทางธุรกิจจะทํางาน 2 ครั้งต่อเหตุการณ์ ตรวจสอบว่าฟังก์ชันของคุณ เป็นฟังก์ชันที่ทำซ้ำได้ ก่อนดำเนินการต่อ

  1. เพิ่มฟังก์ชันรุ่นที่ 2 ควบคู่ไปกับฟังก์ชันรุ่นที่ 1 โดยเก็บฟังก์ชันรุ่นที่ 1 ที่มีอยู่ ไว้ในโค้ด และเพิ่มฟังก์ชันรุ่นที่ 2 ที่รับฟังแหล่งเหตุการณ์เดียวกัน

    import * as functions from "firebase-functions/v1";
    import { onMessagePublished } from "firebase-functions/v2/pubsub";
    
    // --- Existing 1st gen function ---
    export const myPubSub = functions.pubsub.topic("my-topic").onPublish((message, context) => {
      console.log("V1 handler running for event:", context.eventId);
      // ... existing v1 function logic ...
    });
    
    // --- New v2 passthrough function ---
    export const myPubSubV2 = onMessagePublished("my-topic", async ({ message, context }) => {
      console.log("v2 handler triggering V1 for event:", context.eventId);
      // Call the v1 function's handler
      await myPubSub.run(message, context);
    });
    
  2. วิ่ง firebase deploy ตอนนี้ฟังก์ชันทั้ง 2 ทำงานอยู่และกำลังฟังเหตุการณ์เดียวกัน

  3. ตรวจสอบว่าฟังก์ชันรุ่นที่ 2 ได้รับการเข้าชม ตรวจสอบบันทึกสำหรับทั้ง 2 ฟังก์ชัน ตรวจสอบว่าได้เรียกใช้ฟังก์ชันรุ่นที่ 2 สำหรับเหตุการณ์ทั้งหมดและเรียกใช้สำเร็จ

  4. เมื่อมั่นใจว่าฟังก์ชันทำงานได้อย่างถูกต้องแล้ว ให้ย้ายตรรกะทางธุรกิจ จริง จากฟังก์ชันรุ่นที่ 1 ไปยัง เนื้อหาของฟังก์ชันรุ่นที่ 2 หากคุณใช้วิธีการส่งผ่าน ให้นำการเรียกไปยัง myPubSub.run() ออก

    import * as functions from "firebase-functions/v1";
    import { onMessagePublished } from "firebase-functions/v2/pubsub";
    
    // --- Existing v1 function (to be removed next) ---
    export const myPubSub = functions.pubsub.topic("my-topic").onPublish((message, context) => {
      console.log("v1 handler running for event:", context.eventId);
      // ... existing v1 function logic ...
    });
    
    // --- New v2 function with full logic ---
    export const myPubSubV2 = onMessagePublished("my-topic", ({ message, context }) => {
      console.log("v2 handler running for event:", context.eventId);
      // ... existing v1 function logic WAS MOVED HERE ...
    });
    

    ใช้การเปลี่ยนแปลงนี้

  5. นำคำจำกัดความของฟังก์ชันรุ่นที่ 1 ออกจากโค้ดแล้วติดตั้งใช้งานอีกครั้ง CLI จะ แจ้งให้คุณลบฟังก์ชันรุ่นที่ 1 ออกจาก Google Cloud