P & D
Research and Development가 아니라 Philosophy and Development의 P & D 입니다. 개발글에 있어서 새로운 시도를 하고 있습니다. 그저 개발기를 적어 내려가는 것이 아니라, 어떤 철학을 가지고 개발을 해나가고 있는지, 어떤 시도를 했는지, 개발은 어떻게 하는지를 적어 내려갑니다. 이번 시리즈는 도움을 주고 받는 방법 - 이타인클럽의 철학과 개발 과정을 다룰 것입니다.
이전글 - [철학과 개발] #20 Navigation - 대세를 따르라
철학: DB 설계에는 시행착오가 따른다!
앱 사용자들의 정보, 채팅 내용, 도움 내용 등을 관리하기 위해서는 Database (DB)가 필요합니다. 중요한 것은 경험이 없으면 DB 설계하는 것이 매우 막연합니다.
어떤 DB를 쓸지, 어떤 구조로 가져가야 할지.. 앱 개발하면서 가장 난감한 부분이었습니다. 그것은 DB 개발 경험이 없었기 때문이니다.
앱 개발 구상도는 있었지만, 초기 구상한 DB가 개발하면서 잘못되어있다는 것을 느꼈습니다. 물론 개발 경험이 없어도 논리적으로 따져서 초기부터 완성된 DB 구조를 만들 수도 있습니다. 그러나 쉽지 않은 작업입니다.
Firebase기준 용어 설명
- field: 데이터의 최소 단위. 사용자이름과 같은 데이터 저장. field는 문자열, 숫자, 배열 등이 있음
- document: field등을 모아놓은 단위. 사용자이름, 사용자위치, 이메일 등을 하나의 document로 관리.
- collection: 여러 documents를 모아 놓은 단위. DB의 최상위 단위임. 여러 사용자 documents를 관리
개발 경험이 많지 았지만, 앱 개발하면서 DB 구조 잡을 때 고려할 것을 정리하면 다음과 같습니다.
1 데이터 충돌/중복 방지:
DB에 데이터를 저장할 때, 사용자별로 중복되지 않게 해야 합니다. 예를 들어 어떤 데이터의 document id가 생성된 순서로 숫자를 지정하게 되면, DB 접속/쓰기 시간차에 따라 데이터 중복, 충돌이 발생합니다.
따라서, 가급적 document id는 시스템이 자동생성하게 하는 것이 좋습니다.
2. 다단계 데이터 관리
DB를 만들다 보면 하나의 document 아래에 새롭게 collection을 만들 수 있습니다. 채팅 데이터의 경우는 채팅이 발생하면 계속 데이터가 새로 추가되어야 하므로, 이러란 다단계 구조가 좋습니다.
3 query 용이성
DB를 만드는 이유는 저장하기 위한 용도보다, 꺼내 쓰는 용도가 중요합니다. 저장만 할 거라면 저장공간에 저장하는 것이 바람직합니다 (Firebase에 Database 기능말고, Storage 기능도 있습니다. 저장만 필요한 것은 Storage에 저장합니다). DB를 설계할 때 데이터를 쉽고, 빠르게 찾을수 있도록 구조를 잡는 것이 중요합니다.
개발
Firebase는 Database용도로 2가지 서비스르 제공하고 있습니다.
- Realtime Database
- Firestore
Firebase에서는 Firestore가 업데이트된 버전으로 소개되어, Firestore 기준으로 설명합니다.
철학편에서 언급했던 내용을 어떻게 구현했는지 살펴보겠습니다.
1 데이터 충돌, 중복
앱 개발 초기에, 도움 요청 메지시 DB collection을 아래와 같이 했었습니다.
그림을 보면, messages collection 밑에 document id로 숫자가 지정되어 있습니다. 즉, 도움 요청이 들어온 순서대로 id로 저장하는 방식입니다. document에는 간단히 요청 메시지가 저장됩니다.
초기 테스트 버전이라 전혀 사용성이 고려되지 않은 방식입니다. 이와 같이 했을 때, 복수의 도움 요청 메시지가 발생했을 때, document의 id를 부여하는 것은 여러 가지 문제를 일으킵니다.
예를 들어, 위와 같이 현재 document가 총 3개가 있는 상태(id가 2까지 부여)에서, 새롭게 요청 메시지가 2개 발생한 경우, DB 접속/쓰기 지연으로 두 메시지 모두 id를 3으로 할당하려고 할 것입니다. 즉, DB에 저장할 때, DB가 최신 상태라고 보장하기가 어렵습니다. 읽는 순간에는 최신이지만, 쓰는 시점에는 다른 메시지가 쓰여질 수 있기 때문에 최신이 아닌 상태입니다. 따라서 누군가 쓴 데이터를 삭제하고 덮어쓸 수 있게 됩니다.
이러한 것은 초보적인 설계 실수입니다. 단, DB의 접근할 수 있는 사용자가 1명 (또는 운영자만 접근)이라면 document id를 숫자로 지정하는 것이 좋을 수도 있습니다.
바람직한 방법은 시스템이 id를 부여하도록 하는 것입니다.
개선된 요청 메시지 DB 구조는 다음과 같습니다.
2 다단계 데이터 관리
위 개선된 도움 요청 메시지 DB에서 도움 요청 메시지마다 chats이라는 sub collection을 가집니다.
이것의 장점은 채팅 메시지와 같이 계속 증가하는 데이터를 관리하기에 용이합니다. Firebase를 포함해서 데이터 쿼리의 관점에서는 연속적으로 DB 트리를 탐색해야 하므로, 덜 효율적일 수 있습니다. 예를 들어, sub collection을 사용하지 않고, document 밑에 바로 field로 채팅 메시지를 저장했을 때, 보다 빨리 데이터를 가져올 수도 있습니다. 그러나 이와 같은 구조에서는 어울리지 않습니다.
결론적으로, 어떤 데이터를 sub collection으로 할지를 신중하게 정해야 합니다.
3 query 용이성
데이터 충돌/중복에서 document id를 생성 순서대로 숫자로 지정한 것은 또 다른 문제를 야기합니다. 앞서 얘기했듯이 DB는 저장용도보다 꺼내쓰는 용도가 중요하다고 했습니다.
이와 같은 경우, document를 새로 생성하기 위해서는 먼저, DB에 접근해서 현재 몇 개의 document가 있는지 체크해야 합니다. 즉, 별도의 작업이 필요한 것이죠. 그리고 바로 이때 충돌, 중복 문제를 야기할 소지를 제공하는 것이 되게 되구요.
document id를 생성 순서로 했을 때, 장점도 있을 수 있습니다. 특정 사용자가생성한 document를 별로로 저장해 놓고 빨리 불러 올수가 있습니다. document id를 시스템이 생성한 경우는 document id를 알 수 없기 때문에 일일이 조사해야 하는 단점이 있습니다. 참고로, Firebase에서 DB에서 이벤트가 발생했을 때, 해당 document의 id를 얻어오는 기능이 있습니다. 이 경우는 document id를 알 수 있기 때문에, 편의를 위해 별도로 저장하여 사용하면 편할 수 있습니다.
그럼에도 불구하고, 데이터 충돌/중복 문제로 인해 특수한 경우를 제외하고 document id는 시스템이 설정하는 것이 좋습니다.
코드
cases라는 도움 요청 collection의 데이터를 query하는 예입니다. 특히 sub collection인 chats의 정보를 생성 시간 순으로 정렬하여 불러들이는 코드입니다.
firebase.firestore().collection('cases')
.doc(`${caseId}`)
.collection('chats')
.orderBy('createdAt', 'desc')
.onSnapshot(snapshot => {
let messages = [];
snapshot.docs.forEach(doc => {
if (__DEV__) console.log('[ChatScreen] doc data', doc.data());
const createdAt = doc.data().createdAt.toDate();
messages = [
...messages,({
_id: doc.data()._id,
text: doc.data().text,
createdAt: createdAt,
user: doc.data().user,
image: doc.data().image
})];
});
위 코드를 보면 caseId라는 부분이 document id 부분입니다. 이것은 Firebase Functions에서 DB의 내용이 변경되었을 때 트리거된 함수에서 획득한 것입니다.
다음은 DB에 데이터를 업데이트 하는 코드입니다.
// case reference
const caseRef = firebase.firestore().collection('cases').doc(`${caseId}`);
// set the vote flag
caseRef.update({ voted: true });
update함수는 document에 votes라는 field값만 업데이트 합니다. 그런데 만약 votes라는 field가 없다면 생성합니다. 이것은 DB를 업데이트하거나 생성할 때 매우 중요합니다. update함수 말고, set 함수를 사용한다면 해당 document의 내용이 모두 사라지고, 오직 votes field만 남게 되니 주의가 필요합니다.
다음은 Firebase에서 document의 개수를 계산하는 코드입니다.
const usersRef = firebase.firestore().collection('users');
// count number of test accounts
let numTesters = 0;
await usersRef.where("tester", "==", true).get()
.then(snapshot => {
numTesters = snapshot.size;
})
.catch(error => console.log(error));
사용자정보를 모아 놓은 users collection에 접근하여 테스터 사용자의 갯수를 계산하고 있습니다.
보다 자세한 DB 관련 코드는 소스코드를 참고하세요.
https://github.com/EtainClub/helpus
도움 주고 받는 앱 helpus
앱 사용자 중 다음과 같은 도움이 가능하 사용자들이 있습니다.
- 최면 상담
- 영어, 일본어 관련 문제
- 건강 상담, 심리 상담
물론, 저도 명상, 루시드 드림 관련 도움을 줄 수 있습니다.
구글 플레이: https://play.google.com/store/apps/details?id=club.etain.helpus
애플 앱 스토어: https://apps.apple.com/us/app/helpus-instant-help-in-town/id1496615309
APK 파일 다운로드설치: https://etain.club/download
소개 영상
사용법
보다 자세한 내용은 홈페이지를 참고해 주세요.
https://etain.club