Firebase Realtime Database 사용해 보기

Firebase Realtime Documentation

firebase_core | Flutter Package
firebase_database | Flutter Package

Firebase 세팅하기 - Flutter 3.0 이후
Firebase 세팅하기 - Flutter 3.0 이전

이번 글에서는 Firebase의 데이터베이스 중 하나인 Realtime Database에 대해서 알아보도록 하자.

이전 시간까지 Firestore 데이터베이스에 대해서 살펴보았는데, Realtime Database는 Firestore 출시 이전 부터 사용해오던 데이터베이스다.

Realtime 데이터베이스도 Firestore와 마찬가지로 NoSQL DB인데, Firestore 보다는 좀 더 친숙한 json 형태이다.
Firestore와 Realtime 데이터베이스의 공통점은 NoSQL DB의 공통된 요소가 포함되있다고 볼 수 있고, 큰 차이점은 Query와 가격이라고 생각한다.

저도 Realtime 데이터베이스는 용량이 적고 호출이 잦은 경우에만 가볍게 사용하고 있고, 메인은 Firestore로 개발을 해왔다.

Realtime의 가벼운 기능만 사용해봤고, 복잡한 쿼리에 대해서는 경험한 적이 없을뿐더러 Firestore만 사용해도 되기에 거의 사용을 안한다고 보면된다. 하지만 Realtime도 Firestore와 마찬가지로 하루 무료 사용량을 제공하고 있으니, 적절하게 사용한다면 리소스를 줄이는 역할로는 사용할만 하다.

Flutter Package에서 확인하더라도 Like 숫자가 압도적으로 Firestore가 높다고 볼 수 있다. 하지만 상황에 따라 더 효율이 좋은 DB를 사용하여야 하기에 Realtime에 대한 사용 방법도 미리 익히고, 사용해보면서 Firebase에서 제공하는 DB의 차이점을 파악해보면 좋을 것 같다.

Firebase

Firebase에서 Realtime Database를 선택하자. 아직 생성한 DB가 없으면 아래와 같이 나온다. 데이터베이스 만들기를 클릭하여 Realtime을 생성해보자.

Firestore와 마찬가지로 Region을 선택할 수 있는데, 선택지가 Firestore에 비해서는 적은편이다.
원하는 지역을 선택해서 다음을 클릭하자.

보안규칙을 선택해야 하는데, 테스트 모드로 선택해서 사용하고 실제 앱을 배포하려면 잠금 모드로 변경할 수 있다. 우선 테스트 모드로 생성을 하자.

처음 생성되면 아무것도 없는 데이터베이스가 생성되어 있다. 여기에서 데이터를 json 형태로 볼 수 있는데, 이 부분은 천천히 다뤄보도록 하겠다.

규칙 탭으로 이동하면 Firestore와 동일하게 보안규칙을 작성할 수 있다. Firestore에서 테스트 모드의 기간을 DateTime 형태로 변경할 수 있었는데, Realtime은 Timestamp 형태를 지원하여서 테스트 기간을 늘리고 싶다면 원하는 Timestamp로 변경해주면 되고, 실제 배포를 하려고 하면 보안규칙을 작성하면 된다.

사용량 탭에서 Realtime에 대한 리소스를 실시간으로 확인할 수 있다.

Flutter

Flutter에서 Realtime의 기본적인 기능인 데이터 생성/수정/삭제/읽기에 대해서 가볍게 살펴보도록 하겠다. Firestore에 비햇서는 기능이 많지 않고 복잡한 Query작업을 지원하지 않아 사용 방법에 대해서만 간단하게 살펴보도록 하겠다.

dependencies

dependencies:
	firebase_core: ^2.7.0
	firebase_database: ^10.0.15

Create

Realtime 데이터베이스에 간단한 데이터를 저장하도록 하자. 사용 방법은 어렵지 않다.
path로 경로를 사용하여 저장하면 된다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref().child("test").set({
		"testId": 123,
	});

이번에는 반복문을 통해 5개의 데이터를 차례로 저장해보자. Firestore와는 다르게 데이터가 순서대로 저장이 되는 것을 확인할 수 있다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
for (int i = 0; i < 5; i++) {
		await _realtime.ref().child("test$i").set({
			"testId": i,
		});
	}

이번에는 /users 경로안에 /tyger 라는 사용자의 정보를 저장하도록 하자.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref().child("users").child("tyger")
	.set({
		"id": 1,
		"name": "tyger,",
	});

위에서 저장한 데이터를 똑같이 다시 저장해보자.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref("users")
				.child("tyger").set({
                  "id": 1,
                  "name": "tyger,",
                });

이번에는 /tyger 경로에 followers 라는 배열 타입의 데이터를 추가해보자. 기존 데이터에 새로운 데이터가 추가된 것을 확인할 수 있다.

경로만 같지 않으면 데이터를 덮어씌우지 않는다는 것을 확인할 수 있다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref("users").child("tyger")
				.child("followers").set([
                  "lion",
                  "puma",
                ]);

아래와 같이 TestModel 객체를 생성해서 데이터를 객체 형태로 저장하도록 해보자.

class _TestModel {
  final int id;
  final String name;
  final List<String> followers;

  _TestModel(this.id, this.name, this.followers);

  Map<String, dynamic> toJson() {
    return {
      "id": id,
      "name": name,
      "followers": followers,
    };
  }
}

