제로베이스

WECAR 프로젝트 회고 본문

프로젝트 회고

WECAR 프로젝트 회고

코드킴 2023. 6. 20. 17:01

📍 위코드에서의 마지막 프로젝트

위코드에서 2개월 동안의 2개의 프로젝트를 진행하였고, 마지막으로 한달 간 기업협업 과정을 경험하게 되었다. 기업협업 이라는 과정이 위코드를 선택한 가장 큰 이유였다. 현업 개발자 분들과 함게 일하며 실제 기업에서 배울 수 있는 경험은 매우 소중하다고 생각했다. 하지만 실제로 배정 받은 기업에 출근해보니 기대했던 만큼의 관심과 지원을 받지 못한 느낌을 받았다. 이런 상황에서 마냥 기다리며 공부만 하는 것은 시간 낭비일 것이라고 판단하여 함께 간 백엔드 동기와 직접 프로젝트를 기획하고 기업 사수분들께 제안하기로 결정했다. 학원에서 해보고 싶었지만 선택되지 못해서 진행하지 못한 프로젝트를 이번 기회에 해볼 수 있겠다는 생각에 신나게 Producting 기획안과 스프린트 계획을 Notion을 활용하여 정리해서 사수분들께 브리핑을 했다. 적극적으로 의사를 표현한 결과, 사수님들께서는 매주 코드 리뷰를 진행해주시기로 하셨고 기획안을 들으시며 프로젝트의 방향을 함께 정리해주셨다. 그렇게 프론트엔드 1명과 백엔드 1명 둘이서 3주 동안 프로젝트를 진행하게 되었다.

 

📍 그렇게 탄생한 WECAR

WECAR란?

전국 차량 공유 서비스 WECAR는 차량을 구하는 사람(게스트)과 본인이 소유하고 있지만 자주 타지 않거나 일정 기간 방치될 수 있는 차량을 빌려줘서 돈을 벌고자 하는 사람(호스트)을 연결해주고 수수료를 받는 서비스를 제공하는 플랫폼이라고 보면 된다.

 

 

 

📍 혼자서 프론트엔드 전체를

혼자서 프론트엔드 전체를 책임지는 일은 처음이었다. 기간 내에 모두 완료할 수 있을꺼라는 걱정도 있었지만, 설렘과 기대가 더 크게 다가왔다. 완벽주의성향을 가지고 있어 UI 구성에 있어서도 세세한 부분까지 신경을 쓰며 작업을 하는편인데 이전에 진행한 프로젝트에서는 여러 프론트엔드 동기들과 함께 작업하다보니 UI의 통일성도 떨어지고 다른 팀원들과 역할을 분담하여 작업했기 때문에 맡은 기능이 제한적이어서 아쉬움이 남았었다. 하지만 이번 프로젝트에서는 혼자하다보니 다양한 기능을 해볼 수 있고 더 나은 결과물을 만들고자 하는 코드에 대한 욕심도 크게 다가왔다.

 

프로젝트를 마치고 돌이켜보았을 때, 내가 생각한 혼자서의 장점은 두가지가 있었다.

첫째, 매우 자유로웠다는 점이다. 혼자 작업할 때는 다른 사람들과의 코드 충돌을 걱정하지 않아도 되고, 다른 사람이 내 코드를 기다리지 않기 때문에 내가 원할 때 언제든지 코드를 수정하고 추가할 수 있었다. 또한 문제가 발생했을 때 충분히 시간을 들여 고민할 수 있었다.

둘째, 모든 코드를 파악할 수 있다는 점이다. 협업 시에는 내가 맡은 부분에만 집중하다 보면 전체적인 구조를 파악하지 못하는 경우가 종종 있다. 다른 사람이 사용한 라이브러리나 외부 API 등에 대해 이해하지 못하고 넘어가는 경우가 있을 수 있다. 그러나 혼자 작업할 때는 모든 코드를 파악하게 되어 이러한 부분이 없어졌다. 이를 통해 혼자서 프로젝트를 진행하는 경험은 매우 유익하였고, 개발자로서 성장하는 데 큰 도움이 되었다.

 

