프로그래머스 데브코스 1차 프로젝트 고찰(회고)(1)

HappyFrog·2022년 7월 16일

가봤슈

목록 보기
1/2
post-thumbnail

개요

데브코스를 진행하던 와중... 1차 프로젝트를 하게 되었다.
시간은 그다지 길지 않은 2주 반안에 기획, 설계, 구현, 발표까지 완성해야 해서 여유롭게 코딩하지는 못하였다. 그래도 1차 프로젝트를 하면서 배운것도 많고, 해보고 싶은 것도 해보아서 이에 대한 회고를 작성하고자 한다.

프로젝트 관련 사항

  • 프로젝트 이름: 가봤슈

    • 일단 아이디어는 여러가지가 나오기는 했지만 사실 크게 두가지로 좁혀졌었다. 스터디 모집 플랫폼, 여행 일정 공유 사이트. 이 중 여행 일정 공유 사이트를 선택하게 되었는데, 고려한 사항으로는 비슷한 사이트가 있는가? 실용성이 있는가?를 집중적으로 보았었다.
    • 왜 가봤슈인가? 사실 팀원 분 중에 대전에 거주하고 계신 분이 계셨는데, 대전에 자전거 공유 플랫폼인 타슈를 조금 변형시켜서 가봤슈라고 이름을 짓게 되었다.
  • 협업 방식

    • 협업으로는 크게 깃허브, 슬랙, 카카오톡을 사용하였다.
    • 구현하는 기능 혹은 컴포넌트 단위로 브랜치를 만들었고, 구현을 다하면 pr을 날려서 2명 이상의 approve를 받으면 머지 하는 방식을 사용하였다.
    • eslint와 prettier를 사용하였다. (eslint의 경우 airbnb의 eslint를 사용하였다.)
  • 기술 스택

    • 전역상태 관리로는 Redux와 Recoil 간에 고민을 하였으나, Recoil이 Redux보다 이해하기 쉬운 점. 코드량이 적은 점. 개발 기간이 짧은 점들을 고려하여 Recoil을 선택하게 되었다. (Context Api를 사용하게 된 이유는 추후 적을 예정)
    • SPA로 사이트를 구성하여야 했기 때문에 React Router Dom을 사용하였다.
    • Css in js를 위해서 emotion, 특히 emotion/styled를 사용하였다.
    • UI 컴포넌트들을 직접 구현 하였는데, 이에 대한 테스트를 진행 할 때 불편함이 있어서, 프로젝트 중간에 storybook을 추가하였다.
    • 사실 Docker, Nginx, ec2 그리고 ci/cd를 위한 jenkins는 순수 내 욕심... 때문에 사용하게 되었다. 이전 부터 인프라 관련 경험을 쌓고 싶은 생각이 있었고 내 욕심이였기 때문에 이 부분(배포)는 내가 담당하여서 진행하였다.

프로젝트 진행

프로젝트 진행에 관한 사항은 내가 담당했던 부분에서 회고하고자 한다.

시작하기전 api에 관하여....

사실 api는 데브코스 측에서 명세서를 주었다.
이 때문에 편한점도 있긴 있었으나(백엔드와의 추가적인 소통의 불필요) 이것이 단점으로 작용하기도 하였다. (추가적인 기능 구현시 제약)

로그인 로직 & 회원가입 로직 구현

로그인 api는 로그인시 token을 내려주는 형태였다.
이 token을 react-cookie를 사용하여 쿠키에 저장하였고 recoil의 atom에 저장하여 전역에서 접근 할 수 있게 끔 하였다. 전역 atom으로 왜 사용하였는가 하면 사실 api 호출시 token의 필요성 때문이였다. http method와 관련된 사항들은 api js 파일에 따로 함수를 만들어 사용하였는데 각기 필요한 컴포넌트 혹은 페이지에서 호출시 token을 이 함수로 전달 해줄 필요가 있었고, 필요 할때마다 쿠키에 접근해서 token을 가져오는 방식보다는 recoil로, 즉 전역상태로 관리하여 필요할때마다 이 state를 활용하는 방식이 더 효율적이라 판단하여 recoil로 token을 관리하여 주었다.

import { selector, atom } from "recoil";
import { getCookie, setCookie, removeCookie } from "../utils/cookie";

// jwtToken atom
export const jwtToken = atom({
  key: "jwtToken",
  default: getCookie("token"),
});

// 로그인 여부를 관리하는 atom
export const loginStatus = atom({
  key: "LoginStatus",
  default: false,
});

또한 login시 login 처리를 해줄 필요성이 있어서 로그인 프로세스에 사용하기 위해 selector를 사용하였다.

export const loginProcess = selector({
  key: "loginProcess",
  get: ({ get }) => {
    return get(jwtToken);
  },
  set: ({ set }, newValue) => {
    // 현재 시간에서 1일 후 만료
    const TOKEN_EXPIRE_TIME = new Date(Date.now() + 60 * 60 * 24 * 1000);
	// cookie 설정
    setCookie("token", newValue, {
      path: "/",
      expires: TOKEN_EXPIRE_TIME,
    });
	// atom value update
    set(jwtToken, newValue);
  },
});

