django 2nd Project Review - 원티드

Junyoung Kim·2022년 4월 1일
0

29-2nd-needed-backend

배포 사이트 주소

Introduction

👉 I need YOU for the world’s leading companies!

  1. 다양한 일자리를 카테고리별로 나눠 구직자에게 접근성이 용이하게 합니다.
  2. 잡포스팅 외에도 다양한 이벤트, 직군별 연봉정보 등을 제공합니다.
  3. 각종 테마에 대하여 서로 의견을 주고받을 수 있는 커리어 커뮤니티를 가지고 있습니다.

개발 인원 및 기간

  • 기간 : 21.02.14 ~ 22.02.25
  • Frontend 4명 : 곽승현, 박무선, 윤남주, 장예지
  • Backend 2명 : 김준영, 김성수

Frontend Git Repository

적용 기술 및 구현 기능

  • Frontend : JavaScript, React.js, SASS, React-router-dom, Styled Component
  • Backend : Python, Django, MySQL, AWS(EC2, RDS, S3), Docker
  • 협업 및 일정 관리 : Git, Github, Slack, Trello, Notion, Figma

Backend Features

Part

구현 파트
김준영모델링, 소셜로그인, 채용리스트, 회사/채용 상세정보, 직군별 연봉, 월간 구독 서비스 Needed+
김성수모델링, 직업 카테고리 리스트, 이력서(파일) 업로드 및 삭제/조회(soft-delete 적용) ,검색 기능

User API

  • 회원가입/로그인 - 소셜 로그인(OAuth 2.0), KakaoAPI 모듈화(토큰, 사용자 정보 가져오기)
  • 마이페이지 정보수정 - 개인정보 수정 기능
  • 직군별 연봉 - 사용자가 입력한 연봉/경력(연차)를 토대로 직군/연차별 연봉 데이터 전송

Recruitment API

  • 직군(카테고리), 직무(서브카테고리)
  • 키워드를 통한 채용 리스트 정렬(filtering, sorting) - 직군,직무,지역 채용보상금, 날짜순
  • 채용 상세 페이지(채용 회사, 위치, 채용보상금 등등)
  • 검색 기능

Company API

  • 회사 상세 페이지(모집하는 채용, 태그, 회사정보 등등)
  • 검색 기능

Application API

  • 채용 지원(soft-delete)
  • 회사별 지원 내역

Resume API

  • 이력서 파일 업로드, 딜리트(aws s3 모듈화)
  • 유저가 업로드한 이력서 내역

Neededplus API

  • 월간 아티클 구독 서비스 Needed+ - 회원의 구독 여부를 받아서(GET) 구독시 구독 여부 값과 구독 날짜를 DB에 전송(PATCH)

Reference

API Documentation

db.diagram

후기

모든 것이 처음인 1차 프로젝트 때 보다 생각보다 여유롭게 진행된 프로젝트였던 것 같다.

프로젝트에 필요한 요구 기능들을 분석해보니 원티드라는 사이트가 모델링만 복잡할 뿐 1차 프로젝트때 진행했던 다른 커머스 사이트와 차이가 거의 없어서 백엔드 팀원인 성수님이랑 1주차 때 처음해보는 기능들을 최우선으로 구현하는 데에 모든 역량을 쏟기로 합의했다.

1주차 때 나는 OAuth 2.0 를 이용한 소셜 로그인 파트를 맡았고, 성수님은 AWS S3를 이용한 이력서(파일) 업로드 기능을 구현했다.

2주차직군별 연봉 그래프 API를 중점적으로 개발했고, 채용정보/회사정보/채용지원 등 1차 프로젝트에서 개발했던 것과(커머스 사이트에서 상품 장바구니 추가 및 주문) 동일한 나머지 API를 작성했다,

소셜 로그인

내가 소셜 로그인을 구현하면서 막혔던 블로커는 다음과 같다.
1. 리디렉션

기존 프로젝트에서 로그인 회원가입 인증 과정은 백엔드가 작성한 API를 프론트엔드가 fetch 혹은 axios 함수를 이용해 사용하기만 하면 되기 때문에 작동 여부를 확인하는 통신을 제외하면 서로가 깊이 관여할 일이 없었지만, OAuth 2.0은 위 그림과 같이 여러 단계의 과정을 거쳐서 인증/인가 과정을 거치기 때문에 프론트엔드/백엔드간 역할 분담통신이 더욱 중요해졌다.

처음에는 멘토님이 백엔드가 모든 과정을 할 수 있다고 하셔서 내가 위 그림의 첫번째 단계인 인증 코드 요청부터 마지막 단계인 응답 전달까지의 모든 과정을 담당하려고 했었고, 여러 시행 착오를 거쳐서 1주차 금요일에 사이트에서 발급한 JWT 토큰을 응답에 넣어 전달하는 것까지 성공했다. 그 과정은 다음과 같다.

