개발새발 - IT 기술블로그
article thumbnail

 

데이터의 저장

보통 우리는 어떠한 값을 저장할 때 데이터 저장소인 DB를 이용합니다.

저장 하려는 데이터가 원시 데이터일 경우 안드로이드 SharedPreference API를 이용하기도 하고, 외부통신 없이 빠르게 로컬환경에서 데이터를 불러오기 위해 SQLiteRoom을 이용하기도 하죠. 또한 데이터의 양이 많아지고, 공유하는 환경이 늘어난다면 서버에 연결된 외부 DB에 요청을 보냅니다.

 

 

관계형 데이터베이스(RDB)와 비관계형 데이터베이스(No SQL)

일반적인 DB구조에서 관계형 데이터베이스(RDB)는 스키마와 스키마를 요구하는 테이블의 구조를 따릅니다. 따라서 흔히 CRUD라고 부르는 데이터 조작어를 기본 명령어로 가집니다. 반대로 비관계형 데이터베이스(No SQL)는 테이블 구조에서 벗어난 비정형 구조의 데이터를 다룹니다. 오늘 소개드릴 Realtime Database도 NoSQL에 해당합니다. 정형화된 구조인 CRUD가 아닌 set, get, update, remove등의 명령어를 지원합니다.

 

 

기본원리와 장점

정확한 구조는 알 수 없었지만 공식문서에 따르면 Realtime Database는 HTTP 요청방식이 아닌 동기화 방식의 통신으로, 데이터가 변경될 때 연결된 모든 기기가 동기화를 진행하여 업데이트하는 방식의 구조라고 소개하고 있습니다.(아마 Pub/Sub 구조인듯?) 또한 작업 실행 속도를 위주로 설계되어 정말 빠르고 쾌적한 환경에서 DB를 이용할 수 있습니다. 실제로 코드에 적용해 보면 서버(구글서버)를 통한 통신이 맞나싶을 정도로 짧은 지연시간과 반영속도를 보여줍니다. 그리고 서버에서 트래픽을 관리하지 않아도 된다는 점도 좋은 것 같습니다.

 

 

클라이언트 기기에서 엑세스 가능

저는 Realtime Database를 사용하면서 이 점이 가장 놀라웠습니다. 아래처럼 서버에서 보아야 하는 코드들이 클라이언트에서 작성되어 사용되는 것은 확실히 기존 데이터베이스 서버와의 통신방식과는 달랐습니다.

firebase.child("users").child(userId).get().addOnSuccessListener {
    var value = it.value
    ...
}.addOnFailureListener{
    val message = it.message
    val code = it.code
    ...
}

하지만 이러한 코드들이 늘어날수록 클라이언트 측 코드의 양이 점점 증가하는 것을 의미하기 때문에, 앱서버가 불필요한 이점과 잘 비교하여 선택하셔야 할 듯 합니다.

 

 

실제로 사용해보기

어느정도 Realtime Database에 대한 이해가 되셨으면 실제로 DB환경을 구축하고 호출하는 예제를 진행해 보겠습니다.

 

 

 

프로젝트 만들기

 

Firebase 메인페이지 링크

위 링크로 이동하고 구글에 로그인하면  프로젝트 만들기 버튼이 보이실 겁니다. 클릭해줍시다.

그다음 프로젝트의 이름을 지정하고 약관에 동의한 후 계속버튼을 눌러주시면 됩니다.

 

 

 

Google Analytics 사용설정

 

구글 애널리틱스를 사용할지에 대한 여부를 설정합니다. 동의하면 명세되어있는 기능들을 사용할 수 있는데, 권장사항이니 동의하시는것을 추천드립니다. 저는 단순히 데이터베이스 예제만을 위해 생성하는 것이라 동의하지 않았습니다.

 

 

 

프로젝트 생성완료

 

이렇게 프로젝트가 생성되었습니다. 메인페이지로 돌아오면 프로젝트 리스트에 생성한 프로젝트가 추가되어 있는것을 확인 할 수 있습니다.

 

 

 

Android에 Firebase 추가하기

 

프로젝트 메인페이지에서 Android를 클릭하고 앱 등록에 관한 정보를 입력합니다.

 

 

 

 

 

※ 앱 SHA-1 해시값 빠르게 불러오는 방법

더보기

아래 사진의 순서대로

진행해주시면 됩니다. 처음사진의 Gradle은 안드로이드 스튜디오 가장 우측 상단에 있습니다.

 



 

google-service.json 다운로드

다운로드 받은 json 파일을 안드로이드 프로젝트 app폴더 안에 붙여넣어 줍니다. 

 

 

 

 

데이터베이스 만들기

 

환경설정까지 끝냈으면 웹페이지로 돌아와 좌측메뉴에 Realtime Database를 클릭하고 데이터베이스 만들기를 클릭합니다.

 

 

 

 

