위코드 기업 협업에 참여하며 배운 내용을 정리 & 회고 한 내용입니다. (너무 깁니다. 보지 마세요)
하나의 서비스를 선정해 NoSQL 데이터 모델링을 해보고 현실적인 시간을 고려해 도메인 모델을 재 설정하고 직접 만들어 보는 미션을 받았다. 데이터 모델링에 2-3일, 실제 코드 작성에 2-3일 정도의 시간이 주어졌다. 직감적으로 완성은 어렵다는 생각이 들었지만 일단 모델링 방법부터 찾아보면서 차근차근 진행을 해보았다. 아래는 그 모델링 과정과 멘토님의 코멘트등이 뒤 섞여 있다. 결과물(코드)을 계속 개선해야 하지만 이런 과정 자체를 잘 기억하고 싶어 긴 글을 남긴다.
내가 선정한 사이트는 '뭐라고 할까' 라는 서비스로, 디자이너와 개발자 2명이 사이트 프로젝트로 만든 사이트다. 뭐라고 할까라는 이름처럼, 각종 상황에서 뭐라고 쓰면 좋을지 고민되는 각종 문장들을 템플릿처럼 등록하고 복사해서 사용할 수 있는 곳으로, 위코드 팀 프로젝트 사이트로 추천했다 리젝된 곳이다. (여러명이 헙업하기에 적합하지 않아서) 아무튼 결과적으로 나 혼자 이 사이트를 만들 게 되었다니, 뭔가 돌고 돌아 나에게 왔다는 생각이 들었다.
NoSQL 의 데이터 모델링은 구조가 아닌 쿼리부터 짠다는 것을 이전 학습으로 알았고, 조금 더 찾아보니 그 앞단에 서비스 도메인 모델을 파악하고 다룰 데이터 타입을 이해해야 한다는 것을 알았다. 그리고 80은 계획서 작성, 20은 코드 작성의 비율을 적용하기 위해 쿼리문을 작성하며 해당 쿼리에서 사용할 각종 코드를 대략 정리해보는 과정인 줄 알았으나 역시나 놓치는 게 많았다.
1차 접근 때는 쿼리문을 짠다는 것에 대한 감이 없어서, 일단 짠 것을 바탕으로 피드백을 받고 이후에 코드를 붙여볼 생각이었다. 대략 아래처럼 쿼리를 통해 보여줄 기능을 서비스 기준으로 선정하고 이대로 살을 붙여 2차 모델링을 시작했다.
1차를 바탕으로 각 쿼리에 맞는 파이어베이스 코드를 찾아서 넣어봤다. 이런 쿼리에서는 이런 코드를 쓴다고 미리 정해두면, 이후 코드 작성 효율이 올라간다. 다만 쿼리문을 짜는 것과 사용하는 것 모두 경험해보지 않아서 이게 맞는지 잘 몰라 억지로 끼워 맞춘 느낌이 많았다.(아래 코드들은 참고하면 안됨) 그리고 무엇보다 이런 쿼리를 바탕으로 어떤 데이터 구조를 만들면 좋을지까지 나아가야 했는데 그러지 못했다. 어떻게 하겠다는 있지만 왜 그렇게 해야 하는지, 근거나 논리가 더 채워져야 한다는 피드백을 받았다.
// 실시간 업데이트 수신, 컬렉션의 여러 문서 리슨
db.collection("cities").where("state", "==", "CA")
.onSnapshot((querySnapshot) => {
var cities = [];
querySnapshot.forEach((doc) => {
cities.push(doc.data().name);
});
console.log("Current cities in CA: ", cities.join(", "));
});
// 단일 문서 만들기
db.collection("cities").doc("LA").set({
name: "Los Angeles",
state: "CA",
country: "USA"
})
// 키워드 설정 페이지와 템플릿 작성 페이지가 나뉘어져 있다면, 각 페이지에서 따로 set을 실행한다?
// 실시간 업데이트 가져오기
db.collection("cities").doc("SF")
.onSnapshot((doc) => {
console.log("Current data: ", doc.data());
});
// 아니면 리렌더링될 때 다시 데이터를 불러온다?
// 상위 키워드 다섯개 노출
citiesRef.where("population").orderBy("population").limit(2);
// 키워드 클릭 시 해당 키워드 템플릿 리스트업
db.collection("cities").where("capital", "==", true)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
템플릿 카드에는 해당 키워드와 템플릿 텍스트, 태그 내용을 보여주고 새로 올라온 글이나 내가 쓴 글에는 마크가 찍힌다.
키워드 검색을 통해 키워드를 탐색하고 클릭시 해당 키워드만 리스트업 한다.
// 키워드 검색 리스트 가져와서 보여주기
var docRef = db.collection("cities").doc("SF");
docRef.get().then((doc) => {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
// 해당 키워드 리스트 업
db.collection("cities").where("capital", "==", true)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
- 템플릿 갯수를 보여주고 싶다면 갯수를 세기위해 전체 데이터를 불러오지 말고 템플릿 갯수만 따로 저장해서 관리하는 게 효율적일 것
- 키워드 기준으로 서브 컬랙션을 나눠 포스트를 저장하면 필요한 것만 선택해서 가져올 수 있다. 혹은 해당 포스트에 키워드를 담아서 where 절을 사용해 가져올 수 도 있다. 어떤 것이 더 좋은 선택일지 고민해보는 거싱 좋다.
- 어떤 데이터 구조를 만들지에 대한 생각이 더 필요하다. 이 쿼리는 이런 걸로 짜야한다에 서 그치지 말고 그 쿼리로 짠다면 어떤 구조가 필요할지까지 가야 한다.
- 이런 생각들을 충분히 하고 그것을 함축해서 작성하는 것이 개발 계획서다. 그 정리에 일정이나 플랜이 들어가야 한다.
- 이런 생각이나 근거 없이 작성한 계획서는 대부분 추축이다. 생각 못한 부분이 많을 수 밖에 없다.
- 기획서를 쓴 논리가 중요하다. 어떤 논리로 이런 기획서가 나왔는지 설명할 수 있어야 한다. 기준이 틀릴 수는 있지만 논리 구성 흐름이 얕으면 안된다. 틀리더라도 깊게 고민한 결과를 얘기하는 게 중요하다.
3차 데이터 모델링을 하면서는 현실적인 작업 시간을 고려한 도메인 모델 재설정, 실제 개발 계획서(모델링 과정을 압축해서 정리하고 시간을 고려한 계획 세우기)까지 작성해보았다. 앞선 피드백을 바탕으로 데이터 구조도 짜 보았으나, 각 구조의 관계를 정확히 설명하지 못해 해맸다. 어떤 분명한 이유가 있기 보다는 파이어베이스의 컬렉션 - 문서 - 컬렉션 순의 구성이라 그냥 그렇게 만든, 계층을 만들어버린 부분이 있었다.
템플릿 키워드, 내용 데이터 문서에 업로드하기
ㄴ 이 후 정렬을 고려해 작성 시간과 복사 카운트 값도 같이 세팅
템플릿 등록하면서 숫자 카운트해서 별도 테이블로 저장
ㄴ전체 템플릿 데이터를 불러와 갯수를 세지 않고 해당 숫자만 불러와서 사용하기 위함 (불필요한 정보를 다 받아 오느라 비효율적 => 비용증가)
ㄴ 등록된 템플릿 수는 어느정도 템플릿이 쌓이면 노출하는 게 효과적(이 만큼 많이 쌓였다는 것을 보여주는 용도) 그래서 초반에는 노출하지 않지만, 이후에 노출할 예정이므로(1000개 정도 되면..) 별도 테이블을 만들어서 관리,
템플릿 계층 구조는 이렇게, temCon(c) - temDoc(d) - templates(c) - template1(d)
ㄴ temCon 컬렉션 안에 temDoc 문서가 있고 그 아래 하위 컬렉션으로 tempates 를 만들고 그 안에 template1, 2, 3.... 순으로 문서를 생성하는 계층구조. 데이터에 쉽게 엑세스 할 수 있게 계층구조로 관리
카운트 테이블 계층 구조는.. temCon(c) - temCount(d)
ㄴ 템플릿처럼 문서가 계속 늘어나는 것이 아니므로 두번째 문서 층에 생성해서 관리
데이터 계층 구조
쿼리문
// 템플릿 내용 업로드
db.collection("temCon").doc("temDoc").collection("templates").add({
keyword: "아침인사",
text: "좋은 아침입니다",
createdTime: time.. // 등록된 시간 순서대로 정렬?
copyCount: 0, // 이후 복사 카운트할 값도 세팅
})
// 템플릿 카운트 업데이트
db.collection("temCon").doc("temCount").update({
templateNum : (cur) => {cur + 1},
})
// 복사 카운트 업데이트
db.collection("temCon").doc("temDoc").collection("templates").doc("template").update({
copyCount: (cur) => {cur + 1},
})
// 업데이트 된 숫자 리렌더링
db.collection("temCon").doc("temDoc").collection("templates").doc("template").get({
copyCount: 숫자,
})
// 생성된 순서대로 나열 (키워드 선택전, 인기순 솔팅 전)
db.collection("temCon").doc("temDoc").collection("templates").
orderBy("createTime", "desc").limit(15)
// 키워드 선택하면, where 추가?
db.collection("temCon").doc("temDoc").collection("templates")
.where("keyword", "==", "아침인사")
.orderBy("createTime", "desc").limit(15)
// 인기순 솔팅하면, 복사 카운트 순서대로 나열
db.collection("temCon").doc("temDoc").collection("templates")
.where("keyword", "==", "아침인사")
.orderBy("copyCount", "desc").limit(15)
위 내용을 바탕으로 아래처럼 개발 계획서를 만들어 보았다.
// 템플릿 내용 업로드
db.collection("temCon").doc("temDoc").collection("templates").add({
keyword: "아침인사",
text: "좋은 아침입니다",
createdTime: time.. // 등록된 시간 순서대로 정렬?
copyCount: 0, // 이후 복사 카운트할 값도 세팅
})
// 템플릿 카운트 업데이트
db.collection("temCon").doc("temCount").update({
templateNum : (cur) => {cur + 1},
})
// 복사 카운트 업데이트
db.collection("temCon").doc("temDoc").collection("templates").doc("template").update({
copyCount: (cur) => {cur + 1},
})
// 업데이트 된 숫자 리렌더링
db.collection("temCon").doc("temDoc").collection("templates").doc("template").get({
copyCount: 숫자,
})
// 생성된 순서대로 나열 (키워드 선택전, 인기순 솔팅 전)
db.collection("temCon").doc("temDoc").collection("templates").
orderBy("createTime", "desc").limit(15)
// 키워드 선택하면, where 추가?
db.collection("temCon").doc("temDoc").collection("templates")
.where("keyword", "==", "아침인사")
.orderBy("createTime", "desc").limit(15)
// 인기순 솔팅하면, 복사 카운트 순서대로 나열
db.collection("temCon").doc("temDoc").collection("templates")
.where("keyword", "==", "아침인사")
.orderBy("copyCount", "desc").limit(15)
(희망 사항)
- temCon, temDoc 같은 약어 사용은 지양해야 한다. 서로 약속한 것이 아니라면 누가 보더라도 알 수 있게 길게 작성하는 게 좋다. 요즘 에디터들이 좋아져서 그런 긴 변수명도 자동 완성이 가능하다.
- 도큐먼트에 하위 컬렉션을 둔다고 하며 그 이유가 있어야 한다. 폴더 안에 파일이 있고 또 폴더가 있다면 그 이유가 무엇인지 그런 경로를 왜 선택했는지. 굳이 계층 구조를 만들었다는 그 이유가 분명해야 한다.
- 어떤 것을 분류한다는 것은 비슷한 류를 묶는 것인데 temDoc 과 temCount 를 같은 계층으로 묶는 것이 맞을까. 그럼 temCon 은 너무 브로드한 개념이 아닐까. 싶다. temCount 속성을 루트수준으로 뺀다면 어떨까.
- 숫자 카운트 하나 때문에 계층이 하나 만들어지는 게 오버 스펙이긴 하다. 다른 방법이 있다면 등록되는 템플릿 데이터 안에 넘버링을 하고 마지막 등록한 템플릿을 넘버를 가져올 수 있다. 이 값은 삭제를 고려하지 않는 부정확한 숫자 일 수 있는데 서비스 단에서 그렇게 사용할 수 있다는 결정이 필요하다.
- fieldvalue, increment 를 사용하면 id 값을 1씩 증가시킬 수 있으니 참고
- 콜렉션에서 add 하면 그 문서의 id 가 그 문서의 이름이 되는데 그 값을 프론트에서 실제 데이터와 id 값으로 추가해서 사용한다.
- 15개 보여주고 또 15개 보여주고 싶다면 페이지네이션 개념을 알아야 한다.
- 계획서 시간에 +@는 잘못된 것. 시간 산정은 +@까지 고려한 맥시멈 시간을 정해야 한다. 이 시간 안에서는 내가 하려고 했던 것들이 다 구현되고 생각처럼 돌아가야 한다.
이후 작업은 아래처럼 변경해 진행했고, 남은 작업은 피드백을 바탕으로 각자 해보게 되었다. 코드에 대한 구체적인 피드백도 있었느데 그 부분은 따로 올릴 예정이다. 위코드에서도 이론을 배우긴 하지만, 그보다는 how 에 초점을 맞추어서 코드를 치는 행위에 집중하는 느낌이었다. 여기서는 그런 작업에 앞서 what 을 충분히 고민하고 계획을 설계하는 연습을 해보고 있다. 기업협업이 끝나고 혼자 공부를 하게 된다면 이런 과정을 경험한 게 매우 큰 도움이 될 것 같다.