[2차 프로젝트 먹방 21.03.29 ~ 04.09] Project Retrospective

TK·2021년 4월 10일
5

Project

목록 보기
6/6

What is it about?

  • 먹방 프로젝트는, 직방 사이트에 영감을 받아 시작하게 된 프로젝트이다.
  • 디자인과 기획 부분만 직방 레이아웃을 참고하였으며, 나머지는 프론트 3명 백엔드 2명으로 구성된 우리 먹방 팀에서 전부 다 구현하였다.
  • 지도위에 맛집정보들을 표시해주는 사이트이며, 사용자가 직접 맛집을 등록할 수도 있고 리뷰와 평점, 사진을 남길수도 있게 제작하였다.
  • 키워드 검색기능을 추가하여 주소, 맛집이름, 근처 지하철역 키워드로 검색할 수 있게 하였다.
  • 카카오 API 로그인을 추가하여 사용자의 편리성을 더했고, 일반 회원가입(문자인증포함)을 추가하여 보안을 강화하려고 했다.

How is it different from the first one?

  • 1차 프로젝트때와 달리 프로젝트의 pm 을 맡아, "Agile in scrum" 방식으로 매일 stand-up meeting 을 진행하고 Trello 로 스케줄을 관리하며 프로젝트를 주도적으로 진행했다.

  • postman 으로 만든 API 들을 (요청할 uri, 요청 method, key 값, query params 값... 및 요청 예시) 와 함께 문서화하려고 노력했다.

  • 프론트/백에서 겪는 blocker 를 혼자서만 해결하게 두지 않고 다같이 고민해보는 시간을 가졌다.

  • 프론트/백 기능을 처음부터 떨어져서 각자 만들지 않았다. 같은 기능을 담당하는 프론트/백이 같이 모여서 만들면서, 서로가 데이터를 어떻게 처리할 것인지 로직을 충분히 설명한 이후에 코딩하는 방식을 고수했다.

  • 만들다가 기능에서 이해가 되지 않는 부분이 있으면 바로바로 이야기 하도록 주도하여, 나중에 프론트/백 어느 한곳에서 기능을 다시 갈아엎어야하는 수고를 덜었다.

  • 1차 때 하지 못했던 기능들을 최대한 많이 해보고 이해하려고 노력했다.

  1. geopy, haversine 모듈을 활용한 위,경도 거리 계산
  2. 카카오 API 를 활용한 소셜로그인 구현
  3. 네이버 문자서비스를 통한 문자인증 로직 구현
  4. select_related, prefetch_related 를 통한 쿼리 수 줄이기
  5. django orm method 를 이용한 다양한 filtering 방법들 적용
  6. unit test 작성

Agile in scrum

  • 스크럼 방식으로 agile 하게 진행했다.
  • 말로만 스크럼이 아니라, 실제로도 최대한 빨리 기능을 만든 다음 프론트/백 에서 맞춰보게 했다.
  • 그렇게 함으로써 빠르게 전체적인 틀을 잡았고, 각자 세부적인 부분에 집중할 수 있었다.
  • Trello 를 활용해 작업했으며, board 에 크게 스케줄을 크게 네가지 파트로 나눴다.
  • Backlog -> This Week -> In Progress -> Done
  • 팀/개인 별로 티켓 색깔을 구분하여 할당했으며, 우리 팀이 어느위치에 와있는지 한눈에 알 수 있었다.
  • 필수 구현사항/추가 기능 구현사항 을 나누어 작업의 우선순위를 정했다.
  • 매일 stand-up meeting 을 통해 팀/개인 별 작업 진행속도 및 각자가 처한 blocker 를 파악하는 시간을 가졌다. blocker 가 있으면, 혼자 고민하게 두지 않고 백엔드, 프론트엔드가 모두가 함께 고민하도록 했다. 이렇게 함으로써 한사람이 너무 앞서가거나 뒤쳐지지 않게 조절하려고 노력했다.

Communication

  • 팀 내 communication tool 은 slack 을 사용했고,
  • 코드 reviewer 와의 소통은 slack 및 github 에 올린 PR 의 code review 를 통해 진행했다.

My role in the project

As a Project Manager

  • pm 을 맡아, 프로젝트의 전체적인 work flow 를 관리했다.
  • 프론트/백엔드 가릴 것 없이 문제가 있으면 칠판 및 노트에 설명을 하면서 blocker 를 해결하려고 애썼다.
  • 작업 속도를 보면서 2주 내에 구현하지 못할 기능들은 과감하게 제하고 추가 구현사항으로 빼놓음으로써, 필수적으로 구현해야 할 것들에 먼저 집중하게끔 노력했다.
  • stand-up meeting 및 팀 내 communication 을 주도했다.

