[1차 프로젝트] - 회고

Kiyong Lee·2021년 10월 17일
1

1차프로젝트

목록 보기
4/4

1차 프로젝트 회고록

  1. 1차 프로젝트 시작~모델링
  2. 1차 프로젝트 1주차 돌아보기
  3. 1차 프로젝트 중간 발표

에 이어서 쓰는 종료 후 회고록입니다.

어제 쓰려고 했는데, 백신 2차 접종을 맞고 오니 팔이 너무 아파서 푹 쉬고 오늘 쓰게 되었습니다.

단순히 내가 스파오 클론코딩에서 어떤 걸 만들었다의 글보다는, 2주동안 느낀 점에 대한 글을 써보려고 합니다.

회고록이라는 거창한 이름을 쓰기에는 부족한 점이 많지만, 이왕 이렇게 된 거 솔직하게 써보려고 합니다


0. Team SPAO 소개

프론트엔드 Git 링크
백엔드 Git 링크
팀 노션 링크

프론트엔드 : 강성구 김현진 정경훈
백엔드        : 김주현 송영록 이기용(PM)


1. 1주차


1-1. 역할 분담


프론트 엔드 필수구현사항 역할 분담
강성구 : 메인페이지 View
정경훈 : 회원가입, 로그인, 장바구니 추가 및 조회
김현진 : 상품리스트, 상품상세페이지, 후기 및 댓글 작성

백엔드 필수구현사항 역할 분담
김주현 : 후기 및 댓글 작성
송영록 : 회원가입, 로그인, 장바구니 추가 및 조회
이기용 : 상품리스트, 상품상세페이지, 데이터생성 및 DB관리


1-2. Scrum & Trello

프로젝트 진행은 Scrum 방식을 따랐습니다.

Scrum

  • 1~4주 단위의 개발주기를 설정하여, 각 주기마다 실제 동작할 수 있는 결과를 제공하는 개발 방식
  • 작은 목표를 짧은 주기마다 설정하여, 점진적으로 개발

1차 프로젝트 돌아보기에서 쓴 것처럼, 트렐로의 기본 관리 카드는 아래와 같이 4개로 구성되어 있습니다

Backlog : 앞으로 해야 할 모든 일
This Week : 금주내로 해야 할 모든 일
In progress : 진행중
Done : 끝

하지만 세분화된 관리가 더 필요하여, 아래와 같이 나누게 되었습니다.

해야 할 일
이번 주내로 해야할 일
진행 중
Push완료
코드리뷰 완료
Backorder
Done


1-3. 마음가짐


처음에 팀 발표가 났을 때 드는 생각은 제 마니또였던 산성님에게 말했었는데

재미있겠다! 였습니다. 그 때 산성님께 몰래 챙겨드릴 걸 그랬네요 ㅎㅎ;

대학교 때 부터 팀을 이끄는 걸 좋아해서, 이번 팀도 잘 이끌어보고 싶었고

시작 전 주말동안 이전 기수의 1차 프로젝트 회고록을 보며, 내가 어떤 걸 준비하면 될지 적어놨던 것 같습니다
(상세 내용은 1주차 돌아보기에 있습니다)

사실 팀의 PM은 첫 주 수요일인가 목요일날 퍼포먼스 코치님과의 대화날 정해졌었지만,

저는 첫 날부터 내가 PM이다라는 마인드로 회의를 이끌고, 의견을 정리하고, 팀을 이끌었습니다.
(나이먹으면서 수줍음이 증가된건지, 막상 내가 이끌겠다고 말을 못 하겠더군요)

팀원의 경우, 이야기 못 해봤던 분들이 대다수였지만 같은 꿈을 가지고 온 사람들이니

친해지면 되겠다라고 생각했던 것 같습니다.


1-4. 프로젝트 시작


프로젝트를 진행하면서 걱정했던 부분은 총 2가지였습니다.

  1. 내 실력
  2. 팀원의 실력

1번의 경우, 스스로 실력이 부족한 걸 알았기 때문에 팀을 잘 이끌면서 내 할 일을 할 수 있을지 걱정되면서도,

제 실력에 주제넘게 빨리 내 할 일 끝내고 팀원을 도와주고 싶었습니다.

같은 팀원이 나한테 모르는 걸 물어봤을 때 해결을 해줘서, 팀에 도움이 되고 싶었기 때문입니다.

2번의 경우, 프로젝트 전까지 이야기를 못 해봤던 사람들이었기 때문에 어느정도 하는지 제가 몰랐습니다.

그래서 잘하는 사람이 있는데, 내가 팀을 이끌어서 팀에 피해를 주는 게 아닌지라는 생각이 들었고,

만약, 조금 부족한 사람이 있으면 내가 이 사람을 케어해줄 수 있을만큼 역량이 있는지 생각이 들었습니다.

