확장 프로그램 빌드 시작하기

이 페이지에서는 프로젝트에 설치하거나 다른 사용자와 공유할 수 있는 간단한 Firebase 확장 프로그램을 빌드하는 데 필요한 단계를 설명합니다. 이 간단한 Firebase 확장 프로그램 예시에서는 실시간 데이터베이스에서 메시지를 확인하고 대문자로 변환합니다.

1. 환경 설정 및 프로젝트 초기화

확장 프로그램 빌드를 시작하려면 먼저 필요한 도구로 빌드 환경을 설정해야 합니다.

  1. Node.js 16 이상을 설치합니다. 노드를 설치하는 방법 중 하나는 nvm(또는 nvm-windows)을 사용하는 것입니다.

  2. 최신 버전의 Firebase CLI를 설치하거나 업데이트합니다. npm를 사용하여 설치하거나 업데이트하려면 다음 명령어를 실행합니다.

    npm install -g firebase-tools
    

이제 Firebase CLI를 사용하여 새 확장 프로그램 프로젝트를 초기화합니다.

  1. 확장 프로그램의 디렉터리를 만들고 그 안에 cd를 만듭니다.

    mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
    
  2. Firebase CLI의 ext:dev:init 명령어를 실행합니다.

    firebase ext:dev:init
    

    메시지가 표시되면 함수 언어로 JavaScript를 선택하고(자체 확장 프로그램을 개발할 때 TypeScript를 사용할 수도 있음) 종속 항목을 설치하라는 메시지가 표시되면 '예'를 선택합니다. (다른 옵션은 기본값을 수락합니다.) 이 명령어를 실행하면 확장 프로그램 개발을 시작할 수 있는 새 확장 프로그램의 스켈레톤 코드베이스가 설정됩니다.

2. 에뮬레이터를 사용한 예시 확장 프로그램 사용해 보기

Firebase CLI는 새 확장 프로그램 디렉터리를 초기화할 때 Firebase 에뮬레이터 도구 모음을 사용하여 확장 프로그램을 실행하는 데 필요한 파일이 포함된 간단한 예시 함수와 integration-tests 디렉터리를 만듭니다.

에뮬레이터에서 예시 확장 프로그램을 실행해 봅니다.

  1. integration-tests 디렉터리로 변경합니다.

    cd functions/integration-tests
    
  2. 데모 프로젝트로 에뮬레이터를 시작합니다.

    firebase emulators:start --project=demo-test
    

    에뮬레이터가 사전 정의된 '더미' 프로젝트(demo-test)에 확장 프로그램을 로드합니다. 지금까지 확장 프로그램은 액세스할 때 'Hello World' 메시지를 반환하는 단일 HTTP 트리거 함수 greetTheWorld로 구성됩니다.

  3. 에뮬레이터가 아직 실행 중이면 시작할 때 출력된 URL로 이동하여 확장 프로그램의 greetTheWorld 함수를 사용해 봅니다.

    브라우저에 'greet-the-world의 Hello World'라는 메시지가 표시됩니다.

  4. 이 함수의 소스 코드는 확장 프로그램의 functions 디렉터리에 있습니다. 원하는 편집기 또는 IDE에서 소스를 엽니다.

    functions/index.js

    const functions = require("firebase-functions");
    
    exports.greetTheWorld = functions.https.onRequest((req, res) => {
      // Here we reference a user-provided parameter
      // (its value is provided by the user during installation)
      const consumerProvidedGreeting = process.env.GREETING;
    
      // And here we reference an auto-populated parameter
      // (its value is provided by Firebase after installation)
      const instanceId = process.env.EXT_INSTANCE_ID;
    
      const greeting = `${consumerProvidedGreeting} World from ${instanceId}`;
    
      res.send(greeting);
    });
    
  5. 에뮬레이터가 실행되는 동안 Firebase Functions 코드에 대한 변경사항이 자동으로 새로고침됩니다. greetTheWorld 함수를 약간 변경해 봅니다.

    functions/index.js

    const greeting = `${consumerProvidedGreeting} everyone, from ${instanceId}`;
    

    변경사항을 저장합니다. 에뮬레이터가 코드를 새로고침하므로 이제 함수 URL로 이동하면 업데이트된 인사말이 표시됩니다.

3. extension.yaml에 기본 정보 추가

이제 개발 환경을 설정하고 확장 프로그램 에뮬레이터를 실행했으므로 자체 확장 프로그램 작성을 시작할 수 있습니다.

