C++용 Firebase 실시간 데이터베이스로 데이터 저장

시작하기

아직 앱과 데이터베이스에 대한 액세스를 설정하지 않았으면 먼저 Get Started 가이드를 참조하세요.

DatabaseReference 가져오기

데이터베이스에 데이터를 쓰려면 DatabaseReference의 인스턴스가 필요합니다.

    // Get the root reference location of the database.
    firebase::database::DatabaseReference dbref = database->GetReference();

데이터 저장

Firebase 실시간 데이터베이스에 데이터를 쓰는 메서드는 다음과 같이 4가지입니다.

메서드 일반적인 용도
SetValue() 정의된 경로(예: users/<user-id>/<username>)에 데이터를 쓰거나 대체합니다.
PushChild() 데이터 목록에 추가합니다. Push()를 호출할 때마다 Firebase에서 고유 식별자로도 사용할 수 있는 고유 키(예: user-scores/<user-id>/<unique-score-id>)가 생성됩니다.
UpdateChildren() 정의된 경로에서 모든 데이터를 대체하지 않고 일부 키를 업데이트합니다.
RunTransaction() 동시 업데이트에 의해 손상될 수 있는 복잡한 데이터를 업데이트합니다.

참조 위치에서 데이터 쓰기, 업데이트 또는 삭제

기본 쓰기 작업

기본 쓰기 작업은 SetValue() 코드를 사용하여 지정된 참조에 데이터를 저장하고 해당 경로의 기존 데이터를 모두 바꿉니다. 이 메서드를 사용하여 다음을 지원하는 Variant 유형을 통해 JSON에서 허용하는 유형을 전달합니다.

  • Null(데이터를 삭제함)
  • 정수(64비트)
  • 배정밀도 부동 소수점 수
  • 부울
  • 문자열
  • Variant의 벡터
  • Variant에 대한 문자열 맵

이 방법으로 SetValue()를 사용하면 지정된 위치에서 하위 노드를 포함하여 모든 데이터를 덮어씁니다. 그러나 전체 객체를 다시 쓰지 않고도 하위 항목을 업데이트하는 방법이 있습니다. 사용자가 프로필을 업데이트하도록 허용하려면 다음과 같이 사용자 이름을 업데이트할 수 있습니다.

dbref.Child("users").Child(userId).Child("username").SetValue(name);

데이터 목록에 추가

멀티 사용자 애플리케이션에서 목록에 데이터를 추가하려면 PushChild() 메서드를 사용합니다. PushChild() 메서드는 지정된 Firebase 참조에 새 하위 요소가 추가될 때마다 고유 키를 생성합니다. 목록의 새 요소마다 이러한 자동 생성 키를 사용하면 여러 클라이언트에서 쓰기 충돌 없이 동시에 하위 요소를 같은 위치에 추가할 수 있습니다. PushChild()가 생성하는 고유 키는 타임스탬프에 기반하므로 목록 항목은 시간순으로 자동 정렬됩니다.

PushChild() 메서드가 반환하는 새 데이터에 대한 참조를 사용하여 하위 요소의 자동 생성 키 값을 가져오거나 하위 데이터를 설정할 수 있습니다. PushChild() 참조에 대해 GetKey()를 호출하면 자동 생성 키 값이 반환됩니다.

특정 필드 업데이트

다른 하위 노드를 덮어쓰지 않고 특정 하위 노드에 동시에 쓰려면 UpdateChildren() 메서드를 사용합니다.

UpdateChildren()를 호출할 때 키 경로를 지정하여 더 낮은 수준의 하위 항목 값을 업데이트할 수 있습니다. 확장성 개선을 위해 데이터를 여러 위치에 저장한 경우 데이터 팬아웃을 사용하여 해당 데이터의 모든 인스턴스를 업데이트할 수 있습니다. 예를 들어 게임은 다음과 같은 LeaderboardEntry 클래스를 사용할 수 있습니다.

class LeaderboardEntry {
  std::string uid;
  int score = 0;

 public:
  LeaderboardEntry() {
  }

  LeaderboardEntry(std::string uid, int score) {
    this->uid = uid;
    this->score = score;
  }

