Wecode | 2차 Project 마무리 회고

2차 프로젝트 회고

회고(Retrospective)란 사전적 의미로 돌아다보는 것, 지나간 일을 돌이켜 생각하는 것이다.
그렇기에 프로젝트에서 느낀점과 생각들이 휘발되기 전에 빠른시간에 기록하는 것이 중요할 것이다.

위코드에서 할 수 있는 마지막 2차 프로젝트까지 모두 끝이 났다. 벌써??? 마지막 프로젝트를 끝내고 아쉽기도하고 뿌듯하기도 하고 뭔가 만감이 교차했다. 벌써 두번의 프로젝트를 끝내고 기업협업을 가야하다니 잘할 수 있을까? 첫 프로젝트를 시작했던 느낌처럼 처음에는 막막하겠지만 결국 또 적응하고 잘해낼 수 있지 않을까? 라는 자신감을 가져본다.

어쨌든 본론으로 돌아가 2차 프로젝트는 에어비앤비를 모티브로 하여 프로젝트를 진행하였다.

Groundbnb 시연 영상 링크

🕺🏿✈️Team Groundbnb

사용 기술

  • Front-End : React.js, StyledComponent, HTML, JavaScript
  • Back-end : Python, Django web framework, MySQL, Bcrypt, JWT, Redis
  • common : AWS(EC2, RDS, S3, Docker), RESTful API

협업 tool

프로젝트를 하며 느낀점

공통

  • 잘한점
    1차 프로젝트의 단점을 보완하여 팀원과 소통하며 합의된 내용을 문서화하려고 노력하였다. 매일 스탠드업미팅의 내용을 회의록으로 작성하여 트렐로에 기재하여 서로 혼동이 없도록 하였고, API 문서나 팀원끼리 공유해야할 내용들을 트렐로 전부 기재하여 똑같은 내용을 다시 묻는 일이 없도록 하였던 점 그렇기 때문에 이번에는 트렐로만 보고도 정리가 잘 되었다.

  • 아쉬운점
    너무 많은 기능을 도전하려 했던 것이었다. 결국 다 해내긴 했지만 마감기일까지 아주 촉박하게 기능구현이 이루어졌고 최종발표일까지도 계속해서 기능구현한 부분에 대해 점검하였다. 아슬아슬하게 발표는 마쳤지만 개발 기획을 할 때 일정은 최대한 보수적으로 잡고 진행해야겠다는 생각이 들었다.

백엔드

  • 잘한점
    1차 프로젝트에서는 굉장히 의사소통은 잘되었지만 리더의 부재가 아쉬웠다고 생각한다. 한명의 리더가 있으면 좋겠다는 생각이 있었는데 2차 때는 리드역할을 해주시는 분이 있었다. 분명한 장단점이 있었고 큰 규모의 프로젝트에는 꼭 리드역할을 해주는 분이 필요하다는 생각이 들었다. 의사소통이 잘 되더라도 최종결정을 맡는 사람이 없다는 좋은 아이디어도 사장될 수 있었고 그 부분이 아쉬웠다. 그렇기에 2차 프로젝트때 다양한 의견을 수렴하여 결단력을 가지고 실행해주니 일이 조금 더 속도를 낼 수 있었다.

  • 아쉬운점
    역시나 이번에도 아쉬운 부분은 모델링 부분이었다. 에어비앤비가 기능이 많아 모델링을 아무리 신중하게 한다 하더라도 구현하는 과정에서 몇번을 고치고 수정했다. 모델링을 애초에 100프로 완벽하게 할 수 없지만 최대한 설계 과정에서 유연하게 수정이 가능하도록 몇가지 대안까지 생각해가며 설계해야겠다는 생각을 했다. 멀고도 험한 모델링의 길.. 프로젝트 기간 중에 해야할 일이 많아 되게 절대적인 시간이 되게 짧았어서 이번 프로젝트에는 소셜로그인 외에 특별한 기능을 더 하지 못한 점이 되게 아쉬웠다.

개인

  • 잘한점
    이번에 소셜로그인 부분을 담당했는데 사실상 백엔드 부분에서는 클라이언트 서버에서 소셜로그인을 제공하는 서버쪽으로 엑세스토큰을 받아오는 작업을 하지않고 받아와준 엑세스토큰을 넘겨받아 기능구현을 했으면 됐는데 나는 앞에서 소셜로그인 서비스를 제공하는 서버로부터 인증 받는 과정까지 개념을 알고 싶어 전부 직접 해보았다. 하나의 기능을 위해 철저하게 처음부터 개념까지 이해하려고 노력했다. 그렇게 하니깐 이 부분은 자신이 생겨 남한테도 자신감있게 설명할 수 있게 되었고 그 과정이 뿌듯했다.

  • 아쉬운점
    위의 잘한점이 아쉬운점이 되었다. 무슨 말이냐면 앞의 모든 개념을 공부하면서 넘어가다보니 생각보다 시간이 오래 걸렸다. 그래서 한 기능에 너무 많은 시간을 투자하는 바람에 다른 기능에 시간을 못써서 아쉬웠다. 마음은 굴뚝 같아 다른 기능들을 전부 해보고 싶었지만 마감기한이 다가와 다하지 못했다. 하지만 프로젝트가 끝나도 나중에 리팩토링하며 다른 기능을 추가해보면 되니깐 너무 속상해하지 않기로 했다.