첫 번째 단계로 사전 정의된 확장 프로그램 메타데이터를 수정하여 greet-the-world 대신 작성할 확장 프로그램을 반영합니다. 이 메타데이터는 extension.yaml 파일에 저장됩니다.

  1. 편집기에서 extension.yaml을 열고 파일의 전체 콘텐츠를 다음과 같이 바꿉니다.

    name: rtdb-uppercase-messages
    version: 0.0.1
    specVersion: v1beta  # Firebase Extensions specification version; don't change
    
    # Friendly display name for your extension (~3-5 words)
    displayName: Convert messages to upper case
    
    # Brief description of the task your extension performs (~1 sentence)
    description: >-
      Converts messages in RTDB to upper case
    
    author:
      authorName: Your Name
      url: https://your-site.example.com
    
    license: Apache-2.0  # Required license
    
    # Public URL for the source code of your extension
    sourceUrl: https://github.com/your-name/your-repo
    

    name 필드에 사용되는 이름 지정 규칙을 참조하세요. 공식 Firebase 확장 프로그램의 이름은 확장 프로그램이 작동하는 기본 Firebase 제품을 나타내는 프리픽스로 지정되고 뒤이어 확장 프로그램의 기능에 대한 설명이 붙습니다. 자체 확장 프로그램에 동일한 규칙을 사용해야 합니다.

  2. 확장 프로그램의 이름을 변경했으므로 에뮬레이터 구성도 새 이름으로 업데이트해야 합니다.

    1. functions/integration-tests/firebase.json에서 greet-the-worldrtdb-uppercase-messages로 변경합니다.
    2. 이름을 functions/integration-tests/extensions/greet-the-world.env에서 functions/integration-tests/extensions/rtdb-uppercase-messages.env로 변경합니다.

확장 프로그램 코드에 greet-the-world 확장 프로그램의 잔여 부분이 아직 남아 있지만 지금은 그대로 둡니다. 다음 몇 가지 섹션에서 이를 업데이트할 예정입니다.

4. Cloud 함수를 작성하고 확장 프로그램 리소스로 선언

이제 코드 작성을 시작할 수 있습니다. 이 단계에서는 실시간 데이터베이스에서 메시지를 확인하고 대문자로 변환하는 확장 프로그램의 핵심 태스크를 수행하는 Cloud 함수를 작성합니다.

  1. 원하는 편집기 또는 IDE에서 확장 프로그램의 함수 소스를 엽니다(확장 프로그램의 functions 디렉터리). 콘텐츠를 다음과 같이 바꿉니다.

    functions/index.js

    import { database, logger } from "firebase-functions/v1";
    
    const app = initializeApp();
    
    // Listens for new messages added to /messages/{pushId}/original and creates an
    // uppercase version of the message to /messages/{pushId}/uppercase
    // for all databases in 'us-central1'
    export const makeuppercase = database
      .ref("/messages/{pushId}/uppercase")
      .onCreate(async (snapshot, context) => {
        // Grab the current value of what was written to the Realtime Database.
        const original = snapshot.val();
    
        // Convert it to upper case.
        logger.log("Uppercasing", context.params.pushId, original);
        const uppercase = original.toUpperCase();
    
        // Setting an "uppercase" sibling in the Realtime Database.
        const upperRef = snapshot.ref.parent.child("upper");
        await upperRef.set(uppercase);
    });
    

    바꾼 이전 함수는 HTTP 엔드포인트에 액세스할 때 실행된 HTTP 트리거 함수였습니다. 새 함수는 실시간 데이터베이스 이벤트에 의해 트리거되는 함수로, 특정 경로에서 새 항목을 확인한 다음, 새 항목이 감지되면 대문자 버전의 값을 데이터베이스에 다시 씁니다.

    이 새 파일은 CommonJS(require) 대신 ECMAScript 모듈 구문(importexport)을 사용합니다. 노드에서 ES 모듈을 사용하려면 functions/package.json"type": "module"을 지정합니다.

    {
      "name": "rtdb-uppercase-messages",
      "main": "index.js",
      "type": "module",
      …
    }
    
  2. 확장 프로그램의 모든 함수는 extension.yaml 파일에서 선언해야 합니다. 예시 확장 프로그램에서 greetTheWorld를 확장 프로그램의 유일한 Cloud 함수로 선언했습니다. 이를 makeuppercase로 바꿨으므로 이제 선언도 업데이트해야 합니다.

    extension.yaml을 열고 resources 필드를 추가합니다.

    resources:
      - name: makeuppercase
        type: firebaseextensions.v1beta.function
        properties:
          eventTrigger:
            eventType: providers/google.firebase.database/eventTypes/ref.create
            # DATABASE_INSTANCE (project's default instance) is an auto-populated
            # parameter value. You can also specify an instance.
            resource: projects/_/instances/${DATABASE_INSTANCE}/refs/messages/{pushId}/original
          runtime: "nodejs18"
    
  3. 이제 확장 프로그램이 실시간 데이터베이스를 트리거로 사용하고 있으므로 에뮬레이터 구성을 업데이트하여 Cloud Functions 에뮬레이터와 함께 RTDB 에뮬레이터를 실행해야 합니다.

    1. 에뮬레이터가 여전히 실행 중인 경우 Ctrl-C를 눌러 중지합니다.

    2. functions/integration-tests 디렉터리에서 다음 명령어를 실행합니다.

      firebase init emulators
      

      메시지가 표시되면 기본 프로젝트 설정을 건너뛴 후 Firebase Functions 및 데이터베이스 에뮬레이터를 선택합니다. 기본 포트를 수락하고 설정 도구에서 필요한 파일을 다운로드하도록 허용합니다.

    3. 에뮬레이터를 다시 시작합니다.

      firebase emulators:start --project=demo-test
      
  4. 업데이트된 확장 프로그램을 사용해 봅니다.

    1. 에뮬레이터를 시작할 때 에뮬레이터에서 출력한 링크를 사용하여 데이터베이스 에뮬레이터 UI를 엽니다.

    2. 데이터베이스의 루트 노드를 수정합니다.

      • 필드: messages
      • 유형: json
      • 값: {"11": {"original": "recipe"}}

      모든 항목이 제대로 설정된 경우 데이터베이스 변경사항을 저장하면 확장 프로그램의 makeuppercase 함수가 트리거되어 "upper": "RECIPE" 콘텐츠와 함께 메시지 11에 하위 레코드를 추가해야 합니다. 에뮬레이터 UI의 로그 및 데이터베이스 탭에서 예상 결과를 확인합니다.

    3. messages 노드({"original":"any text"})에 하위 레코드를 더 추가합니다. 새 레코드를 추가할 때마다 확장 프로그램은 original 필드의 대문자 콘텐츠가 포함된 uppercase 필드를 추가해야 합니다.