위와 같이 좋은 부분도 있었지만, 프로젝트를 진행하면서 가장 아쉬웠던 부분은 혼자서 평가하기 어려웠다는 점이다. 매일 수많은 난관에 부딪치며 코드를 작성하는 과정에서 내가 제대로 하고 있는지, 최선의 방법인지를 판단하기가 어려웠다. 함께 얘기하며 고민할 동료가 있다면 좋았을 텐데라는 생각이 들었다. 내가 진행한 프로젝트는 리액트로 개발을 진행한 프로젝트인데 기업의 프론트엔드 사수는 앵귤러 개발자라 자세한 피드백을 받을 수 없는 점도 아쉬웠다. 좀 더 완성도 있는 프로젝트를 마무리하기 위해서는 협렵하는 동료들과 시너지를 내는 것이 중요하다는 것을 다시 한번 느낄 수 있는 경험이었다.

 

📍 이번 프로젝트에서 처음 시도

axios 인터셉터

아래 코드와 같이 이번 프로젝트에서는 서버와 통신을 할 때 간편한 API 호출과 요청과 응답의 중간 처리를 위해 axios 라이브러리를 사용했다. 토큰의 유무가 필요한 기능이 많았기 때문에 서버에 요청을 보낼 때 매번 헤더에 accessToken을 담아서 코드를 구현하면 코드의 중복도 많아지고 비효율적인거 같아서  axios 인터셉터 기능을 활용해서 특정 요청을 제외하고는 모든 요청에 accessToken 담아서 보내도록 코드를 구현하였다. 이를 통해 매번 헤더에 accessToken 추가하는 코드의 중복을 줄일 수 있었고 효율적인 API 호출을 할 수 있었다.

 

axios.interceptors.request.use(
    config => {
      if (
        config.url?.includes(HOST_ADDRESS) &&
        !config.url?.includes('refresh')
      ) {
        const accessToken = localStorage.getItem('accessToken');
        if (accessToken) {
          config.headers.Authorization = `Bearer ${accessToken}`;
        }
      } else if (config.url?.includes('refresh')) {
        const refreshToken = localStorage.getItem('refreshToken');
        if (refreshToken) {
          config.headers.Authorization = `Bearer ${refreshToken}`;
        }
      } else {
        delete config.headers.Authorization;
      }

      return config;
    },
    error => {
      return Promise.reject(error);
    }
  );

사용자 인증 방식

이전 프로젝트에서는 accessToken 하나로 사용자를 인증하는 방식이었다면 이번 프로젝트에서는 보안상의 이유로 엑세스 토큰의 유효시간을 짧게 설정하고, 엑세스 토큰과 리프레시 토큰 두 가지를 사용하여 사용자 인증을 구현했다. 엑세스 토큰의 유효 시간이 만료되면 axios 인터셉터를 활용하여 유효 시간이 긴 리프레시 토큰을 사용해 엑세스 토큰을 재발급 받는 방식으로 진행했다.

 

엑세스 토큰의 유효 시간이 만료되어 인증이 필요한 요청에서 401 오류가 발생하면, axios 응답 인터셉터에서 해당 오류를 감지하고 리프레시 토큰을 사용하여 새로운 엑세스 토큰을 발급 받는 요청을 보낸다. 이후 새로 발급 받은 엑세스 토큰을 로컬 스토리지에 저장하고, 재요청할 때는 엑세스 토큰을 헤어데 담아 보낸다. 이를 통해 엑세스 토큰이 탈취 되더라도 유효 시간이 짧기 때문에 악용을 최소화 할 수 있으며, 보안적으로 안전하면서 효율적인 토큰 관리를 할 수 있다.

 

axios.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    const originalRequest = error.config;
    const refreshToken = localStorage.getItem('refreshToken'); // 로컬 스토리지에서 리프레시 토큰을 가져옴

    if (
      error.response.status === 401 && // 인증 실패 상태 코드 확인
      !originalRequest._retry && // 재시도한 요청이 아닌 경우
      refreshToken
    ) {
      originalRequest._retry = true;
      
      // 리프레시 토큰을 사용하여 새로운 엑세스 토큰 발급 요청
      return axios
        .post(`${HOST_ADDRESS}/hosts/refresh`, { refreshToken })
        .then((res) => {
          if (res.status === 200) {
            const newAccessToken = res.data.accessToken;
            localStorage.setItem('accessToken', newAccessToken);
            originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
            return axios(originalRequest);
          }
        });
    }

    return Promise.reject(error);
  }
);

 