As a Backend

  • 모델링
  • Django project 초기 세팅
  • Kakao API 소셜 로그인 & 회원가입, Naver 문자 서비스를 이용한 문자인증
  • 가게 등록
  • 위도, 경도, 축척, 화면 픽셀 & DPI 기준 가게 정보 표시
  • 가게와 근처 지하철 사이의 거리 표시
  • 가게 세부정보 표시
  • 리뷰 등록 & 삭제
  • 가게 검색
  • 가게 찜하기, 찜하기 삭제
  • 기준에 맞는 가게 필터링
  • Django project RDS 에 연결 및 AWS EC2 인스턴스에 docker 를 활용하여 배포

Backend Modeling & APIs

1. Modeling

1차 프로젝트 때는 커머스 사이트라서 주문관리 때문에 모델링이 복잡했었다. 그래서 모델링에 대해 정말 많이 고민했었고 힘든 만큼 많이 배웠었다. 그 덕분에 2차 프로젝트는 수월하게 진행했다.

  • 모델링은 user app, store app 두개로 나눠서 진행했다.

  • 가게(stores) 테이블안에 주소(address) 정보를 넣으려고 하다보니 한 테이블 안에 정보가 너무 많기도 하고, 따로 독립적으로 addresses 로 테이블을 빼서 관리하는 것이 깔끔해 보였다.

  • 그래서 처음엔 그냥 foreign key 로 address 가 stores 를 참조하도록 했다가,

  • Django 의 one-to-one field 에 대해 공부했던 것이 생각나서 바로 적용해보았다.

  • 1차 때는 one-to-one 관계 설정을 안해봤었는데, one-to-one field 를 쓰다보니 왜 사용하는지 몸소 깨닫게되었다. store.address_set 으로 접근을 안해도 된다는 점이 너무 편했다.

  • 나머지 테이블에 대해서는 필요한 부분에 one-to-many, many-to-many 관계를 정했다.

2. Kakao API Login & Sign Up

  • 카카오 소셜로그인 시 처음에 백엔드에서 인가토큰, 엑세스 토큰, 사용자정보를 다 받았었다.

  • 그렇게 되면 백엔드에서 로그인 페이지로 redirect 까지 시켜줘야 했다. 이 부분은 프론트엔드의 부분이기 때문에 최종적으로
    -> 프론트에서 엑세스토큰을 받는 부분까지 처리한 뒤 토큰을 넘겨주면, 백엔드에서 해당 토큰으로 Kakao API 에 사용자정보를 요청하는 로직으로 변경하였다.

  • 사용자정보를 받아 db에 정보가 없으면 회원가입을 시키고 없으면 그냥 or 로그인을 시킨 뒤 jwt 토큰을 발행해서 프론트에게 넘겨주었다.

  • 추가적으로 통합회원 관리를 할 것인가 그렇지 않을 것인가에 대한 고민 끝에, 로그인 방식에 따라 각자 다른 유저로 취급하기로 했다. 그래서 다음과 같이 모델링 수정을 한번 거쳤다.

  • id, created_at, updated_at 을 제외한 모든 field 값을 null 로 변경해주었다.

또 만약에 통합회원 관리를 하려면 어떻게 해야하는가에 대한 궁금증이 있었는데, 그렇게 하려면
회원가입을 먼저 시키고 로그인을 하게 한 다음 소셜 로그인을 연동하는 방식으로 진행해야한다.

그래야만 어떤 유저인지 알 수가 있고,
그 때 소셜 로그인을 연동하면 해당 유저 object 를 불러와서 kakao_id 든 google_id 든 해당 field 에 id 값만 추가하면 되기 때문이다.

3. Store Display depending on Latitude/Longitude & map scale & pixel

  • 프론트에서 위도, 경도, 지도의 축척, 화면픽셀 값을 받으면 백엔드에서 화면의 위도 경도 바운더리를 계산 후, DB 에서 바운더리 내부에 있는 가게정보 표시했다.

