Development/Google API

Google cloud function : Firestore 데이터 조회/수정하기

dokevee 2024. 1. 12. 01:24

안녕하세요. 도깨비 개발자입니다.

지난 글에서는 Google Cloud Function의 환경설정, Clound Function 함수작성 및 동작 테스트까지 해보았는데요.

궁금하신 분은 아래 링크를 통해 지난 내용을 확인해주세요.

Google cloud function(1/2) 소개 및 환경설정 (tistory.com)

Google cloud function(2/2) 함수 만들기 및 동작테스트 (tistory.com)

 

오늘은 Cloud Function으로 Firestore의 데이터를 조회/수정하는 함수를 만들고 동작을 시켜보겠습니다.

시작에 앞서 먼저 이런 의문을 가지신 분들이 있을 수 있습니다.

'모바일앱이나 PC어플리케이션에서 직접 Firestore의 데이터베이스를 조회하고 수정하면 되는데 굳이 Cloud Function을 만들어 써야하나?'

그렇습니다. 모바일 앱이나 PC어플리케이션에서 직접 조회하고 수정할 수 있습니다.

하지만 Cloud Function을 사용함에 몇가지 중요한 장점이 있습니다.

 

첫째, 보안입니다.

클라이언트 어플리케이션에서 직접 Firestore에 접근한다는 것은 결국 데이터베이스에 접근할 수 있는 중요한 키가 해킹당할 수 있는 리스크가 있습니다.

이러한 보안 리스크 때문에 보안규칙을 정의하고 관리해야하는 복잡성이 증가하게 됩니다.

 

둘째, 자체 서버구축이 필요없는 점(서버리스)입니다.

결국 첫번째 보안문제로 인해 Firestore를 서버쪽에서 관리한다고 하면 두가지 선택지가 있을 수 있겠죠.

자체 서버를 만들어 클라이언트의 요청을 받아 서버에서 데이터베이스를 조회/수정하거나 Cloud Function에서 관리하거나 이겠죠.

자체 서버를 만든다는 것은 물리적 또는 VM으로 직접 아파치나 IIS등으로 웹서버를 구동시키고 REST API도 직접 만드는 등 할 일이 커지게 됩니다.

이 때, Cloud Function을 만들어 쓴다면 서버구축 필요없이 구글에서 알아서 함수호출에 대응을 해주게 되니 아주 편리해집니다. 또한 트래픽이 급증하는 상황에서도 안정적으로 서비스를 제공할 수 있습니다.

이는 대규모 사용자기반을 가진 어플리케이션에서 특히 중요하다고 할 수 있겠죠.

단, 호출횟수에 따른 비용은 발생하게 되므로 월 예상 호출횟수를 확인하여 현명한 선택을 하시면 됩니다.

 

셋째, 유지보수의 용이성

비즈니스 로직을 Cloud Function에 구현함으로써, 어플리케이션의 복잡성을 줄이고 유지보수를 용이하게 할 수 있습니다.

또한, 서버측 코드 변경이 필요한 경우 클라이언트 어플리케이션을 업데이트할 필요없이 Cloud Function만 수정하면 됩니다.

 

제가 개인적으로 Cloud Function을 활용한 분야는 소프트웨어 복사방지 시스템입니다.

윈도우용 어플리케이션을 배포하면서 불법복제 방지를 위해서 여러가지 방안을 검토하던 중

OAuth, USB동글 등의 방법도 있지만 더 간단한 방법으로 Cloud Function을 활용하게 되었습니다.

사용자마다 어플리케이션의 라이선스 고유키를 발급하여 사용자에게 고유키를 제공하면, 사용자가 어플리케이션에서 고유키를 입력하여 라이선스 인증을 하게 됩니다.

 

이상으로 Cloud Function을 왜 사용하는 지, 그리고 실제로 어떻게 쓰일 수 있는 지에 대해 간단히 얘기해보았습니다.

 

이제 본격적으로 예제를 진행하도록 하겠습니다.

먼저 index.ts 파일의 전체코드를 보여드리겠습니다.

/**
 * Import function triggers from their respective submodules:
 *
 * import {onCall} from "firebase-functions/v2/https";
 * import {onDocumentWritten} from "firebase-functions/v2/firestore";
 *
 * See a full list of supported triggers at https://firebase.google.com/docs/functions
 */
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import * as logger from "firebase-functions/logger";

// Firebase Admin 초기화
admin.initializeApp();

// Firestore 참조
const db = admin.firestore();