어드민 페이지

이번 프로젝트에서는 관리자의 입장도 고려하여 서비스를 구현해보라고 현업 개발자 분들께서 조언해주셨다. 이전에는 주로 사용자의 입장에서 웹사이트를 이용하는 기능을 구현하는 데에 집중했었는데 이번에는 관리자가 웹페이지를 관리하고 데이터를 업로드 할 수 있는 기능을 고려하게 되었다. 일반적으로 웹 서비스에서는 관리자가 필요한 경우가 많다. 그러나 이전에는 사용자의 입장에서만 생각 하다보니 관리자 기능을 고려한 적이 없었다. 이번 프로젝트에서는 이러한 부분을 인지하고, 서버에서 데이터를 생성하여 관리하는 것이 아니라, 실제 어드민페이지를 만들어 관리자가 차량을 등록하면 서버에 저장된 차량 리스트가 업데이트되고 웹페이지에 업로드 되는 방식으로 구현했다. 이를 통해 편리하게 데이터를 업데이트할 수 있으며, 관리자와 사용자 모두에게 더 나은 경험을 제공할 수 있었다. 또한 이전에는 고려하지 못했던 관리자의 입장을 고려함으로써 서비스의 완성도를 높일 수 있었다.

 

AWS S3 signed url

차량을 등록할 때 이미지 파일을 업로드 하는 과정에서 AWS S3를 사용하기로 결정 했고 네트워크 효율을 고려하여 서버로부터 signed URL을 받아오고 해당 URL에 PUT 메소드를 이용하여 클라이언트가 파일을 업로드 하는 방식으로 구현했다. 이때 리스폰스로 반환된 signed URL을 이지미 URL로 가공하여 다시 서버로 전달했다. 그러나 파일 업로드 과정에서 문제가 발생했고 여러 시도 끝에 문제를 해결했다. 초기에는 signed URL이 문제인 줄 알았지만 실제로는 그렇지 않았고 문제는 내가 구현한 코드 중 모든 요청의 헤더에 사용자 엑세스 토큰을 넣은 인터셉터에서 발생한거였다. 이로 인해 외부 서버에 전송되는 요청에도 인증되지 않은 토큰이 포함되어 거부되었다. 이러한 연쇄적인 동작 때문에 문제의 원인을 파악하는 데에 시간이 오래 걸렸다. 특히 인터셉터를 처음 사용해보는 상황에서 오류의 원인을 찾는 것은 더욱 어려웠다. 

 

이 경험을 통해 인터셉터와 클라이언트 - 서버 간 연쇄 동작에 대한 이해를 높일 수 있었고, 앞으로는 유사한 문제가 발생했을 때 원인을 보다 쉽게 파악할 수 있을 것 같다. 오류를 해결하는 과정은 개발자로서 성장하는 기회이며, 이번 경험을 통해 더욱 능동적이고 효율적인 문제 해결 방법을 익힐 수 있었다.

 async function fetchSignedURL() {
    try {
      const response = await axios.get(`${HOST_ADDRESS}/utils/aws/signedurl`);
      const signedURL = response.data.signedUrl; // 서버에서 반환한 Signed URL
      return signedURL;
    } catch (error) {
      console.error('Failed to fetch Signed URL:', error);
      throw error;
    }
  }

  async function uploadFile(file: File) {
    try {
      const signedURL = await fetchSignedURL(); // Signed URL을 받아옴

      // Signed URL을 사용하여 파일 업로드
      await axios
        .put(signedURL, file, {
          headers: {
            'Content-Type': file.type,
          },
        })
        .then(response => {
          const { config } = response;
          const imageUrl = config.url?.slice(0, config.url.indexOf('?'));
          setFileData([
            ...fileData,
            {
              name: config.data.name,
              type: config.data.type,
              url: imageUrl,
            },
          ]);
        });
    } catch (error) {
      console.error('Failed to upload file:', error);
      throw error;
    }
  }

외부 API