1. 클라이언트가 카카오 로그인 버튼을 누르면, 카카오에서 인증 코드를 요청하는 페이지로 리다이렉트 해주는 API가 실행이 된다.
2. 리다이렉트가 성공적으로 완료되면, 클라이언트는 카카오 로그인 과정을 거치고, 로그인에 성공하면 카카오 개발자 페이지에서 내가 설정한 콜백 URI로 카카오에서 발급한 인증 코드쿼리스트링에 포함한 채 다시 리다이렉트 된다.
3. 콜백 URI에서는 KakaoCallbackView을 실행한다. 발급받은 카카오 인증 코드request.GET.get 메서드로 변수에 저장한 뒤, 차례대로 토큰 요청 및 전달, 카카오 API 호출, 유효성 확인, 응답 전달 과정을 한번에 실행한다. 여기서 카카오 API 호출은 토큰을 요청하는 API, 로그인/회원가입 과정에 필요한 사용자 정보를 가져오는 API를 호출한다.
4. 카카오에서 유저 정보를 가져오는데 성공했다면, JWT 토큰을 발급하고 성공 메세지를 리턴한다. 만약 DB에 해당하는 카카오 유저 정보가 없다면, 가져온 데이터를 DB에 저장하고 사이트 자체 회원가입 페이지로 연결한다.


하지만 문제가 있었는데, 클라이언트가 로그인 버튼을 누르고 모든 과정에 성공한 뒤 최종적으로 클라이언트가 연결되는 페이지가 KakaoCallbackView의 리턴값인 JsonResponse 페이지인 것이였다. 카카오 로그인에 성공하면 브라우저에서는 흰 화면에 세션 스토리지로 저장되어야 할 JWT 토큰값과 성공 메시지만 덩그러니 존재했다.

이것 또한 리턴으로 JsonResponse가 아니라 프론트엔드에서 설정한 메인페이지로 리다이렉트를 해주면 해결되긴 하지만 점점 백엔드 API와는 멀어지는 것 같아서 멘토님, 팀원과 회의를 거쳐서 포기했다.
최종적으로는 인증 코드 요청인증 코드로 토큰 요청 부분은 프론트엔드 담당인 예지님이 맡기로 하였고, 백엔드에서 전부 처리한 해당 부분은 수정하게 되었다.

나는 소셜 로그인을 구현하는 것에 1주차 수,목,금 3일을 온전히 쏟아부었는데, 결과적으로 내가 짠 코드(리디렉션, 카카오 토큰 요청 API)가 사용되지는 않았지만 그래도 모든 것을 다 내 손에서 해결하려는 것에서 의미가 있었다고 생각한다. 문제를 해결하는 과정에서 OAuth에 대해 잘 알게 된것도 좋았다.


2. API 클래스화
1차 프로젝트에서 외부 함수나 클래스 호출은 utils.py에 인가 과정에 필요한 authorization 데코레이터를 작성해 모듈화해서 사용한 것 하나밖에 없었지만, 2차 프로젝트 소셜 로그인에서는 외부 API를 호출하는 것 까지 추가되었다.

카카오 소셜 로그인에서는 토큰 요청, 카카오 유저 정보 가져오기 2개의 API를 사용하였는데, 이것을 별개의 함수로 사용하기보다 클래스로 만들어 한꺼번에 관리해 보라는 멘토님의 과제가 있어서 클래스로 만들어 보게 되었다.

클래스를 만들어보는 과제에서 미흡했던 객체,생성자,초기화,인스턴스 등 클래스에 대한 개념을 다시 한번 정립하게 되었다. 특히 기억에 남았던 점은 클래스데이터액션의 결합이며, 데이터에 따라서 액션이 달라지므로 데이터를 고정시키면 안된다는 멘토님의 조언이었다.
처음에 토큰 요청 API를 작성할 때 API에서 필요한 클라이언트 ID, 리디렉션 URI, Grant_type등의 데이터를 클래스 안에 넣었는데, 데이터가 달라지면 클래스가 무용지물이 되므로, 코드의 유지 보수에 좋지 않았다. 따라서 API를 요청하는 사용자가 직접 입력하게 해 클래스의 원래 용도에 맞게 하드코딩을 피했다. 나의 선입견과 고정 관념을 타파해주는 좋은 계기가 되었다.


3. CORS
프론트엔드와 역할 분담을 마치고 코드를 수정하고 최종적으로 통신하는 과정에서 맞이한 블로커다.
CORS는 교차 출처 리소스 공유(Cross Origin Resource Sharing)의 줄임말로, 쉽게 말하면 실행 중인 API에서 다른 출처(도메인)의 자원에 접근할 수 있는 권한을 부여하도록 알려주는 체제다.

