Seize the day

POST : Android Dev Study

Google firebase 쓸만한가?


쓸만한가?라는 주제로 글을 쓰게 될 줄은 몰랐는데 firebase로 한 2주간 습작을 만들어 보면서 느낀 점을 적어본다. 사실 firebase가 google cloud(or app engine)의 새 버전이라는 것을 알게 된 것도 공부삼아 채팅 프로그램 같은 것을 만들어 보려고 검색하다가 최근에 알게되었다. 구글 app engine을 사용해 본 경험이 있고 app engine이 python, java, node-js도 지원한다는 것을 알고 있었기때문에 app engine 서버에서 connection을 유지하면서 실시간 채팅이 가능하게 하려면 어떻게 서버를 구성해야하나 찾아보고 있었다.  app engine에 node-js를 올리고 socket-io를 이용하면 쉽게 시작할 수 있다는 것은 이미 알고 있었고 구현 경험도 있었지만, 내가 원하는 것은 node-js를 제외한 방식이다. Kotlin을 사용하고 싶었기때문에 Java 언어를 지원하면서 socket-io를 지원하는 방식이면 좋겠는데 그런 건 서버를 돌리지 않으면 불가능해보였다. 도커를 이용해 서버를 직접 운영방식으로 가능하지만 왠지 귀찮고 도커를 이용하면 사용여부와 관계없이 운영시간만큼 과금이 되기때문에 습작같은 것을 만드는데 비용문제도 고려하지 않을 수 없었다. 


그런 와중에 firebase realtime database라는 것이 눈에 들어왔다. connection 유지가 필요한 이유는 유지가 되어 있으면 직접 메시지를 보내고 그렇지 않으면 push 메시지를 보내기위해서인데, firebase realtime db에 서버 db에 변경사항이 발생하면 클라이언트로 자동적으로 전달되고, db연결이 끊어졌는지 여부도 알 수가 있었다. 서버도 필요없다고 하니 음 좋구나 했는데 이런 글들을 보게됬다. 


https://brunch.co.kr/@bokyungkimp19d/5

https://crisp.chat/blog/why-you-should-never-use-firebase-realtime-database/

https://medium.freecodecamp.org/firebase-the-great-the-meh-and-the-ugly-a07252fbcf15

https://www.raizlabs.com/dev/2016/12/firebase-case-study/


대부분 firebase realtime database의 구조적 문제를 지적한 내용이다. realtime database NoSQL의 한계도 생각보다 심각했다. 조건 두 개로 쿼리가 불가능하다니??? 하지만 이건 NoSQL자체의 문제이니 NoSQL에 맞는 데이타 모델링으로 해결할 수 밖에 없을 것 같다. 그렇지만 무엇보다도 realtime database의 동시접속이 무료버전에서는 100개가 최대라는 것이 걸렸다. 100개는 너무 적지 않은가? db의 최대 사이즈가 1기가이고, 1기가 넘어선 것은 분리를 해야한다는데 1기가 이상쓸 일도 없겠지만 미래의 이런 문제의 가능성을 안고 시작하기에는 db가 너무 허접하다는 생각이 든다.  아무튼 베타이기는하지만 Firestore라는 새로운 DB가 나왔고, Functions라는 것도 있어서 구조적인 문제는 어느 정도 해결이 가능해진 것 같았다. realtime database로 몇 일 개발하다가 Firestore를 사용하기로 결정하고 connection 연결 체크를 찾아봤는데 방법이 없었다. 가이드에도 realtime database + Functions를 이용하라고 되어 있다. 베타가 맞긴한가 보다. https://firebase.google.com/docs/firestore/solutions/presence?authuser=0




앱에서 status 변경사항 업데이트시키기

Application클래스의 registerActivityLifecycleCallbacks를 이용해서 앱의 상태를 탐지한다.
state는 0은 offline, 1은 online, 2는 background 상태이다. 



Real time db에 업데이트한다.

   status/{uid}

         status: 0

         updatedAt: SERVER TIMESTAMP


DataManager라는 싱글톤에서 realtime db의 status를 업데이트시킨다. 여기서 삽질한 거 하나 공개하자면, app모듈의 gradle.build에서 아래 apply를 파일의 맨 아래에 놓아야 에러가 안 난다. 


// Add this at bottom

apply plugin: 'com.google.gms.google-services'





Node-js로 구현하는 Functions에서 status의 변경사항을 감지하고, Firestore에 업데이트한다. 

   users/{uid}

status

updatedAt 


기본적으로 가이드를 참고하면 크게 문제가 없다. 코드도 가이드의 코드가 대부분 https://firebase.google.com/docs/functions/get-started?authuser=0

package.json에 아래 버전을 추가해 준다.
"@google-cloud/firestore": "^0.9.0" 