이렇게 두 가지 걱정을 가지고 시작하게 되었습니다.


1-5. 1주차 종료


1주차 종료 및 후기에 대해서는 1주차 돌아보기로 대체하겠습니다.


2. 2주차


2-1. TEST


1주차가 끝나고 백엔드의 기본적인 부분이 구현이 완료되어,

주말에 백엔드 팀원끼리 모여서 테스트를 했습니다.

이전 회사 사수분께서 테스트는 오류를 내려고 하는 것이라는 정말 좋은 철학을 저에게 공유해주셨기 때문에,

저 또한 이번 테스트에서 오류가 날 경우의 수에 대해 연구하고, 테스트를 팀원과 진행했습니다.

다만, 프론트의 관점과 백엔드의 관점은 다르기 때문에 백엔드의 관점에서만 테스트가 된 게 아쉬웠습니다.

제가 이전회사 출근한 첫 날에 사장님께서 하셨던 말씀이 있었습니다.

지금 이기용이라는 사람은 우리 회사의 일원이지만,
우리 소프트웨어를 처음 쓰는 이방인이라고도 볼 수 있다.
이미 기존 사람들(내부 직원)은 우리 소프트웨어에 적응하여 새로운 시각을 보는데 어려움이 있다.
그래서 모르는 사람의 관점에서 테스트가 진행되어 생각치도 못 한 오류를 찾아야 하는데,
이 부분에 대해서 당신의 테스트 결과가 정말 중요하다

저 또한 이 말에 굉장히 동의했기 때문에, 백엔드 내부 테스트가 끝나도 오류가 나올거라고 생각했습니다.

일단 백엔드끼리 진행하는 시간을 가졌고 당연히 사소한 오류들도 있었습니다.

그런 부분들을 수정하며 1차적으로 코드의 완성도를 높이는 시간을 가졌습니다.


2-2. 중간발표


프론트엔드와 백엔드가 나뉘어 중간발표를 하는 시간이 있었습니다.

실제로 저는 제 벨로그에 작성한 내용을 모두에게 보여주며 발표를 했습니다.

또한 각 발표자별로 공유하고 싶은 코드에 대해 이야기를 할 시간도 주어졌었습니다.

다른 팀의 백엔드분이 offset과 limit을 이용한 페이징기법 구현하는 거에 대해 방법을 찾는다고 하셨고,

저는 offset과 limit을 이용해 이미 구현이 끝난 상태여서, 공유할 코드로 이 내용을 발표했습니다.

자세한 코드는 아래 결과물에서 다루겠습니다.


2-3. 프로젝트 종료


프로젝트를 마치고 연동하는 과정에서 역시 오류가 발생했습니다.

다행인 점은 서로 오류에 대해 니 책임, 누구 책임이 아닌 실수를 인정하는 모습들이 보기 좋았고

2차 땐 각자 할 거 다 하고 맞추는 게 아닌 중간중간 틈틈이 맞추는 게 좋을 것 같다는 생각이 들었습니다.

발표는 제가 PM으로서 팀원 및 각자 기능 소개, 백엔드 부분 발표를 진행한 다음

프론트엔드 발표자에게 바통을 넘겨 시연하는 방식으로 진행되었습니다.

제가 로직 구현한 부분에 대해서만 잠시 첨부하자면

1. 상품리스트 조회

  • offset, limit을 이용한 페이징기법
  • 최신상품, 높은가격순, 낮은 가격순, 상품명 정렬

2. 상품상세페이지 조회

  • 상품별 색상/사이즈, 등록시 저장된 이미지들 불러오기
  • 상품별 후기 및 댓글 정보 불러오기


2-4. 마무리


프로젝트가 끝나고 아쉬운 점들이 많지만 가장 먼저 든 생각은 이 사람들과 헤어지기 싫다 였습니다.

부족한 점이 많은 사람이자, 개발자이자, PM임에도 저를 믿고 따라와줬고

각자 실수에 대해 인정하고, 괜찮다고 격려해주는 모습에 제가 더 힘을 얻었기 때문입니다.

제가 겪었던 회사에서의 TF팀은 하나의 목표를 위해 모인 사람이라고 해도,

동상이몽을 꾸는 듯한 느낌이 많아서 얼굴 붉히고 그런 일이 많았었습니다.

이전 경험과는 전혀 다른 느낌의 저에게 소중하고, 즐거웠던 프로젝트였습니다.

다음 2차 프로젝트나, 현업으로 나가서 어떤 마음가짐으로 일해야 하는지