로직은 생각보다 간단했다.

  • 지도의 중앙으로 부터 지도의 위,아래/양옆 변에 해당하는 좌표를 구해야했다.

  • 그러려면 사용자의 지도 좌표에서 지도 맨 끝쪽 까지의 거리를 알아야 했다.

  • 축척이 주어져있어서 cm 당 몇 m 인지 알 수 있었다.

  • 모니터 해상도 및 픽셀을 기준으로 실제 사용자의 화면에서 지도가 어느정도 크기인지 구했다.

  • 지도의 길이와 축척으로 지도에 나타난 실제 가로세로 길이를 구했다.

  • 이후에 중앙으로부터 수직으로 지도 오른쪽 끝부분까지 500m 라는 것을 알았으면
    중앙의 위도/경도 + 동쪽으로 500m 를 계산해야했는데, 이 부분을 찾기 위해 스택오버플로우를 뒤져가며 위도/경도 에 거리를 더했을 때 위도 경도를 구하는 공식을 찾으려고 노력했다.

  • 위도 경도 사이의 거리를 구하는 모듈은 많았는데, 위도 경도에 거리를 더했을 때 위도 경도를 구하는 모듈은 찾기 힘들었다.

  • 그래도 결국 찾아냈다. geopy 라는 모듈의 distance method 를 사용하여 해결했다.

  • 동서남북의 boundary 를 구한 뒤, db 에 addresses 테이블을 Q 객체로 필터링 했다.

DPI, INCH, SCALE_LEVEL 상수로 선언

get_boundary_range()

--> 한가지 문제점은 넘겨받는 인자 수가 5개나 된다는 것이다.
그래서 리팩토링 할 때 저 위도, 경도, 축척, 가로픽셀, 세로픽셀의 정보를 담고있는
object 를 만든 뒤 그 object 자체를 함수에 넘겨줄 예정이다.

그렇게 하면 훨씬 더 깔끔해질 것 같다.

store filtering with lat,lng boundary range

이 때 프론트 단에서 DPI 값을 구하는 모듈을 결국 못찾아서 백엔드에서 직접 DPI 값을 역으로 조정하면서 적절한 값을 찾았다. 프론트 테스트 환경에서 200 이 딱 오차없이 맞아서 DPI 를 200 으로 고정했다.

Distance between Store and Metro Station

haversine 모듈을 이용하여 위도/경도 간 거리를 구했다.

4. Store filtering with many categories & pagination

  • 가게 정보를 보내줄 때, 프론트에서 두번 요청을 했다.
  • 첫 번째 요청은 지도에 표시할 가게를 먼저 요청(업종, 가격범위 필터링 포함)
  • 이후, 두 번째 요청은 가게 페이지 목록에 들어갈 가게 요청(리뷰순, 평점순 필터링 포함)

두 개의 API 를 만들기에는, 필터링을 해도 어차피 같은 data set 인데,
여러 API 를 만드는 것이 비효율적이라고 생각했다.

-> 같은 view 에서 가격범위, 업종, 리뷰 개수, 평점평균 에 따라 필터링 후
페이지네이션 까지 작성하고 파라미터만 다르게 받아서 처리하는 방식으로 로직을 구현했다.

  • 필터링 과정에서 exclude(), annotate(), order_by(), field lookup 등 다양한 QuerySet API 를 활용하여 깔끔한 코드를 작성할 수 있었다.

  • 또한 이 과정에서 select_related, prefetch_related 를 통해 DB 데이터를 메모리에 caching 하여 반복문 마다 쓸데없는 쿼리 수를 감소시켰다.

5. Store Search with keywords

  • 가게 이름, 가게 주소, 근처 지하철역, 업종 이름으로 필터링을 했다.

  • Q 객체를 사용하여 | 연산자로 필터링에 걸린 모든 store object 를 담았고, 중복된 object 들을 거르기 위해 distinct() 메소드를 사용했다.

  • select_related, prefetch_related 를 사용하여 for 문마다 쿼리가 db 를 hit 하는 횟수를 감소시켰다.