그리고 @google-cloud/firestore가 추가되었기 때문에 필요한 모듈을 모두 설치해 주어야 하는데 가장 좋은 방법은 index.js 파일을 모두 수정하고
firebase init functions
을 다시 실행해서 index.js, json등 소스파일은 덮어쓰지 않기를 하는 방식이 최선인 것 같다. 모듈을 하나씩 설치하면서 배포하는 것은 시간낭비다. 모듈이 없다는 에러를 출력하기는 하지만 하나씩만 보여주기때문에 필요한 모듈을 한 번에 설치할 수 없고 배포도 시간이 많이 걸리기 때문..


collection의 이름에 debug-가 들어간 것은 앱이 디버그 모드일 때 사용하는 db collection 이름이고, 마켓이 올라가는 버전은 real-status를 사용한다. db는 이런식으로 prefix를 붙여서 분리하기로 했다. 따라서 Function은 두 개가 필요하다.




클라이언트는 Firestore db의 users 연결정보를 통지받으면 status가 1(ONLINE)이 아닌 경우 Functions를 통해서 푸시 메시지를 전달한다. 

클라이언트는 간단히 tokens와 payload를 request body에 넣어전달하면 된다. tokens는 string | string[] 둘 다 가능하다. playload는 클라이언트에서 gcm 스펙에 맞게 꾸미기 나름이다.  여기서는 테스트를 위해서 notification:body만 넣었다. 





node-js sendPush Function에서는 FCM을 통해서 push 메시지를 전송한다. 

serviceAccountKey.json 파일은 가이드대로 firebase 콘솔에서 내려받아서 index.js가 있는 폴더에 두었다. 그런데 "./"를 붙이지 않으면 파일을 찾지 못한다. tokens와 payload만 적절하다면 푸시 전송은 성공할 것이다. topic이나 group 전송은 필요할 경우 추가 구현한다. 이렇게 해서 서버 키를 클라이언트 코드에 노출시키지 않으면서 여러 클라이언트에 동시에 푸시 전송을 한 번의 요청으로 가능하게 되었다. 

가이드에 보면 https 방식의 function은 도메인을 확인해보라고 하는데 아무리 찾아봐도 내가 속한 region을 찾을 수가 없었는데, 혹시나해서 일단 배포를 해 보았더니 대시보드에 https://us-central1-XXXXXXXXX.cloudfunctions.net/sendPush 라는 도메인이 보였다. 




클라이언트는 push메시지를 받으면 노티를 등록한다.  

코드는 생략한다. FCM을 클라이언트에 적용하는 예제는 가이드에 잘 나와있다. https://firebase.google.com/docs/cloud-messaging/android/receive?authuser=0



참고할 것은 JobScheduler라는 모듈이 있는데 이게 원래 min api가 21인데, firebase-jobdispatcher는 자체 구현한 것으로 min api가 9이다. Google play service가 필요하다는 것은 참고. 

https://github.com/firebase/firebase-jobdispatcher-android#user-content-firebase-jobdispatcher-




친구 관계 NO-sql 모델링

이것 때문에 고민이 많았는데, 고민의 대부분은 No-sql을 처음하다 보니 이게 맞는 방식인지 아닌지 확신이 없어서 더 나은 방식이 있나 찾아보는 것이었다. 


내 아이디는 M이고 A, B가 내 친구라면 

users/A (라는 document가 있고)

       friendOf

          M = true


users/B (라는 document가 있고)

       friendOf

          M = true


firestore에서는이런식으로 저장을 한다. 즉 A document안의 frindOf는 A를 친구로 추가한 유저 아이디의 map 정보인 것이다. (A의 친구 목록이 아니다) frinedOf는 하위 collection이 아니다. friendOf를 하위 collection으로 만들 수도 있는데 문제가 하위 collection은 쿼리시 조건으로 걸 수 없기때문에 M의 모든 친구 리스트를 쿼리할 수 없다. 


friendOf의 요소는 개별적으로 추가하거나 삭제할 수도 있기때문에 set의 개념으로 봐야한다. 문제는 이 정보가 매우 많을 경우라도 friendOf의 모든 정보는 앱에 모두 전달될것으로 보인다. 앱에서 실제로 사용되는 정보는 아님에도 말이다. 


참고로 이런방식은 realtime db에서도 가능했다. 실제 가이드에서도 이런 모델링을 가이드하고 있다. 


이렇게 해서 내 친구의 fcmToken이 변경되거나 status가 변경될 경우, 이름, 프로필 이미지 등이 변경될 경우 실시간으로 전달받아 업데이트 할수 있게 되었다. 



결론

realtime db만으로는 구조적 문제가 많아 서비스를 만들어나가기 어려울 것이나, firestore + nodejs functions 라면 괜찮을 것 같다. 


top

posted at

2017. 12. 1. 23:12


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크