이제 RTDB 인스턴스에서 작동하는 완전하지만 간단한 확장 프로그램이 만들어졌습니다. 다음 섹션에서는 몇 가지 추가 기능으로 이 확장 프로그램을 미세 조정합니다. 그런 다음 확장 프로그램을 다른 사용자에게 배포할 준비를 하고, 마지막으로 Extensions Hub에 확장 프로그램을 게시하는 방법을 알아봅니다.

5. API 및 역할 선언

Firebase는 인스턴스별 서비스 계정을 사용하여 설치된 확장 프로그램의 각 인스턴스에 프로젝트 및 해당 데이터에 대한 제한된 액세스 권한을 부여합니다. 각 계정에는 작동하는 데 필요한 최소한의 권한이 있습니다. 따라서 확장 프로그램에 필요한 IAM 역할을 명시적으로 선언해야 합니다. 사용자가 확장 프로그램을 설치하면 Firebase는 이러한 역할이 부여된 서비스 계정을 만들고 이를 사용하여 확장 프로그램을 실행합니다.

제품의 이벤트를 트리거하기 위해 역할을 선언할 필요는 없지만, 그 외의 방식으로 상호작용하려면 역할을 선언해야 합니다. 이전 단계에서 추가한 함수가 실시간 데이터베이스에 작성되므로 extension.yaml에 다음 선언을 추가해야 합니다.

roles:
  - role: firebasedatabase.admin
    reason: Allows the extension to write to RTDB.

마찬가지로 확장 프로그램에서 사용하는 Google API를 apis 필드에서 선언합니다. 사용자가 확장 프로그램을 설치하면 프로젝트에 이러한 API를 자동으로 사용 설정할지 묻는 메시지가 표시됩니다. 이는 일반적으로 Firebase 이외의 Google API에만 필요하며 이 가이드에서는 필요하지 않습니다.

6. 사용자가 구성할 수 있는 매개변수 정의

마지막 두 단계에서 만든 함수가 받은 메시지의 특정 RTDB 위치를 확인했습니다. 확장 프로그램이 확장 프로그램 전용 데이터베이스 구조에서 작동하는 경우와 같이 특정 위치를 실제로 확인해야 하는 경우가 있습니다. 그러나 대부분의 경우 프로젝트에 확장 프로그램을 설치하는 사용자가 이러한 값을 구성할 수 있도록 하는 것이 좋습니다. 이렇게 하면 사용자가 확장 프로그램을 활용하여 기존 데이터베이스 설정으로 작업할 수 있습니다.