카카오 지도 API :

  • 카카오 지도 API 사용하여 차량 등록 입력한 픽업 반납 위치를 기반으로 핀을 꽂아 지도로 보여주어 사용자에게 위치를 시작적으로 전달할 있게 구현했다.

토스페이먼츠 API

  • 사용자는 결제 수단을 토스로 선택하고 예약 요청 버튼을 클릭해 Toss 결제 API를 호출하여 결제를 진행 한다.
  • 결제 완료 후, 서버에 결제 정보를 전송합니다. 이후 서버에서는 Toss 결제 API의 secret key를 사용하여 결제 승인 요청을 생성한다.
  • 결제 승인이 성공적으로 이루어진 경우, 완료 페이지로 이동하여 사용자가 상세한 예약 내역을 확인할 있다.

리액트 라이브러리

react-daum-postcode :

  • 'react-daum-postcode' 활용하여 우편번호 검색과 도로명 주소 검색 기능을 간편하게 구현할 있다. 사용자가 주소를 선택하면 해당 주소 또는 우편번호가 입력 필드에 표시된다.

react-calendar : Input 태그의 date 타입을 사용하지 않고, react-calendar 라이브러리를 활용하여 픽업 및 반납을 지정할 수 있는 기능을 구현했다. react-calendar 라이브러리를 선택한 이유는 아래와 같다.

 

  • 웹사이트의 전체적인 디자인과 UI 통일감 있게 구현하기 위해서였다. Input 태그의 date 타입은 제한적인 스타일링 기능을 제공하기 때문에, react-calendar 사용하여 자유로운 달력 디자인을 구현할 있었다.
  • minDate와 maxDate 속성을 이용하여 선택 가능한 날짜 범위를 제한함으로써 차량 등록 시 지정한 날짜 이외에는 선택할 수 없도록 제한하여 사용자가 잘못된 날짜를 선택하는 것을 방지했다.
  • tileDisabled 속성을 활용하여 예약이 진행된 날짜에는 선택을 할 수 없도록 설정하여 충돌을 방지했다.

위와 같이 react-calendar 라이브러리를 활용하여 달력을 구현하고 날짜 선택 기능을 구현현 결과, 웹사이트의 디자인 일관성을 유지하며 사용자가 직관적으로 픽업 및 반납일을 선택할 수 있으며, 잘못된 날짜 선택이나 중복 예약을 방지할 수 있다.

 

 

📍 새로운 언어 TypeScript

이번 프로젝트에서 특별했던 점은 TypeScript를 스스로 익혀 적용한 것이다. 이전 프로젝트에서 자주 발생하는 런타임 에러를 줄이기 위해 TypeScript를 도입하고자 했다. TypeScript는 JavaScript 기반에서 타입만 지정해주면 되는 부분이라고 생각하여 몇 일간의 공부 후 프로젝트에 적용했다. 그러나 TypeScript는 처음 접해보는 개념이어서 어려움이 많았다. 타입에 대한 개념이 익숙하지 않으니까 중간 중간 간 막히는 부분이 많았지만 매일 공부하면서 조금씩 익숙해져서 나중에는 조금 더 쉽게 사용할 수 있었다. 직접 부딪혀가며 사용해보는 것이 가장 좋은 학습 방법이라는 것을 깨달았다. 짧은 시간이었지만 TypeScript를 사용해보니 매우 강력하고 안정적인 언어라는 생각이 들었다. TypeScript를 사용하니 확실히 런타임 에러가 훨씬 줄어들고 안정성이 높아진게 느껴졌다. 다시 JavaScript는 돌아갈 수 없을 것 같다. 

📍 현업 개발자의 조언

이번 프로젝트에서 가장 값진 경험은 현업 개발자분들의 피드백을 받을 수 있었다는 점이었다. 매주 수요일마다 코드 리뷰를 받으며 일주일 동안 작성한 코드를 함께 검토하고 피드백을 받을 수 있었습니다. 제 사수님은 앵귤러 개발자이셔서 리액트로 구현한 이번 프로젝트에서는 리액트 관점의 피드백은 많이 받지 못했지만, 전반적으로 프론트엔드 개발을 진행할 때 이전 프로젝트에서는 간과하고 넘어갔던 부분들을 중점으로 조언을 주셨다.