깨닫게 해준 소중한 사람들과의 시간이었습니다.

  • 메인 페이지를 만든 성구님
    아쉽게도 이번 프로젝트 때는 백엔드와 연동이 없어서, 제가 메인 페이지는 우리 프로젝트의 얼굴이니
    프론트엔드적(?) 기술을 많이 보여달라고 했는데 실제로도 잘 만들어주셔서 감사합니다
  • 로그인/회원가입/장바구니를 만든 경훈님
    끝나고 회식 때, 본인이 뒤쳐지는 거에 대한 걱정들이 많았다고 이야기 해주셨는데
    실력이 부족하다는 거는 그만큼 올라갈 길이 높다는 거니까 걱정 안 하셔도 될 거 같아요
    그리고 만드신거 보니 그런 걱정할 필요 없이 충분히 2차 때도 잘할겁니다
  • 저와 가장 많이 소통한 현진님
    덕분에 편하게 함께 작업했습니다 ^^7

* 그리고 우리 백엔드 팀원인 주현님, 영록님
같은 백엔드로서, 모르는 부분에 대해 같이 공유하고 서로 알려준 덕분에 의지 많이 할 수 있었습니다
1차 때부터 이렇게 좋은 백엔드 팀원을 만나 스스로도 한 단계 더 성장할 수 있는 시간을 만들어주셔서
감사합니다


3. 개선사항


3-1. 효율적인 코드를 작성하자


2차 프로젝트는 1차 때 경험을 통해 더 발전할 수 있는 시간이 되도록하는 기간입니다.

1차 때는 시간에 쫓겨 내 구현하는 거에 급급했다보니, 다 하고 나서도 수정할 부분이 많이 보였습니다.

실제로 아래의 코드도 수정을 약간 하여 첨부했습니다.

그래서 구현을 하며, 정말 질 좋고 효율적인 코드를 작성해야겠다고 마음 먹게 되었습니다.


3-2. 모델링


데이터베이스 모델링을 프론트엔드와 같이 하라고 하셨습니다.

하지만 그 말을 전혀 이해하지 못 하고 백엔드끼리 했었습니다.

그런데 프로젝트를 진행하고 보니 어떤 의미인지 깨달았습니다.

1차 때, 백엔드에서 모델링하고 변수 설정하여 프론트에 맞춰달라는 형태의 작업이 많았었습니다.

그게 아니라, 처음 회의 때 구현해야 할 페이지를 정할 때 어떤 게 필요한지 같이 논의를 하라는 것이었습니다.

사람마다 어떻게 생각할지 모르겠지만, 프론트와 함께 모델링하라는 게 저는 이 뜻이라고 생각합니다.

프-백의 관점이 달라 함께 연구하여 프로젝트를 완성하라는 뜻이지요.


3-3. 나는 백엔드만 할거야!


처음에 생각할 때, 나는 백엔드 개발자니까 파이썬과 장고를 잘 알면 되지 않나?가 제 머리속에 있었습니다.

또한, 장고의 플로우를 알았지 프론트-백엔드 연동에 대해 잘 알지는 못 했습니다.

하지만 프로젝트를 진행하며, 리액트에 대해 알 필요가 있었습니다.

프론트 팀원분들이 저에게 물어본적이 있었는데, 초기에는 아는 게 없다보니 되게 어버버할때가 많았습니다.

그리고 팀원이 물어봤으니, 나는 백엔드인데 왜 나한테 물어봐? 가 아닌 해결을 위해 도움을 주려고

노력하는 것이 올바른 PM의 자세라고 생각했습니다.

리액트의 fetch 함수부터, 프론트 개발자가 만든 함수 등 리액트 로직을 천천히 이해하다보니

프론트엔드의 문제도 프로젝트 수준내에서 어느정도 해결할 수 있게 되었습니다.


3-4. 테스트는 한 번에 하는 게 아니다


아까 제가 살짝 언급한 것처럼, 테스트를 틈틈이 프론트엔드 개발자와 해야 할 것 같다고 생각이 들었습니다.

이 부분은 2차 때 꼭 팀원들과 함께 하려고 합니다.


4. 구현 코드


구현코드의 경우, 다 가져와봤자 읽히지도 않고 제가 공유하고 싶은 내용을 담은 코드만 작성했습니다


4-1. TimeStampedModel

from django.db import models

class TimeStampedModel(models.Model) :
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True, default=None, null=True)
    deleted_at = models.DateTimeField(auto_now=True, default=None, null=True)

    class Meta :
        abstract = True

이력관리를 위해 만들게된 모델입니다.

공유하고 싶은 부분은 deleted_at에 대한 내용입니다.

우리가 회원탈퇴를 할 때, 바로 되는 경우도 있지만 30일정도 지나야 탈퇴되는 경우가 있습니다.

제가 알기론, 페이스북도 탈퇴를 해도 즉시 되는 게 아닌 일정 기간이 지나야 하며

그 사이에 로그인을 하면 다시 계정이 복구됩니다. 그 때 사용하는 것이 deleted_at입니다.