  std::map<std::string, Object> ToMap() {
    std::map<string, Variant> result = new std::map<string, Variant>();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

LeaderboardEntry를 만들고 최근 점수 피드 및 사용자의 점수 목록을 동시에 업데이트하려는 경우 게임은 다음과 같은 코드를 사용합니다.

void WriteNewScore(std::string userId, int score) {
  // Create new entry at /user-scores/$userid/$scoreid and at
  // /leaderboard/$scoreid simultaneously
  std::string key = dbref.Child("scores").PushChild().GetKey();
  LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
  std::map<std::string, Variant> entryValues = entry.ToMap();

  std::map<string, Variant> childUpdates = new std::map<string, Variant>();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

이 예시에서는 PushChild()를 사용하여 모든 사용자의 항목을 포함하는 노드(/scores/$key)에서 항목을 작성하는 동시에 key()로 키를 검색합니다. 그런 다음 이 키로 /user-scores/$userid/$key에서 사용자의 두 번째 항목을 작성합니다.

이러한 경로를 사용하면 이 예시에서 두 위치에 새 항목을 생성한 것처럼 UpdateChildren()를 한 번만 호출하여 JSON 트리의 여러 위치에 동시에 업데이트할 수 있습니다. 이러한 동시 업데이트는 원자적 성격을 갖습니다. 즉, 모든 업데이트가 한꺼번에 성공하거나 실패합니다.

데이터 삭제

데이터를 삭제하는 가장 간단한 방법은 해당 데이터 위치의 참조에 RemoveValue()를 호출하는 것입니다.

SetValue() 또는 UpdateChildren() 등의 다른 쓰기 작업 값으로 null Variant을 지정하여 삭제할 수도 있습니다. UpdateChildren()에 이 방법을 사용하면 API 호출 한 번으로 여러 하위 항목을 삭제할 수 있습니다.

데이터가 커밋되는 시점 알기

Firebase 실시간 데이터베이스 서버에 데이터가 커밋되는 시점을 알려면 성공에 대한 Future 결과를 확인합니다.

데이터를 트랜잭션으로 저장

증가 카운터와 같이 동시 수정으로 인해 손상될 수 있는 데이터를 다룰 때 트랜잭션 작업을 사용할 수 있습니다. 이 작업에 DoTransaction 함수를 지정합니다. 업데이트 함수는 데이터의 현재 상태를 인수로 취하고 이 데이터에 새로 기록하려는 값을 반환합니다. 새 값이 기록되기 전에 다른 클라이언트에서 해당 위치에 기록하면 업데이트 함수가 새로운 현재 값으로 다시 호출되고 쓰기가 다시 시도됩니다.

예를 들어 게임에서 사용자가 최고 점수 5개로 리더보드를 업데이트하도록 허용할 수 있습니다.

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() >= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector<MutableData> children = mutableData.children();
      std::vector<MutableData>::iterator it;
      for (it = children.begin(); it != children.end(); ++it) {
        if (!it->value().is_map())
          continue;
        long childScore = (long)it->Child("score").value().int64_value();
        if (childScore < minScore) {
          minScore = childScore;
          minVal = &*it;
        }
      }
      if (minScore > score) {
        // The new score is lower than the existing 5 scores, abort.
        return kTransactionResultAbort;
      }

      // Remove the lowest score.
      children.Remove(minVal);
    }

    // Add the new high score.
    std::map<std::string, Variant> newScoreMap =
      new std::map<std::string, Variant>();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

트랜잭션을 사용하면 여러 사용자가 동시에 점수를 기록하거나 클라이언트 데이터의 동기화가 어긋나도 리더보드가 잘못되지 않습니다. 트랜잭션이 거부되면 서버에서 현재 값을 클라이언트에 반환하며, 클라이언트는 업데이트된 값으로 트랜잭션을 다시 실행합니다. 트랜잭션이 수락되거나 시도가 일정 횟수를 초과할 때까지 이 과정이 반복됩니다.

오프라인으로 데이터 쓰기

클라이언트의 네트워크 연결이 끊겨도 앱은 계속 정상적으로 작동합니다.

Firebase 데이터베이스에 연결된 모든 클라이언트는 자체적으로 활성 데이터의 내부 버전을 유지합니다. 데이터를 쓰면 우선 로컬 버전에 기록됩니다. 그런 다음 Firebase 클라이언트가 해당 데이터를 원격 데이터베이스 서버 및 다른 클라이언트와 '최선을 다해' 동기화합니다.

이와 같이 데이터베이스에 대한 모든 쓰기 작업은 로컬 이벤트를 즉시 트리거하며, 그 이후에 서버에 데이터가 기록됩니다. 따라서 앱은 네트워크 지연 또는 연결 여부에 관계없이 응답성을 유지합니다.

네트워크에 다시 연결되면 앱에서 적절한 이벤트 세트를 수신하여 클라이언트와 현재 서버 상태를 동기화하므로 맞춤 코드를 별도로 작성할 필요가 없습니다.

다음 단계