데이터베이스 세팅

 

테스트 모드에서 시작합니다.

 

 

 

 

이렇게 빈 화면이 나오면 정상적으로 생성된 것입니다.

 

 

 

 

프로젝트 환경설정

 

API 사용을 위해 프로젝트 환경을 설정해줍니다.

 

<AndroidManifest.xml>

<uses-permission android:name="android.permission.INTERNET"/>

 

 

<build.gradle(Module)>

plugins {
    ...
    id 'com.google.gms.google-services'
}

...

implementation platform('com.google.firebase:firebase-bom:31.2.0')
implementation 'com.google.firebase:firebase-database-ktx'

 

 

<build.gradle(Project)>

plugins {
   ...
    id 'com.google.gms.google-services' version '4.3.15' apply false
}

 

이제 DB에 데이터를 추가해보겠습니다. 

 

 

 

인스턴스 생성

 

first 라는 이름으로 레퍼런스를 생성했습니다.

private val db = Firebase.database
private val myRef = db.getReference("first")

 

 

 

데이터 쓰기(1)

private fun writeValue(data: String) {
    myRef.setValue(data)
}
writeValue("test data")

 

 

 

디버깅을 시킨 후 웹페이지를 확인해보면 데이터가 정상적으로 추가된 것을 확인하실 수 있습니다.

 

 

데이터 쓰기(2)

 

하위 경로를 생성하고 데이터를 write 해보겠습니다. child 메서드를 사용하시면 됩니다. first경로 다음 second 다음 third에 데이터가 추가되었습니다. 

private fun writeValue(data: String) {
    myRef.child("second").child("third").setValue(data)
}

 

 

 

눈치채신 분들도 있겠지만 Realtime Database의 setValue는 기본적으로 가장 하위경로에 있는 값을 갱신하는 형태로 진행됩니다. 따라서 같은 경로에 여러값을 누적시키고 싶다면 push() 메서드를 이용해 경로를 생성하고 값을 추가하는 형태로 구현하시면 됩니다.

 

+ 2023-08-16 추가 설명 : value는 하나의 key를 쌍으로 갖기 때문에 마지막 child와 한 쌍으로 묶입니다. 따라서 push()메서드를 이용하지 않아도 마지막 child만 다르게 해주면 값을 같은 카테고리에 누적시킬 수 있습니다.

myRef.child("second").child("third").push().setValue(data)
writeValue("test data")
writeValue("test data2")

 

 

 

데이터 읽기

 

데이터를 읽는 방식은 addValueEventListener를 override하여 데이터의 변화가 감지되었을 때 콜벡을 받는 방식으로 구현합니다.

아래 예제에서는 리스너를 구현한 후 writeValue로 값을 추가 해 로그를 출력하는 방식입니다.

myRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        Log.d("TAG_DB", "onDataChange : ${snapshot.value}")
    }

    override fun onCancelled(error: DatabaseError) {
        val code = error.code
        val message = error.message
        Log.e("TAG_DB", "onCancelled by $code : $message")
    }
})

writeValue("test data3")
D/TAG_DB: onDataChange : {second={third={-NQP59PXueRq3H7vF4j8=test data, -NQXZEaKC-5QMYsazM9N=test data4}}}

 

 

 

좀 더 세부적인 경로의 데이터를 가져오려면 .child(String)을 이용하여 원하는 경로를 설정해주시면 됩니다.

 

 

 

 

데이터 삭제

 

DB의 데이터를 삭제할 땐 removeValue 명령어를 사용하시거나 setValue의 값을 null로 주시면 됩니다.

myRef.child("second").child("third").removeValue()

myRef.setValue(null)

 

 

 

글을 마치며

이렇게 Firebase의 Realtime Database에 대해 알아보았는데요. 개인적인 사용후기는 정말 편하고 빠르고 좋은 것 같습니다. 애초에 서버의 부담없이 프로젝트를 진행할 수 있다는 장점이 매우 크다고 생각합니다.

 

다만, 앞서 말씀드린대로 클라이언트의 코드가 방대해지면서 유지보수나 앱의 크기 측면에서 부담이 생길 수 있는 부분은 사실인 것 같습니다. 그리고 실제 서비스에 적용하려고 하면 높은 수준의 DB구성을 적용하기엔 예제나 문서가 많이 부족하다고 생각합니다. 

 

따라서 사용성을 고려할 때 다양한 측면을 생각하고 확실한 경우에 사용하시는 것을 추천드립니다. 감사합니다.

 

 

 

 

참고

https://firebase.google.com/docs/database?hl=ko 

 

Firebase 실시간 데이터베이스

NoSQL 클라우드 데이터베이스로 데이터를 저장하고 동기화하세요. 모든 클라이언트에서 실시간으로 데이터가 동기화되고 앱이 오프라인일 때도 데이터를 사용할 수 있습니다.

firebase.google.com