그리고 이걸 Soft Delete라고 합니다.

삭제를 하면 delete_at에 삭제한 날짜가 들어가고 해당 컬럼의 값 유무에 따라 값을 보이게 하는 것입니다.

물론 데이터베이스 상에서 row가 삭제되는 게 아니라 join할 때 불편한 점이 생길 수도 있지만

현업에서 어떤 삭제 방식을 하든, 이번 경험을 통해 해낼 수 있을 거라고 생각합니다.


4-2. offset & limit

def get(self, request, menu_name, category_name) :
        try :
            offset   = int(request.GET.get('offset', 0)) 
            limit    = int(request.GET.get('limit', 0))
            
            if limit - offset > 20 :
                return JsonResponse({'message':'too much lists'}, status=400)

            menu_id     = Menu.objects.get(name=menu_name)
            category_id = Category.objects.get(menu_id=menu_id, name=category_name)
            products    = Product.objects.filter(menu_id=menu_id, category_id=category_id)[offset:offset+limit]

페이징 기법을 위해 프론트엔드에서 쿼리 파라미터 형태로

localhost:8000/products?offset=0&limit=15

이렇게 요청을 백엔드로 보내게 됩니다.

그러면 offset과 limit파이썬에서는 딕셔너리, 리액트에서는 객체 형태로 값을 보내게 되는데

이때 장고에서 사용하는 게 GET 이라는 메소드입니다.

offset = int(request.GET.get('offset',0))

KEY값이 offset인 것의 값을 가져오되, 없으면 0 이라는 값으로 offset을 설정하겠다는 뜻입니다.

get()이라는 메소드 자체가 딕셔너리형태의 데이터를 대상으로 값을 불러오긴 하지만

request자체는 딕셔너리가 아니기 때문에, request를 딕셔너리로 바꾸기 위해 request.GET을 사용합니다.

또한 프론트엔드에서 데이터의 요청하는 개수를 제한하기 위해 limit과 offset의 차가 일정 이상 넘어가면

너무 많다는 메시지를 띄워주도록 설정하였습니다.


4-3. sort

해당 코드에서 정렬 기능을 붙여주게 되었는데, 정렬 기준은 총 4가지였습니다

최신등록순, 가격높은순, 가격낮은순, 이름

그래서 저는 두 가지 방법을 생각했었습니다

1. 프론트에서 fetch 함수 4개 작성해서 4개의 url로 나눔
2. 하나의 fetch함수에서 요청하되 백엔드에서 조건별로 if문 계속 작성

둘 다 말도 안되는 것이었기 때문에 멘토님께 의견을 구하게 되었고,

딕셔너리 형태와 리스트형태 두 가지를 알려주셨습니다.

제가 사용한 방식은 딕셔너리 형태이며, 2차 때 리스트를 해보고 싶습니다.

def get(self, request, menu_name, category_name) :
        try :
            offset   = int(request.GET.get('offset', 0)) 
            limit    = int(request.GET.get('limit', 0))
            order_id = int(request.GET.get('order_id', 0))

            order_dic = {
                0 : 'created_at',
                1 : '-price',
                2 : 'price',
                3 : 'name'
            }

            if limit - offset > 20 :
                return JsonResponse({'message':'too much lists'}, status=400)

            menu_id     = Menu.objects.get(name=menu_name)
            category_id = Category.objects.get(menu_id=menu_id, name=category_name)
            products    = Product.objects.filter(menu_id=menu_id, category_id=category_id).order_by(order_dic[order_id])[offset:offset+limit]

프론트에서 최신등록순, 가격높은순, 가격낮은순, 이름에 대해 태그를 만들텐데,

그 태그에 값을 0, 1, 2, 3 순으로 기입하여 저에게 보내달라고 했습니다.

그러면 쿼리파라미터형태로 order_id=0 이러한 형태로 값이 오게 됩니다.

그러면 저는 숫자를 Key 값으로, 각 순서에 해당하는 컬럼명을 Value로 지정하면 되는 것입니다.

그리고 대부분의 사이트 역시, 신상품이 첫 기준이기 때문에 order_id가 없으면 기본 정렬을 최신순으로 했습니다.

그래서 order_id를 보내주면 맨 아래줄의 order_by(order_dic[order_id]) 라는 문법을 통해

정렬된 리스트를 프론트엔드에 보내줄 수 있게 되는 것입니다.


이상 긴 회고록 읽어주셔서 감사합니다

profile
ISTJ인 K-개발자

2개의 댓글

comment-user-thumbnail
2021년 10월 17일

"이 사람들과 헤어지기싫다" 라는 말이 눈에 확 들어오네요 🤭
첫 플젝이라 에너지 많이 쓰셨을텐데 수고 많으셨어요 기용님 👏🏻👏🏻👏🏻

1개의 답글