로그아웃 시에도 쿠키를 지우는 등의 로직이 필요하였기 때문에 selector를 사용하여주었다.
(여기서 { path: "/" }를 지정하지 않았더니 쿠키가 제대로 지워지지 않는 버그가 있었다.)

export const logoutProcess = selector({
  key: "logoutProcess",
  get: ({ get }) => {
    return !get(loginStatus);
  },
  set: ({ set }) => {
    removeCookie("token", { path: "/" });
    set(jwtToken, "");
    set(loginStatus, false);
  },
});

또한 api중 token의 유효성을 검증해주는 api가 있어 token을 검증하는 selector 또한 만들어 주었다.(이 api의 response는 user 객체, 즉 userdata 이기 때문에 token의 유효성 검사와 함께 user data를 return 시켜 주었다.)

export const isUserAuthenticated = selector({
  key: "isUserAuthenticated",
  get: async ({ get }) => {
    const token = get(jwtToken);
    if (token) {
      const res = await userAuth(token);
      if (res && res.data) {
        return {
          isTokenValid: true,
          userData: res.data,
        };
      }
    }
    return {
      isTokenValid: false,
      userData: null,
    };
  },
});

그렇다면 새로고침 & 재접속시에는..?

여기까지 봤다면, 의문점이 들 수 있다.

새로고침하거나 재접속하면? recoil에 저장된 state들이 초기화 되는 것 아닌가?

이 점을 해결하기 위한 방법으로 크게 두가지를 생각해 보았다.
1. recoil persist 사용하기
2. App.js에서 새로고침 & 재접속 시마다 인증 요청 보내기

여기서 나는 당연하게 2번을 선택하였다. 그 이유는 recoil persist는 localstorage에 값을 저장한다. 이때 localStorage에서 token 값이 노출이 되게 되고, token이 공격자에 의해서 쉽게 털릴 수 있기 때문이다.

 useEffect(() => {
    if (!isLogined && TokenExist) { //login은 안되어 있는데 token값이 있다면
      if (isTokenValid) { //token이 valid한가?
        setIsLogined(true); // valid 하다면 로그인 처리
        setUserInfo(userData);
      }
    }
  }, [isLogined, TokenExist, isTokenValid, userData]);

따라서 다음과 같이 코드를 App.js에 추가하여 새로고침 & 재접속 시마다 요청을 보내 확인 절차를 거치고 로그인 처리를 시켜 주었다.

🧐 조금 더 고민 해볼점 & 아쉬운 점

  • 로그인 요청을 보낼 때 payload에 password 암호화가 꼭 필요할까?
    • 해야 한다면 어떻게 해야 할까?
    • 다만 이번 프로젝트에서는 백엔드에 접근이 불가능 했기 때문에 front에서 암호화를 진행 하였어도 백쪽에서 복호화가 불가능 하기 때문에 별로 효과가 없을 것이라는 판단하에 진행하지 않았다.(다만 이후 백엔드와의 협업시 필요할 수도 있기 때문에 알아두면 좋을 것 같다)
    • 개인적인 생각이지만 proxy 서버를 구축한다면 백엔드의 협조 없이도 암호화를 구현할 수 있으리라 생각한다.
      • Nginx를 통해 이를 구현 할 수 있는지는 조금 더 확실히 찾아보아야 할 것 같다 🙄
  • 로그인, 로그아웃 프로세스를 selector를 통해 하는 방법이 맞을 까?
    • 일단 위의 코드를 보면 알 수 있지만, 사실상 selector의 get의 활용은 없다. 즉 setter만을 활용하는데 사실상 이는 useRecoilState, useSetRecoilState, useRecoilTransaction을 통해서도 달성 가능하다.
      • 다만 useRecoilState, useSetRecoilState는 로그아웃 로직의 경우 atom들을 업데이트 해주기 위해서 atom마다 호출 해야 하는 만큼, 위 중에서 대체재를 고른다면 useRecoilTransaction을 사용하는 것이 더 깔끔할 것 같다.
      • useRecoilTransaction을 사용하면 redux에서 action을 dispatch하는 것처럼 코드 가독성이 좋아짐을 느꼈다.(어떠한 action을 발생시킬건지에 대해서 좀 더 명확해지는 느낌을 받았다)
      • 다만 selector를 사용할때 보다 확실히 재사용성은 무척 떨어졌다. 즉 만약 로그인 로직을 다른 두개 이상의 컴포넌트에서 사용할일이 생기면 각 컴포넌트 혹은 페이지 마다 useRecoilTransaction 코드를 작성하여야 한다.
    • 다른 개발자들과 협업을 진행하고 있었고, 만든 로직들을 쉽게 재사용 될 수 있다는 점을 좀 더 중요하게 생각하여서 결국 selector로 진행하였다. (하지만 재사용이 별로 안된다면 useRecoilTransaction도 충분히 고려 해볼만 하다 생각이 들었다)
profile
성장하고 싶은 긍정 개구리

0개의 댓글