확장 프로그램에서 새 메시지를 확인하는 경로를 사용자가 구성할 수 있도록 만듭니다.

  1. extension.yaml 파일에 params 섹션을 추가합니다.

    - param: MESSAGE_PATH
      label: Message path
      description: >-
        What is the path at which the original text of a message can be found?
      type: string
      default: /messages/{pushId}/original
      required: true
      immutable: false
    

    이는 사용자가 확장 프로그램을 설치할 때 설정하라는 메시지를 표시하는 새 문자열 매개변수를 정의합니다.

  2. extension.yaml 파일에서 makeuppercase 선언으로 돌아가서 resource 필드를 다음과 같이 변경합니다.

    resource: projects/_/instances/${DATABASE_INSTANCE}/refs/${param:MESSAGE_PATH}
    

    ${param:MESSAGE_PATH} 토큰은 방금 정의한 매개변수에 대한 참조입니다. 확장 프로그램이 실행되면 이 토큰은 사용자가 해당 매개변수에 대해 구성한 값으로 바뀌고, 그 결과 makeuppercase 함수는 사용자가 지정한 경로를 리슨합니다. 이 구문을 사용하여 extension.yaml의 모든 사용자 정의 매개변수를 참조할 수 있습니다(POSTINSTALL.md에 대해서는 나중에 자세히 설명).

  3. 함수 코드에서 사용자 정의 매개변수에 액세스할 수도 있습니다.

    이전 섹션에서 작성한 함수에서는 변경사항을 확인할 경로를 하드 코딩했습니다. 대신 사용자 정의 값을 참조하도록 트리거 정의를 변경합니다.

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate
    

    Firebase Extensions에서 이 변경사항은 순전히 문서 작성만을 위한 것입니다. Cloud 함수는 확장 프로그램의 일부로 배포되면 extension.yaml 파일의 트리거 정의를 사용하고 함수 정의에 지정된 값을 무시합니다. 그럼에도 불구하고 이 값의 출처를 코드에 문서화하는 것이 좋습니다.

  4. 런타임 효과가 없는 코드 변경은 실망스러울 수 있지만, 중요한 점은 함수 코드의 모든 사용자 정의 매개변수에 액세스하여 함수 로직에 일반 값으로 사용할 수 있다는 것입니다. 이 기능을 확인하기 위해 다음 로그 구문을 추가하여 사용자가 정의한 값에 실제로 액세스하고 있음을 보여줍니다.

    functions/index.js

    export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate(
      async (snapshot, context) => {
        logger.log("Found new message at ", snapshot.ref);
    
        // Grab the current value of what was written to the Realtime Database.
        ...
    
  5. 일반적으로 사용자가 확장 프로그램을 설치할 때 매개변수 값을 제공하라는 메시지가 표시됩니다. 그러나 테스트 및 개발에 에뮬레이터를 사용하면 설치 프로세스를 건너뛰므로 대신 env 파일을 사용하여 사용자 정의 매개변수 값을 제공합니다.

    functions/integration-tests/extensions/rtdb-uppercase-messages.env를 열고 GREETING 정의를 다음으로 바꿉니다.

    MESSAGE_PATH=/msgs/{pushId}/original
    

    위의 경로는 기본 경로 및 이전에 정의한 경로와 다릅니다. 이는 업데이트된 확장 프로그램을 사용할 때 정의가 적용되고 있음을 검증하기 위한 것입니다.

  6. 이제 에뮬레이터를 다시 시작하고 데이터베이스 에뮬레이터 UI로 다시 이동합니다.

    위에서 정의한 경로를 사용하여 데이터베이스의 루트 노드를 수정합니다.

    • 필드: msgs
    • 유형: json
    • 값: {"11": {"original": "recipe"}}

    데이터베이스 변경사항을 저장하면 확장 프로그램의 makeuppercase 함수가 이전과 같이 트리거되지만 이제 사용자 정의 매개변수도 콘솔 로그에 출력됩니다.

7. 사용자 정의 로직에 이벤트 후크 제공

확장 프로그램 작성자로서 Firebase 제품이 확장 프로그램 제공 로직을 어떻게 트리거할 수 있는지 이미 살펴봤습니다. 즉, 실시간 데이터베이스에서 새 레코드를 만들면 makeuppercase 함수가 트리거됩니다. 확장 프로그램에는 확장 프로그램을 설치하는 사용자와 닮은 점이 있을 수 있습니다. 즉, 확장 프로그램사용자가 정의한 로직을 트리거할 수 있습니다.

확장 프로그램은 동기식 후크, 비동기식 후크 또는 둘 다를 제공할 수 있습니다. 동기식 후크는 사용자에게 확장 프로그램의 함수 중 하나를 완료하지 못하게 하는 태스크를 수행하는 방법을 제공합니다. 예를 들어 확장 프로그램이 작동하기 전에 커스텀 전처리를 수행하는 방법을 사용자에게 제공하는 데 유용할 수 있습니다.

이 가이드에서는 확장 프로그램에 비동기식 후크를 추가합니다. 이렇게 하면 확장 프로그램에서 대문자 메시지를 실시간 데이터베이스에 작성한 후 실행할 자체 처리 단계를 사용자가 정의할 수 있게 됩니다. 비동기식 후크는 Eventarc를 사용하여 사용자 정의 함수를 트리거합니다. 확장 프로그램은 내보내는 이벤트 유형을 선언하고 사용자는 확장 프로그램을 설치할 때 관심 있는 이벤트 유형을 선택합니다. 사용자가 이벤트를 하나 이상 선택하면 Firebase가 설치 과정에서 확장 프로그램의 Eventarc 채널을 프로비저닝합니다. 그러면 사용자는 해당 채널에서 리슨하고 확장 프로그램이 새 이벤트를 게시할 때 트리거되는 자체 Cloud Functions를 배포할 수 있습니다.

비동기식 후크를 추가하려면 다음 단계를 따르세요.

  1. extension.yaml 파일에서 확장 프로그램이 내보내는 이벤트 유형 하나를 선언하는 다음 섹션을 추가합니다.

    events:
      - type: test-publisher.rtdb-uppercase-messages.v1.complete
        description: >-
          Occurs when message uppercasing completes. The event subject will contain
          the RTDB URL of the uppercase message.
    

    이벤트 유형은 전체적으로 고유해야 합니다. 고유성을 보장하려면 항상 <publisher-id>.<extension-id>.<version>.<description> 형식을 사용하여 이벤트 이름을 지정합니다. (아직 게시자 ID가 없으므로 지금은 test-publisher를 사용하세요.)

  2. makeuppercase 함수 끝에 방금 선언한 유형의 이벤트를 게시하는 코드를 추가합니다.

    functions/index.js

    // Import the Eventarc library:
    import { initializeApp } from "firebase-admin/app";
    import { getEventarc } from "firebase-admin/eventarc";
    
    const app = initializeApp();
    
    // In makeuppercase, after upperRef.set(uppercase), add:
    
    // Set eventChannel to a newly-initialized channel, or `undefined` if events
    // aren't enabled.
    const eventChannel =
      process.env.EVENTARC_CHANNEL &&
      getEventarc().channel(process.env.EVENTARC_CHANNEL, {
        allowedEventTypes: process.env.EXT_SELECTED_EVENTS,
      });
    
    // If events are enabled, publish a `complete` event to the configured
    // channel.
    eventChannel &&
      eventChannel.publish({
        type: "test-publisher.rtdb-uppercase-messages.v1.complete",
        subject: upperRef.toString(),
        data: {
          "original": original,
          "uppercase": uppercase,
        },
      });
    

    이 예시 코드는 사용자가 하나 이상의 이벤트 유형을 사용 설정한 경우에만 EVENTARC_CHANNEL 환경 변수가 정의된다는 사실을 활용합니다. EVENTARC_CHANNEL이 정의되어 있지 않으면 코드는 이벤트 게시를 시도하지 않습니다.

    Eventarc 이벤트에 추가 정보를 첨부할 수 있습니다. 위의 예시에서는 이벤트에 새로 생성된 값에 대한 참조가 포함된 subject 필드와 원본 및 대문자 메시지가 포함된 data 페이로드가 있습니다. 이벤트를 트리거하는 사용자 정의 함수는 이 정보를 활용할 수 있습니다.

  3. 일반적으로 EVENTARC_CHANNELEXT_SELECTED_EVENTS 환경 변수는 사용자가 설치 중에 선택한 옵션을 기반으로 정의됩니다. 에뮬레이터로 테스트하려면 rtdb-uppercase-messages.env 파일에 다음 변수를 수동으로 정의합니다.

    EVENTARC_CHANNEL=locations/us-central1/channels/firebase
    EXT_SELECTED_EVENTS=test-publisher.rtdb-uppercase-messages.v1.complete
    

이제 확장 프로그램에 비동기식 이벤트 후크를 추가하는 데 필요한 단계를 완료했습니다.

다음 몇 단계에서 확장 프로그램을 설치하는 사용자의 역할로 방금 구현한 이 새 기능을 사용해 봅니다.

  1. functions/integration-tests 디렉터리에서 새 Firebase 프로젝트를 초기화합니다.

    firebase init functions
    

    메시지가 표시되면 기본 프로젝트 설정을 거부하고 Cloud Functions 언어로 JavaScript를 선택한 후 필요한 종속 항목을 설치합니다. 이 프로젝트는 확장 프로그램이 설치된 사용자의 프로젝트를 나타냅니다.

  2. integration-tests/functions/index.js를 수정하고 다음 코드를 붙여넣습니다.

    import { logger } from "firebase-functions/v1";
    import { onCustomEventPublished } from "firebase-functions/v2/eventarc";
    
    import { initializeApp } from "firebase-admin/app";
    import { getDatabase } from "firebase-admin/database";
    
    const app = initializeApp();
    
    export const extraemphasis = onCustomEventPublished(
      "test-publisher.rtdb-uppercase-messages.v1.complete",
      async (event) => {
        logger.info("Received makeuppercase completed event", event);
    
        const refUrl = event.subject;
        const ref = getDatabase().refFromURL(refUrl);
        const upper = (await ref.get()).val();
        return ref.set(`${upper}!!!`);
      }
    );
    

    사용자가 작성할 수 있는 후처리 함수의 예시입니다. 이 경우 함수는 확장 프로그램이 complete 이벤트를 게시하도록 리슨하고 트리거될 때 새 대문자 메시지에 느낌표 3개를 추가합니다.

  3. 에뮬레이터를 다시 시작합니다. 에뮬레이터는 확장 프로그램의 함수와 '사용자'가 정의한 후처리 함수를 로드합니다.

  4. 데이터베이스 에뮬레이터 UI로 이동하고 위에서 정의한 경로를 사용하여 데이터베이스의 루트 노드를 수정합니다.

    • 필드:msgs
    • 유형: json
    • 값: {"11": {"original": "recipe"}}

    데이터베이스 변경사항을 저장하면 확장 프로그램의 makeuppercase 함수와 사용자의 extraemphasis 함수가 순차적으로 트리거되어 upper 필드의 값이 RECIPE!!!가 됩니다.

8. 수명 주기 이벤트 핸들러 추가

지금까지 작성한 확장 프로그램은 메시지가 생성될 때 메시지를 처리합니다. 그러나 확장 프로그램을 설치할 때 사용자에게 이미 메시지 데이터베이스가 있다면 어떻게 될까요? Firebase Extensions에는 확장 프로그램이 설치, 업데이트, 재구성될 때 작업을 트리거하는 데 사용할 수 있는 수명 주기 이벤트 후크라는 기능이 있습니다. 이 섹션에서는 사용자가 확장 프로그램을 설치할 때 프로젝트의 기존 메시지 데이터베이스를 대문자 메시지로 백필하도록 수명 주기 이벤트 후크를 사용합니다.

Firebase Extensions는 Cloud Tasks를 사용하여 수명 주기 이벤트 핸들러를 실행합니다. Cloud Functions를 사용하여 이벤트 핸들러를 정의합니다. 확장 프로그램의 인스턴스가 지원되는 수명 주기 이벤트 중 하나에 도달할 때마다 핸들러를 정의하면 핸들러가 Cloud Tasks 큐에 추가됩니다. 그러면 Cloud Tasks가 핸들러를 비동기식으로 실행합니다. 수명 주기 이벤트 핸들러가 실행되는 동안 Firebase Console은 확장 프로그램 인스턴스에 진행 중인 처리 태스크가 있음을 사용자에게 보고합니다. 진행 중인 상태 및 태스크 완료를 사용자에게 다시 보고하는 것은 핸들러 함수에 달려 있습니다.

기존 메시지를 백필하는 수명 주기 이벤트 핸들러를 추가하려면 다음 단계를 따르세요.

  1. 태스크 큐 이벤트에 의해 트리거되는 새 Cloud 함수를 정의합니다.

    functions/index.js

    import { tasks } from "firebase-functions/v1";
    
    import { getDatabase } from "firebase-admin/database";
    import { getExtensions } from "firebase-admin/extensions";
    import { getFunctions } from "firebase-admin/functions";
    
    export const backfilldata = tasks.taskQueue().onDispatch(async () => {
      const batch = await getDatabase()
        .ref(process.env.MESSAGE_PATH)
        .parent.parent.orderByChild("upper")
        .limitToFirst(20)
        .get();
    
      const promises = [];
      for (const key in batch.val()) {
        const msg = batch.child(key);
        if (msg.hasChild("original") && !msg.hasChild("upper")) {
          const upper = msg.child("original").val().toUpperCase();
          promises.push(msg.child("upper").ref.set(upper));
        }
      }
      await Promise.all(promises);
    
      if (promises.length > 0) {
        const queue = getFunctions().taskQueue(
          "backfilldata",
          process.env.EXT_INSTANCE_ID
        );
        return queue.enqueue({});
      } else {
        return getExtensions()
          .runtime()
          .setProcessingState("PROCESSING_COMPLETE", "Backfill complete.");
      }
    });
    

    이 함수는 스스로를 태스크 큐에 다시 추가하기 전에 몇 개의 레코드만 처리합니다. 이는 Cloud 함수의 제한 시간 내에 완료할 수 없는 처리 태스크에 대해 일반적으로 사용되는 전략입니다. 사용자가 확장 프로그램을 설치할 때 이미 데이터베이스에 있을 수 있는 메시지 수를 예측할 수 없으므로 이 전략이 적합합니다.

  2. extension.yaml 파일에서 백필 함수를 taskQueueTrigger 속성이 있는 확장 프로그램 리소스로 선언합니다.

    resources:
      - name: makeuppercase
        ...
      - name: backfilldata
        type: firebaseextensions.v1beta.function
        description: >-
          Backfill existing messages with uppercase versions
        properties:
          runtime: "nodejs18"
          taskQueueTrigger: {}
    

    그런 다음 함수를 onInstall 수명 주기 이벤트의 핸들러로 선언합니다.

    lifecycleEvents:
      onInstall:
        function: backfilldata
        processingMessage: Uppercasing existing messages
    
  3. 기존 메시지를 백필하는 것도 좋지만 그렇게 하지 않아도 확장 프로그램은 계속 작동할 수 있습니다. 이러한 상황에서는 수명 주기 이벤트 핸들러를 선택사항으로 실행해야 합니다.

    이를 위해 extension.yaml에 새 매개변수를 추가합니다.

    - param: DO_BACKFILL
      label: Backfill existing messages
      description: >-
        Generate uppercase versions of existing messages?
      type: select
      required: true
      options:
        - label: Yes
          value: true
        - label: No
          value: false
    

    그런 다음 백필 함수의 시작 부분에서 DO_BACKFILL 매개변수 값을 확인하고 설정되지 않은 경우에는 일찍 종료합니다.

    functions/index.js

    if (!process.env.DO_BACKFILL) {
      return getExtensions()
        .runtime()
        .setProcessingState("PROCESSING_COMPLETE", "Backfill skipped.");
    }
    

위와 같이 변경한 경우 확장 프로그램이 설치되면 기존 메시지가 대문자로 변환됩니다.

지금까지 확장 프로그램 에뮬레이터를 사용하여 확장 프로그램을 개발하고 진행 중인 변경사항을 테스트했습니다. 그러나 확장 프로그램 에뮬레이터는 설치 프로세스를 건너뛰므로 onInstall 이벤트 핸들러를 테스트하려면 실제 프로젝트에 확장 프로그램을 설치해야 합니다. 하지만 이 자동 백필 기능이 추가되어 이제 튜토리얼 확장 프로그램이 코드로 완성되었습니다.

9. 실제 Firebase 프로젝트에 배포

확장 프로그램 에뮬레이터는 개발 중에 확장 프로그램을 빠르게 반복할 수 있는 유용한 도구이지만 적당한 시점에 실제 프로젝트에서 사용해 보는 것이 좋습니다.

이렇게 하려면 먼저 일부 서비스가 사용 설정된 새 프로젝트를 설정합니다.

  1. Firebase Console에서 새 프로젝트를 추가합니다.
  2. 사용한 만큼만 지불하는 Blaze 요금제로 프로젝트를 업그레이드합니다. Firebase용 Cloud Functions에서는 프로젝트에 결제 계정이 있어야 하므로 확장 프로그램을 설치하려면 결제 계정이 필요합니다.
  3. 새 프로젝트에서 실시간 데이터베이스를 사용 설정합니다.
  4. 확장 프로그램이 설치 시 기존 데이터를 백필하는 기능을 테스트하려면 몇 가지 샘플 데이터를 실시간 데이터베이스 인스턴스로 가져옵니다.
    1. 일부 시드 RTDB 데이터를 다운로드합니다.
    2. Firebase Console의 실시간 데이터베이스 페이지에서 (더보기) > JSON 가져오기를 클릭하고 방금 다운로드한 파일을 선택합니다.
  5. orderByChild 메서드를 사용하도록 백필 함수를 사용 설정하려면 upper 값에서 메시지의 색인을 생성하도록 데이터베이스를 구성합니다.

    {
      "rules": {
        ".read": false,
        ".write": false,
        "messages": {
          ".indexOn": "upper"
        }
      }
    }
    

이제 로컬 소스의 확장 프로그램을 새 프로젝트에 설치합니다.

  1. Firebase 프로젝트의 새 디렉터리를 만듭니다.

    mkdir ~/extensions-live-test && cd ~/extensions-live-test
    
  2. 작업 디렉터리에서 Firebase 프로젝트를 초기화합니다.

    firebase init database
    

    메시지가 표시되면 방금 만든 프로젝트를 선택합니다.

  3. 로컬 Firebase 프로젝트에 확장 프로그램을 설치합니다.

    firebase ext:install /path/to/rtdb-uppercase-messages
    

    여기에서 Firebase CLI 도구를 사용하여 확장 프로그램을 설치할 때의 사용자 환경을 확인할 수 있습니다. 구성 도구에서 기존 데이터베이스를 백필할지 묻는 메시지가 표시되면 '예'를 선택해야 합니다.

    구성 옵션을 선택하면 Firebase CLI가 구성을 extensions 디렉터리에 저장하고 확장 프로그램 소스 위치를 firebase.json 파일에 기록합니다. 이 두 레코드를 총칭하여 확장 프로그램 매니페스트라고 합니다. 사용자는 매니페스트를 사용하여 확장 프로그램 구성을 저장하고 다른 프로젝트에 배포할 수 있습니다.

  4. 확장 프로그램 구성을 실시간 프로젝트에 배포합니다.

    firebase deploy --only extensions
    

Firebase CLI가 확장 프로그램을 프로젝트에 업로드하고 설치하면 모든 것이 순조롭게 진행되고 있는 것입니다. 설치가 완료되면 백필 태스크가 실행되며 몇 분 후에 데이터베이스가 대문자 메시지로 업데이트됩니다. 메시지 데이터베이스에 새 노드를 추가하고 확장 프로그램이 새 메시지에도 작동하는지 확인합니다.

10. 문서 작성

확장 프로그램을 사용자와 공유하기 전에 사용자가 성공할 수 있을 만큼 충분한 문서를 제공해야 합니다.

확장 프로그램 프로젝트를 초기화할 때 Firebase CLI가 필요한 최소 문서의 스텁 버전의 만들었습니다. 빌드한 확장 프로그램을 정확하게 반영하도록 이 파일을 업데이트합니다.

extension.yaml

이 확장 프로그램을 개발하면서 이미 이 파일을 업데이트했으므로 지금은 더 이상 업데이트할 필요가 없습니다.

하지만 이 파일에 포함된 문서의 중요성을 간과하지 마세요. 확장 프로그램의 중요한 식별 정보(이름, 설명, 작성자, 공식 저장소 위치) 외에도 extension.yaml 파일에는 모든 리소스와 사용자가 구성할 수 있는 매개변수에 대한 사용자 대상 문서가 포함되어 있습니다. 이 정보는 Firebase Console, Extensions Hub, Firebase CLI 사용자에게 표시됩니다.

PREINSTALL.md

이 파일에서는 사용자가 확장 프로그램을 설치하기 전에 필요한 정보를 제공합니다. 확장 프로그램의 역할을 간략하게 기술하고 모든 기본 요건을 설명하며 확장 프로그램 설치에 따른 결제 관련 정보를 제공합니다. 추가 정보가 포함된 웹사이트가 있다면 여기에 연결하는 것이 좋습니다.

이 파일의 텍스트는 Extensions Hub와 firebase ext:info 명령어를 통해 사용자에게 표시됩니다.

다음은 PREINSTALL 파일의 예시입니다.

Use this extension to automatically convert strings to upper case when added to
a specified Realtime Database path.

This extension expects a database layout like the following example:

    "messages": {
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
      MESSAGE_ID: {
        "original": MESSAGE_TEXT
      },
    }

When you create new string records, this extension creates a new sibling record
with upper-cased text:

    MESSAGE_ID: {
      "original": MESSAGE_TEXT,
      "upper": UPPERCASE_MESSAGE_TEXT,
    }

#### Additional setup

Before installing this extension, make sure that you've
[set up Realtime Database](https://firebase.google.com/docs/database/quickstart)
in your Firebase project.

#### Billing

To install an extension, your project must be on the
[Blaze (pay as you go) plan](https://firebase.google.com/pricing).

- This extension uses other Firebase and Google Cloud Platform services, which
  have associated charges if you exceed the service's no-cost tier:
  - Realtime Database
  - Cloud Functions (Node.js 10+ runtime)
    [See FAQs](https://firebase.google.com/support/faq#extensions-pricing)
- If you enable events,
  [Eventarc fees apply](https://cloud.google.com/eventarc/pricing).

POSTINSTALL.md

이 파일에는 후속 설정 단계, 작동 중인 확장 프로그램의 예시 등 확장 프로그램을 성공적으로 설치한 후에 사용자에게 유용한 정보가 포함되어 있습니다.

확장 프로그램이 구성되고 설치된 후 POSTINSTALL.md의 콘텐츠가 Firebase Console에 표시됩니다. 이 파일에서 사용자 매개변수를 참조할 수 있으며, 사용자 매개변수는 구성된 값으로 바뀝니다.

다음은 튜토리얼 확장 프로그램의 설치 후 파일 예시입니다.

### See it in action

You can test out this extension right away!

1.  Go to your
    [Realtime Database dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/database/${param:PROJECT_ID}/data) in the Firebase console.

1.  Add a message string to a path that matches the pattern `${param:MESSAGE_PATH}`.

1.  In a few seconds, you'll see a sibling node named `upper` that contains the
    message in upper case.

### Using the extension

We recommend adding data by pushing -- for example,
`firebase.database().ref().push()` -- because pushing assigns an automatically
generated ID to the node in the database. During retrieval, these nodes are
guaranteed to be ordered by the time they were added. Learn more about reading
and writing data for your platform (iOS, Android, or Web) in the
[Realtime Database documentation](https://firebase.google.com/docs/database/).

### Monitoring

As a best practice, you can
[monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor)
of your installed extension, including checks on its health, usage, and logs.

CHANGELOG.md

또한 확장 프로그램 출시 간에 변경된 사항도 CHANGELOG.md 파일에 문서화해야 합니다.

예시 확장 프로그램은 이전에 게시된 적이 없으므로 변경 로그에는 항목이 하나만 있습니다.

## Version 0.0.1

Initial release of the _Convert messages to upper case_ extension.

README.md

또한 대부분의 확장 프로그램은 사용자가 확장 프로그램의 저장소를 방문하는 데 도움이 되는 리드미 파일을 제공합니다. 이 파일을 직접 작성하거나 명령어를 사용하여 리드미를 생성할 수 있습니다.

이 가이드에서는 리드미 파일 작성을 건너뜁니다.

추가 문서

위에서 설명한 문서는 사용자에게 제공해야 하는 최소한의 문서 집합입니다. 많은 확장 프로그램을 성공적으로 사용하려면 더 자세한 문서가 필요합니다. 이 경우 추가 문서를 작성하고 사용자를 안내할 수 있는 위치에서 호스팅해야 합니다.

이 가이드에서는 더 광범위한 문서 작성은 건너뜁니다.

11. Extensions Hub에 게시

이제 확장 프로그램이 코드로 완성되고 문서화되었으므로 Extensions Hub를 통해 이를 전 세계에 공유할 수 있습니다. 하지만 이것은 튜토리얼에 불과하므로 실제로 그렇게 해서는 안 됩니다. Firebase Extensions 게시자 문서의 나머지 부분과 여기에서 학습한 내용을 바탕으로, 또한 Firebase에서 작성한 공식 확장 프로그램의 소스를 검토하여 자체 확장 프로그램을 작성해 보세요.

Extensions Hub에 작업을 게시할 준비가 되면 다음 단계를 따르세요.

  1. 첫 번째 확장 프로그램을 게시하는 경우 확장 프로그램 게시자로 등록합니다. 확장 프로그램 게시자로 등록할 때 사용자가 확장 프로그램의 작성자를 빠르게 식별할 수 있는 게시자 ID를 만듭니다.
  2. 공개적으로 검증 가능한 위치에 확장 프로그램의 소스 코드를 호스팅합니다. 검증 가능한 소스에서 코드를 사용할 수 있으면 Firebase가 이 위치에서 직접 확장 프로그램을 게시할 수 있습니다. 이렇게 하면 현재 출시된 버전의 확장 프로그램을 게시하는 데 도움이 되고, 프로젝트에 설치하는 코드를 사용자가 검토하도록 허용하여 사용자에게도 도움이 됩니다.

    현재 이것은 확장 프로그램을 공개 GitHub 저장소에서 사용할 수 있도록 하는 것을 의미합니다.

  3. firebase ext:dev:upload 명령어를 사용하여 확장 프로그램을 Extensions Hub에 업로드합니다.

  4. Firebase Console의 게시자 대시보드로 이동하여 방금 업로드한 확장 프로그램을 찾은 후 'Extensions Hub에 게시'를 클릭합니다. 이렇게 하면 검토팀에 검토가 요청됩니다. 며칠 정도 걸릴 수 있습니다. 승인되면 확장 프로그램이 Extensions Hub에 게시됩니다. 거부되는 경우에는 이유를 설명하는 메시지가 표시됩니다. 그러면 보고된 문제를 해결하고 검토를 위해 다시 제출할 수 있습니다.