6. Sign-Up, Text Authentication with Naver API

  • 원래는 네이버에 문자인증 서비스라는 것이 따로 API 로 존재하는 줄 알았는데, 그냥 sms API 만 있어서 우리가 직접 문자인증 로직을 구현하기로 했다.
  1. 휴대폰 번호를 입력받으면 프론트에서 백엔드로 핸드폰 번호 전달
  2. 네자리 난수를 생성하고 네이버 문자인증 API 를 사용하여 해당 핸드폰 번호로 난수 전달
  3. 성공적으로 메시지가 전달 되면, 프론트엔드에 해당 난수를 bcrypt 로 복호화 불가능하게 해싱해서 local storage 에 보관하게 함
  4. 사용자가 문자를 받고 인증번호를 입력한 뒤 인증버튼을 누르면 프론트엔드는 local storage 에 가지고 있던 해싱된 난수 값과 사용자가 입력한 값을 백엔드에게 보낸다.
  5. 백엔드는 해싱된 난수값과 사용자가 입력한 값을 bcrypt.checkpw 메소드로 비교한 뒤 맞으면 프론트에게 jwt 의 payload 에 exp 정보를 담아 토큰의 유효시간을 설정한 뒤 SECRET_KEY 로 SHA-256 알고리즘을 통해 jwt 를 암호화하여 프론트에게 전달한다.
  6. 프론트는 jwt 를 local storage 에 저장한 뒤, 회원가입 시 form 에 입력된 값과 jwt 를 함께 백엔드에게 전달한다.
  7. 백엔드는 jwt 를 검사하고 서버가 발행한 jwt 가 맞으면 회원가입을 시킨다.

--> 이렇게하면 db 를 거치지 않고 문자인증을 할 수 있다.

우리가 외부 도움없이 스스로 이런 로직을 생각해내고 적용했다는 점이 정말 뿌듯했고, 잘 할 수 있다는 자신감을 심어준 기능이였다.

Clean Codes

1. @classmethod

  • 비슷한 정보들을 넘겨주다 보니, 같은 로직이 너무 많은 문제를 직면했다.

-> 빈번하게 사용되는 해당 가게의 리뷰 평점 평균과, 전체 리뷰 개수를 구하는 로직을
classmethod 로 만들어서 필요할 때마다 간편하게 사용했다.

2. class inheritance & method overiding with super()

  • unit test 진행 시, 한 Test 클래스의 setUp() 에서 세팅해뒀던 코드가 다른 Test 클래스들에게도 많이 사용되고 있었다.

-> 불필요한 반복을 줄이기 위해 Base 클래스 를 만든 뒤, 다른 클래스에서 Base 클래스를 상속했다.
-> setUp() 부분만 super() 로 메소드를 오버라이딩해서 사용했다.
-> 이 때 Base 클래스에 __test__ = False 를 안하면 테스트 실행마다
Base 클래스가 계속 테스트되기 때문에 꼭 설정해줘야한다.

wrap-up

1차 프로젝트와 비교했을 때 훨씬 성장한 나를 볼 수 있었다.

내가 구현해야 할 기능에 매몰되서 내 것만 하는 것이 아니라, 내가 좀 느리게 가더라도 팀원들을 도우며 같은 속도로 가려고 했다.

다만 아쉬운점은, 프로젝트 발표였는데 우리는 마지막에 욕심을 내서 추가로 기능을 구현하려다가 발표준비가 다른 팀들에 비해 조금 미흡했던 점이 걸렸고, 팀원들한테 미안했다.
정말 잘 했다고 생각했고 기능 구현도 많이 했는데 발표에서 전부 다 우리가 했던 것들을 자랑하지 못해서 살짝 아쉬웠다.

개발자로서 코딩을 잘하는 것이 물론 중요하지만,
잘 만들고 마지막에 만든 결과물을 어필하는 능력까지 개발자의 몫이고 중요한 덕목이라고 생각한다.

다음부터는 준비를 더 잘 해야겠다!

그리고 다른 팀원들은 속으로 우리 팀을 어떻게 평가할지는 모르겠지만 내가 생각할 때 우리는 목표를 위해서 잘 달려온 것 같다.

서로를 믿고 도와주고 힘들더라도 묵묵하게 각자 맡은 부분을 끝까지 잘 해낸 승옥 님, 성훈 님, 나은 님, 호열 님에게 감사하고

코드리뷰를 통해 많은 인사이트를 주신 지훈 멘토님께도 감사드린다.

좋은 프로젝트에 좋은 팀원들이여서 좋았다.

두 달동안 정말 바쁘게 달려와서 두달이란 시간이 정말 빠르게 흘러갔다는 걸 이제서야 체감한다.
앞으로 한달동안 협업을 하게 될 회사에서는 또 어떤 일들이 있을지 무엇을 또 새롭게 배울지 기대하는 마음으로 회고를 마무리한다.

profile
Backend Developer

4개의 댓글

comment-user-thumbnail
2021년 4월 11일

고생하셨슴다 택향님~

1개의 답글
comment-user-thumbnail
2021년 4월 11일

TK님 wrap-up 부분이 인상적이네요 🤦‍♀️
프론트까지 도와주면서 이끌어가는 PM중의 PM!! 고생 많았어요!
(-묵묵하지 않았던 팀원1-)

1개의 답글