export const ReadAge = functions.https.onRequest(async (request, response) => {
  const docId = request.query.docId;
  if (!docId) {
    response.status(400).send("Name is required");
    return;
  }
  try {
    const userRef = db.collection("users").doc(docId.toString());
    const doc = await userRef.get();
    if (!doc.exists) {
      response.status(404).send("User not found");
      return;
    }

    // callCount 업데이트
    const userData = doc.data();
    const currentCount = userData?.callCount || 0;
    await userRef.update({callCount: currentCount + 1});

    // 나이 정보 반환
    response.send({age: userData?.age});
  } catch (error) {
    logger.error("Error reading age", error);
    response.status(500).send("Error reading age");
  }
});


// 이름과 나이를 사용하여 사용자의 나이를 설정하는 함수
export const SetAge = functions.https.onRequest(async (request, response) => {
  const {docId, age} = request.body;
  if (!docId || age === undefined) {
    response.status(400).send("Name and age are required");
    return;
  }
  try {
    const userRef = db.collection("users").doc(docId);
    await userRef.set({age}, {merge: true});
    response.send("Age updated successfully");
  } catch (error) {
    logger.error("Error setting age", error);
    response.status(500).send("Error setting age");
  }
});

 

위의 코드를 보면 2개의 함수가 있습니다.

ReadAge는 데이터베이스조회(GET), SetAge는 데이터베이스수정(POST)입니다.

 

1) ReadAge

docId를 쿼리로 입력을 받아 docId문서에 있는 age를 반환해줍니다.

테스트코드로 callCount를 1씩 증가시키고 있습니다.

원래 프로그램 방법론으로 볼 때, GET요청에서 DB의 항목을 수정하는 건 바람직 하진 않습니다.

따라서 어디까지나 테스트코드로 봐주시기 바랍니다.

 

2) SetAge

docId와 age를 body문으로 입력을 받아 docId문서의 age를 수정하는 함수입니다.

 

현재 firestore의 database를 확인해보겠습니다.

Firestore 데이터베이스의 users컬렉션-24011101문서의 내용

 

이 상태에서 index.ts의 내용을 Cloud Function에 Deploy해보겠습니다.

Deploy가 완료되었습니다.

 

Functions를 확인해보면 ReadAge, SetAge 2개의 함수가 보입니다.

 

이제 호출을 해서 동작테스트를 해보겠습니다. 동작확인을 위해 Postman을 사용하겠습니다.

 

첫번째 ReadAge를 호출해보겠습니다.

docId=24011101 쿼리로 ReadAge 명령 결과

11세라고 친절하게 답변이 왔네요.

실제 클라이언트에서는 Json을 Deserialize해서 사용하면 되겠습니다.

Firestore에서 callCount가 갱신되었는지 확인해보겠습니다.

0에서 1로 바뀐 걸 확인할 수 있습니다.

 

이제 두번째 SetAge를 호출해보겠습니다.

docId가 24011101인 문서의 age를 38세로 변경해보겠습니다.

Age가 성공적으로 업데이트되었다고 답변이 왔네요.

실제 데이터베이스가 갱신되었는지 확인해보겠습니다.

 38세로 갱신되어 있는 걸 확인할 수 있습니다.

 

[코드 추가 설명]

추가로 TypeScript의 문법적 특성에 대해 한가지 설명 드리겠습니다.

아래 코드를 자세히 봐주세요.

// 이름과 나이를 사용하여 사용자의 나이를 설정하는 함수
export const SetAge = functions.https.onRequest(async (request, response) => {
  const {docId, age} = request.body;
  if (!docId || age === undefined) {
    response.status(400).send("Name and age are required");
    return;
  }
  try {
    const userRef = db.collection("users").doc(docId);
    await userRef.set({age}, {merge: true});
    response.send("Age updated successfully");
  } catch (error) {
    logger.error("Error setting age", error);
    response.status(500).send("Error setting age");
  }
});

코드를 보시면 파라메터 age변수가 있습니다.

이 변수가 아래쪽에서

await userRef.set({age}, {merge: true});

 

라는 코드에서 사용이 되는데요.

set함수내에서 age라는 숫자가 들어간 변수만 들어갔는데 실제로 age필드가 변경되었습니다.

그 이유는 TypeScript와 JavaScript의 객체 리터럴 축약 표현법이기 때문입니다.

만약에 파라메터명이 newAge로 정의되었다고 한다면 코드는 아래와 같이 작성되어야 합니다.

await userRef.set({age: newAge}, {merge: true});

 

 

이상으로 Cloud Function 을 호출하여 Firestore의 데이터베이스를 조회/수정하는 예제를 함께 진행해보았습니다.

지난번 글에서 작성했던 2개의 글과 이 글까지 보셨으면 Cloud Function 을 충분히 쉽게 구현할 수 있을 거라고 생각합니다.

 

긴 글 읽어주셔서 감사합니다.