프론트엔드가 Javascript SDK로 구현한 카카오 로그인 과정은 로그인 성공 후 토큰을 백엔드 API에 넘기고, 백엔드 API는 다시 카카오 API에 토큰을 넘겨 사용자 정보를 가져오는 작업을 수행한다.
이 때 프론트엔드 클라이언트 서버는 http://localhost:3000이고, 카카오 API에 요청을 보내는 출처는 백엔드에서 설정한 http://localhost:8000/users/signin/kakao/callback라서 서로 출처가 달라 SOP 정책을 위반하게 된다.

프로젝트 초기 세팅에서 CORS 관련 코드를 삽입해 문제가 없을 거라 생각했는데 브라우저 콘솔에서 CORS 에러가 나서 해결하는데 꽤 애를 먹었다. 원인은 초기세팅에서 설정한 CORS를 허용하는 헤더(CORS_ALLOW_HEADERS)에 내가 프론트엔드에서 받도록 설정한 토큰 헤더 이름access-token이 없는 것이 문제였다.
access-token 코드를 추가하자 CORS 에러가 나오지 않아 통신이 성공적으로 이루어졌고 최종적으로 깃허브에 merge까지 마쳤다. 프로젝트 1주차 내내 나를 괴롭히던 3가지 블로커를 모두 해결하고 소셜 로그인 구현에 성공하니 감격해 살짝 눈물이 나올 뻔 했다ㅋㅋ.



직군별 연봉

직군별 연봉 그래프 API는 1주차때의 난적인 소셜 로그인을 마무리하고 여유롭게 진행한 2주차 일정에서 그나마 블로커를 느꼈던 것이다.

DB에 있는 회원들을 분류해 0년차(신입)부터 10년차 이상까지 연차별로 평균 연봉을 계산에 프론트엔드에 넘겨 주는 API인데, 원래는 Case,When을 활용한 조건적 Annotate를 써서 개발하려고 했다.
코드를 짜면서 문제가 생겼는데, Annotate를 사용하면 연차별로 평균 연봉 컬럼을 새로 만들어서 전달하는 것이므로 salary_career_0부터 salary_career_10까지 총 11개의 컬럼을 만들어야 하는데 새로운 컬럼을 만드는 것이므로 Dictionary comprehension을 사용해서 중복된 코드를 줄일 수 없다는 것이 문제였다.

고민 끝에 멘토님을 찾아가서 물어봤는데 생각보다 간단하게 해답이 나왔다. Annotate를 활용하는게 아닌 리스트인덱스를 사용해 연차별 평균 연봉을 저장하라는 조언이었고, 알려 주시면서 자료구조를 공부해야 될 필요성에 대한 좋은 예라는 것도 알려주셨다. 리스트를 활용하라는 조언을 들으니 물꼬가 트였고 금방 코드를 짤 수 있었다. 1. 직군별로 회원을 필터링
2. 필터링한 직군에서 연차별로 계산한 평균 연봉을 0번 인덱스는 0년차의 평균 연봉으로 리스트에 저장
3. 딕셔너리 컴프리헨션enumerate 메서드를 이용해 2번에서 만든 리스트를 프론트엔드에 전달할 Key/Value 형식으로 변환


여태까지 API를 개발하면서 데이터를 전달할 때 어떤 자료구조가 효율적인지는 생각조차 못하고 있었는데, 직군별 연봉 API를 개발하면서 약간이나마 느끼게 되어 다시 한번 내가 성장하는 계기가 되었다고 생각한다. 👍

마치며..

이번 2차 프로젝트는 1차 프로젝트때의 경험을 살려 일정 관리를 잘 지킨게 제일 좋았다고 생각한다. 계획한대로 1주차때는 처음 개발해보는 기능 2가지(소셜 로그인, 파일 업로드)를 나누어 맡아 끝내고, 2주차 때는 1차때 경험이 있던 나머지 API를 개발한다는 계획이었는데, 일정 관리에 성공하니 2차 프로젝트에서 처음 배운 개념인 Unit test, Docker 같은 것들을 여유롭게 학습하고 프로젝트에 적용할 수 있었다.
새로운 것을 배우는 것도 중요하지만 기존에 배웠던 것을 유지하는 것도 중요한데, 이번 프로젝트를 하면서 그 2가지가 조화롭게 잘 이루어진 것 같아 좋았다.
그리고 1차때 아쉬운 점으로 남았던 프론트엔드와의 소통도 잘 이루어져 개발한 API를 서로 맞춰보는 통신에도 문제가 없었고, 이로 인해 팀워크와 팀 분위기가 매우 좋았던 것도 기억에 남는다. 프로젝트에서 느꼈던 좋았던 점을 그대로 끌고가 더 열심히 개발에 정진해야겠다. 😄

0개의 댓글