/users에 /lion 데이터를 저장해보자.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime
			.ref("users")
			.child("lion")
			.set(_TestModel(2, "lion", ["tyger"]).toJson());

Update

데이터를 변경하는 Update 기능도 Firestore와 거의 동일하다고 보면 된다. update를 사용해서 데이터를 변경할 수도 있고, 데이터를 읽어와 변경시킨 상태로 덮어 씌울 수도 있다.

/tyger의 id 값을 1에서 3으로 변경해보자.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref("users").child("lion").update({"id": 3});

이번에는 기존에 없었던 필드에 Update 기능을 사용해 보겠다. 어떤 일이 발생할까 ?
/lion의 age 필드를 10으로 변경하도록 하였지만, age 필드가 없기 때문에 새로 생성한 것을 확인할 수 있다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref("users").child("lion").update({"age": 10});

위에서 사용한 TestModel 구조에 age 값을 추가하자.

class _TestModel {
  final int id;
  final int age;
  final String name;
  final List<String> followers;

  _TestModel(this.id, this.age, this.name, this.followers);

  Map<String, dynamic> toJson() {
    return {
      "id": id,
      "age": age,
      "name": name,
      "followers": followers,
    };
  }
}

Update 기능에서도 Object로 데이터를 변경시킬 수 있다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime
			.ref("users")
			.child("lion")
			.update(_TestModel(2, 11, "puma", []).toJson());

Delete

저장된 데이터를 삭제하는 방법에 대해서도 살펴보자. 삭제도 Firestore 사용 방법과 거의 동일하다고 보면된다.

해당 경로를 찾아 remove 함수를 사용하면 삭제가 된다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref("users").child("lion").remove();

데이터가 없으므로 새로운 데이터를 저장하자.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
                await _realtime
                    .ref("users")
                    .child("puma")
                    .set(_TestModel(4, 4, "puma", [""]).toJson());

이번에는 /users 경로를 삭제해보자. /users 아래 경로에 있는 데이터가 전부 삭제된 것을 확인할 수 있다. 사용시 주의 해야 한다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
await _realtime.ref("users").remove();

Read

이번엔 저장된 데이터를 Realtime에서 어떻게 읽어오는지에 대해서 알아보자.

우선 저장된 데이터가 없어 반복문으로 여러 데이터를 저장시켜 보자ㅏ.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
for (int i = 0; i < 20; i++) {
			await _realtime.ref("users").child("tyger$i").set(
                        _TestModel(i, i * 3, "tyger$i").toJson(),
			);
	}

데이터를 읽어올 때는 get을 사용하면 되고, 경로에 해당하는 데이터를 Map 타입으로 리턴한다. 여기서도 Firestore와 동일하게 snapshot을 사용하는 것을 확인할 수 있다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
DataSnapshot _snapshot = await _realtime.ref("users").child("tyger1").get();
Map<dynamic, dynamic> _value = _snapshot.value as Map<dynamic, dynamic>;

TestModel 객체에 fromJson을 추가하여 데이터를 객체로 변환시켜 오자.

class _TestModel {
  final int id;
  final int age;
  final String name;

  _TestModel({required this.id, required this.age, required this.name});

  factory _TestModel.fromJson(Map<dynamic, dynamic> json) {
    return _TestModel(
      id: json["id"],
      age: json["age"],
      name: json["name"],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      "id": id,
      "age": age,
      "name": name,
    };
  }
}

/users 경로에 있는 데이터를 가져와 Object로 변경하엿다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
DataSnapshot _snapshot = await _realtime.ref("users").get();
Map<dynamic, dynamic> _toMap = _snapshot.value as Map<dynamic, dynamic>;
List<_TestModel> _data = _toMap.values.map((e) => _TestModel.fromJson(e)).toList();

아래와 같은 방법으로 사용하여도 된다. 결과는 동일하다.

FirebaseDatabase _realtime = FirebaseDatabase.instance;
DataSnapshot _snapshot = await _realtime.ref("users").get();
_snapshot.ref.onValue.listen((event) {
      logger.e(event.snapshot.value);
    });

마무리

Realtime 데이터베이스에 대해서는 간단하게 살펴보았다. 사실 Firestore 서비스가 시작된 시점 부터 Realtime의 역할이 많이 줄어든 상태라 저도 잘 사용하지 않고 있는 것은 사실이다.

Realtime에서도 Firestore와 동일하게 쿼리를 지원하고는 있지만 Firestore에 비해서는 쿼리 수준이 낮은 편이라 생가한다.

하지만 Firestore 사용시에 건수당 비용이 발생하는 부분이 있어 Realtime을 전혀 사용하지 않는 것은 아니다.

단순하면서 가벼운 데이터베이스 사용이 필요한 경우 Realtime이 좋은 선택이 될 것이다.

지금까지 Firestore, Realtime 데이터베이스에 대해서 살펴봤는데, 의문이 생겼을 수도 있을 것 같다. 바로 서비스의 큰 비중을 차지하는 이미지, 동영상 등의 컨텐츠를 저장하는 방법을 설명하지 않는 것이 이상하게 생각될 수 있는데, 컨텐츠를 저장하는 것은 Firestore, Realtime 데이터베이스 역할이 아니다.

이미지, 동영상 등의 컨텐츠를 저장소에 저장하고 다운로드 받아서 노출 시키기 위해 사용하는 Firebase Storage 저장소에 대해서 다음 시간에 살펴보도록 하겠다.

profile
Flutter Developer

0개의 댓글