먼저, 로딩 처리에 대해 이야기를 나누었다. 서버에서 데이터가 들어오기 전에 아무런 반응이 없다면 사용자가 이탈할 수 있기 때문에 로딩 중임을 사용자에게 알리는 것이 좋다고 하셨다. 데이터가 로딩 중이라는 표시를 해주어 사용자가 인식할 수 있도록 하는 것이 중요하다는 말씀을 듣고 바로 프로젝트에 적용했다.

 

 

또한, 보안 관련해서도 조언을 받았다. 사용자 인증이 필요한 부분에서 토큰 관리를 어떻게 하면 더 안전하고 효율적으로 관리할 수 있는지에 대해 이야기를 나누었다. 안정성을 고려하여 토큰을 관리하는 방법에 대해 조언을 받을 수 있었다. 그리고 네트워크 효율에 대해서도 언급이 있었다. 차량 등록 시 이미지 파일을 업로드하는 과정에서 AWS S3를 사용하여 네트워크 효율을 고려해 서버로부터 signed URL을 받아오고 해당 URL에 PUT 메소드를 이용하여 클라이언트가 파일을 업로드하는 방식으로 구현하라고 조언을 받았다.  해당 조언들도 듣고 바로 프로젝트에 적용했다. 구체적인 구현 방법은 위에 내용이 있으니 참고하면 좋을 것 같다. 이번 프로젝트에서 학원에서 배우지 못했던 중요한 부분들을 많이 배울 수 있었다. 리액트 관점의 피드백은 아니었지만 프론트엔드 개발을 진행할 때 간과했던 중요한 부분들에 대해 많이 들을 수 있어서 좋았다.

 

코드적인 부분 뿐만 아니라 업무 방식이나 scrum 회의 방식 등에서도 옆에서 보고 배운 점이 많았다. 그 중에서도 가장 눈에 띄었던 방식은 프론드엔드와 백엔드 간의 통신 방식이었다. 이전 프로젝트에서는 통신을 위해 백엔드에게 서버를 열어달라고 요청하고 API를 받는 식이었으며 백엔드 팀원이 코드를 수정할 때는 서버를 사용할 수 없는 불편함을 감수해야 했었다. 그러나 이번 프로젝트에서는 각자의 코드를 GitHub에서 클론 받아 자신의 로컬 환경에서 실행시키는 방식으로 진행했다. 이 방식은 정말로 센세이션했다. 이렇게 하니 통신 테스트를 원할 때 바로바로 수행할 수 있어 개발 효성이 크게 향상되었다. 백엔드 팀원이 코드를 수정하면 그 변경사항을 바로바로 확인하고 테스트 할 수 있었다. 서로간의 의사소통과 협업에 큰 도움이 되었고, 개발 프로세스를 원할하게 진행할 수 있었다. 

 

프론트엔드 1명과 백엔드 1 둘이서 진행하면서 각자 맡은 영역에서 다양한 기능들을 시도해볼 있어서 좋았다. 둘이서 협업하여 프로젝트를 진행하는 것도 새로운 경험이었다. "사공이 많으면 배가 산으로 간다" 라는 말이 있듯이, 명이서 진행하다보니 프로젝트 방향도 확실하게 잡히고 조화롭게 진행 있었다.

 

 

POSTMAN: https://documenter.getpostman.com/view/26388948/2s93eeQUpz

프로젝트 진행 시 Postman의 Documentation을 활용하여 백엔드와 소통하였습니다.

사용한 모든 API Postman에서 확인할  있습니다. 

 

WECAR APIs

The Postman Documenter generates and maintains beautiful, live documentation for your collections. Never worry about maintaining API documentation again.

documenter.getpostman.com

 

프로젝트 저장소 링크: https://github.com/KIMYOUNGWOON/44-3rd-wecar

 

GitHub - KIMYOUNGWOON/44-3rd-wecar

Contribute to KIMYOUNGWOON/44-3rd-wecar development by creating an account on GitHub.

github.com

'프로젝트 회고' 카테고리의 다른 글

DREAM 프로젝트 회고  (0) 2023.06.15
4bsop 프로젝트 회고  (1) 2023.04.23