프로젝트를 하며 얻은 것들

이번 프로젝트에는 새로 배우는 기술을 많이 적용해보는 시간이었다. Django-Redis, AWS, Docker 등등 새로운 기술을들을 적용해보며 느낀점은 처음에는 와.. 이게 무슨 기술이지 개념만 들으면 너무 어려워 선뜻 하기도 두려웠는데 뭐든지 차근차근 하다보면 할만하다는 것이었고 어떤 기술을 만나도 이번 경험을 통해 배운 문제해결능력을 통해 해결할 수 있을 것 같은 자신감이 들었다. 물론 학습 단계에서는 적용이 매끄럽게 될 확률이 높고 현업에서는 훨씬 더 힘들 것을 알지만 이런 적극적으로 문제를 해결하는 태도를 통해 분명 좋은 결과를 낼 수 있을 것이라고 생각한다.

사용한 기술 스택에 대해 느낀점

Redis

내가 구현한 기능

  • 소셜 로그인 : kakao, Google, 로그인 로그아웃

  • 데코레이터(토큰)

  • 소셜 로그인 구현 코드

import json, jwt, requests

from django.views     import View
from django.http      import JsonResponse

from groundbnb.settings.local import LOCAL_SECRET_KEY, ALGORITHM
from users.models             import User, SocialFlatform

class KakaoAPI:
    def __init__(self, token):
        self.token = token
    
    def get_kakao_user(self):
        kakao_userinfo_url = "https://kapi.kakao.com/v2/user/me"
        headers            = {"Authorization" : f"Bearer {self.token}"}
        
        response = requests.get(kakao_userinfo_url, headers = headers, timeout=1000) 
        
        if response.status_code != 200:
            return JsonResponse({'MESSAGE' : 'RESPONSE_ERROR'}, status=400)

        return response.json()

    def request_kakao_logout(self):
        kakao_logout_url = 'https://kapi.kakao.com/v1/user/logout'
        headers          = {"Authorization" : f"Bearer {self.token}"}

        response = requests.get(kakao_logout_url, headers = headers, timeout=1000)

        if response.status_code != 200:
            return JsonResponse({'MESSAGE' : 'RESPONSE_ERROR'}, status=400)
        
        return response.json()

class GoogleAPI:
    def __init__(self, token):
        self.token = token

    def get_google_user(self):
        google_userinfo_url = 'https://www.googleapis.com/oauth2/v2/userinfo?access_token='
        response            = requests.get(f'{google_userinfo_url}{self.token}')

        if response.status_code != 200:
            return JsonResponse({'MESSAGE' : 'RESPONSE_ERROR'}, status=400)
            
        return response.json()

class KakaoLoginView(View):
    def get(self, request):
        try:
            access_token = request.headers.get('Authorization')

            if not access_token:
                return JsonResponse({'MESSAGE': 'TOKEN REQUIRED'}, status=400)

            kakao_api  = KakaoAPI(access_token)            
            kakao_user = kakao_api.get_kakao_user()
            
            user, created = User.objects.get_or_create(socialflatform__provider_id=kakao_user["id"])

            if created:
                user.name  = kakao_user["kakao_account"]["profile"]["nickname"]
                user.email = kakao_user["kakao_account"]["email"]
                user.save()
                
                SocialFlatform.objects.create(
                    provider_name = 'kakao',
                    provider_id   = kakao_user["id"], 
                    profile_image = kakao_user["kakao_account"]["profile"]["profile_image_url"],
                    nick_name     = kakao_user["kakao_account"]["profile"]["nickname"],
                    user          = user
                )
            
            access_token = jwt.encode({'id' : user.id, 'is_host' : user.host}, LOCAL_SECRET_KEY, algorithm=ALGORITHM)

            return JsonResponse({'access_token': access_token}, status=200)

        except KeyError:
            return JsonResponse({'MESSAGE': 'KEY_ERROR'}, status=400)     

class KakaoLogoutView(View):
    def post(self, request):
        access_token = request.headers.get('Authorization')
        kakao_user   = KakaoAPI(access_token)
        user_logout  = kakao_user.request_kakao_logout()
        
        return JsonResponse({'access_logout' : user_logout}, status=200)

class GoogleLoginView(View):
    def get(self, request):
        try:
            access_token = request.headers.get('Authorization')

            if not access_token:
                return JsonResponse({'MESSAGE': 'TOKEN REQUIRED'}, status=400)
            
            google_login_api = GoogleAPI(access_token)
            google_user_info = google_login_api.get_google_user()

            user, created = User.objects.get_or_create(socialflatform__provider_id=google_user_info["id"])

            if created:
                user.name  = ''
                user.email = google_user_info['email']
                user.save()

                SocialFlatform.objects.create(
                    provider_name = 'google',
                    provider_id   = google_user_info['id'],
                    profile_image = google_user_info['picture'],
                    nick_name     = '',
                    user          = user
                )

            access_token = jwt.encode({'id' : user.id, 'is_host' : user.host}, LOCAL_SECRET_KEY, algorithm=ALGORITHM)

            return JsonResponse({'access_token': access_token}, status=200)

        except KeyError:
            return JsonResponse({'MESSAGE': 'KEY_ERROR'}, status=400)

처음 구현한 코드가 삭제되어 올릴 수는 없지만 리팩토링한 후 코드다. 처음에는 소셜로그인 API 정보를 받아오는 로직을 로그인 클래스 안에 직접 구현하였지만, 멘토님의 피드백 후 API만 받아오는 로직 자체를 클래스 따로 빼 구현하였다. 이렇게 되면 코드 가독성이 올라가고 해당 API의 정보를 받아오는 로직을 알지 못해도 모듈처럼 가져다 쓰기만 하면 사용할 수 있다.

  • 소셜 로그인 기능을 통해 공부한 것
## 카카오서버에서 인가 코드 발행 로직
class KaKaoSignInView(View):
    def get(self, request):
        REST_API_KEY = "MY_REST_API_KEY"
        ## 카카오 애플리케이션에서 설졍한 Redirect_uri
        REDIRECT_URI = "http://localhost:8000/users/signin/kakao/callback"

        API_HOST = f'https://kauth.kakao.com/oauth/authorize?response_type=code&client_id={REST_API_KEY}&redirect_uri={REDIRECT_URI}'

        return redirect(API_HOST)

## 카카오서버에서 받은 인가 코드로 토큰/리프레쉬 토큰 발행 로직
class KakaoSignInCallbackView(View):
    def get(self, request):
        auth_code       = request.GET.get('code')
        kakao_token_api = 'https://kauth.kakao.com/oauth/token' # 토큰 발행받는 URL
        
        params = {
            'grant_type'      : 'authorization_code',
            'client_id'       : 'my_client_id',
            'redirection_uri' : 'http://localhost:8000/users/signin/kakao/callback',
            'code'            : auth_code,
        }

        token_response = requests.post(kakao_token_api, params=params)
        
        return JsonResponse({"token" : token_response.json()})

## 카카오 서버에서 유저 정보 받아오는 로직
class KakaoSignInCallbackView(View):
    def get(self, request):
        auth_code       = request.GET.get('code')
        kakao_token_api = 'https://kauth.kakao.com/oauth/token' 
        
        params = {
            'grant_type'      : 'authorization_code',
            'client_id'       : 'my_client_id',
            'redirection_uri' : 'http://localhost:8000/users/signin/kakao/callback',
            'code'            : auth_code,
        }

        token_response     = requests.post(kakao_token_api, params=params)
        access_token       = token_response.json().get('access_token')
        user_info_response = requests.get('https://kapi.kakao.com/v2/user/me', headers={"Authorization": f'Bearer ${access_token}'})
        
        return JsonResponse({"user_info" : user_info_response.json()})

소셜로그인 기능을 통해 위 과정은 프론트엔드 쪽에서 구현하고 카카오서버로부터 엑세스토큰을 발행 받으면 그 토큰을 백엔드 쪽으로 전달해주면 백엔드는 그 토큰으로 로직을 구현하면 됐지만 소셜 로그인 전체 메커니즘을 알고 싶어 클라이언트 서버가 카카오서버로 부터 토큰을 받아오는 로직까지 직접 구현해보았다. 위의 코드를 구현하며 알게된 내용은 소셜로그인 포스팅을 더욱 자세하게 다루기로 하고 회고정도로 넘어가겠다. 정말 다 구현하고 나면 쉽게 보이지만 이거 한다 일주일정도 걸렸던 것 같다. 처음이라 몰라서 헤맸던 것이 너무 많았다.ㅜㅜ

프로젝트 소감

두번째 프로젝트도 정말 정신없이 지나갔다. 팀 프로젝트는 언제나 재밌는 것 같다. 함께 무엇을 만들어 낸다는 재미 그 와중에 내가 열심히 만들어낸 것이 적용되어 보탬이 되었다는 보람. 앞으로도 내 실력이 발전해 팀에 도움이 되고 더 멋지고 필요한 사이트를 만들고 싶다.

좋은 웹페이지 즐겨찾기