week3 멘토링 일지 - boostcampwm2023/web04-ALGOCEAN GitHub Wiki

week3 멘토링 일지

✅ 체크리스트

이번주에 우리 팀이 되고 싶은 모습을 상상하며 체크리스트를 추가해봐도 좋습니다.

멘토가 보기에 우리 팀은 어떤지 의견을 구해보세요.

  • 지난주에 마무리하지 못한 기능 구현을 마치고, 이번주 목표의 80% 이상 구현하고 있다.
  • 작성한 코드나 구현 과정을 근거 있게 설명할 수 있다.
  • 첫 주차 부터 지금까지의 핵심 과정이 문서화되어 누적되어 있다.
  • API 연동에 대해 협의하여 계획이 나왔거나, 시도하고 있다.

✔️ 결론 및 To Do

멘토링 이후에는 결론과 To Do를 작성하고 실천해보세요.

멘토에게도 공유하여, 의도와 다르게 정리되지는 않았는지 검토를 받아볼수도 있습니다.

✔️ 아젠다 및 질문

멘토가 미리 아젠다와 질문을 보고 올 수 있도록 사전에 준비하여 공유합니다.

FE & BE 공통

프로젝트 현황

FE

🌊 [www.algocean.site](https://www.algocean.site/)

이번 주 한 일

질문 목록

  • 폼을 쉽게 관리하기 위해서 react-hook-form 라이브러리를 사용한 적이 있습니다. (유효성 검사, 편리한 상태 관리, 포맷팅 제어, 성능 최적화) 현업에서 form 관리를 위해 라이브러리를 사용하는지 궁금합니다.

  • 현업에서 위지위그 등의 텍스트 에디터 데이터를 저장할 때, 어떤 방식으로 저장하나요? draft(현재 선택한 위지윅 에디터)공식 문서에서는 JSON, HTML, Markdown 3가지 형식의 데이터를 지원해주는 것으로 알고 있습니다.

  • CORS 에러 처리 방법 (BE와 동일)

  • 현업에서 CSS 라이브러리가 어떤 추세인지 궁금합니다.

    기존에 정적인 CSS의 한계를 극복하기 위해서 runtime 개념을 도입한 css-in-js가 대세였으나, 최근에 빌드 시간을 개선하기 위한 목적으로 다시 zero-runtime css-in-js, atomic css가 부상하고 있는 걸로 알고 있습니다. 현업에서도 emotion, styled-component가 저물고 tailwind, linaria 등의 라이브러리가 인기를 얻고 있나요?

  • 앞으로의 스프린트에서 아래의 기능을 구현해보고자 하고 있습니다. 해당 기능 외에도 다뤄보기를 추천하는 프론트엔드 기술이 있을까요?

    • OAuth & PWA & SSE(예상)
  • 그 외의 프로젝트 전반 진행 상황과 관련하여 신경써봐야 할 것, 개선해야 할 점에 대해 말씀해 주시면 감사하겠습니다 ☺️

BE

이번 주 한 일

PR 리스트

개발 일지

질문 목록

  • 테스트 코드를 method에 대해 작성해보았는데요. 테스트 코드 작성이 저한텐 좀 어렵습니다. 상황을 설명 드리자면 어떤 메서드가 3개의 예외를 처리한다고 했을 때 성공/실패 경우가 총 4개지 나옵니다. 이 4가지에 대해서만 코드를 작성하면 되는 것인지 궁금합니다. 테스트 코드를 작성하는데 시간이 좀 많이 걸려서 성공하는 경우만 테스트하고 있습니다. 이 부분에 대해서 생각이 좀 많았는데요, 좀 복잡한 로직을 포함하는 메서드에 대해서만 테스트코드를 작성하려고 합니다. 해당 기능을 테스트코드까지 작성하기엔 정해진 시간이 너무 촉박합니다.

    • 질문리스트 필터링/정렬 테스트 코드

      it('should filter by tag', async () => {
          const options: QuestionListOptionsDto = {
            tag: getRandomElement(tags),
          };
      
          const filteredQuestions = mockQuestions.filter(
            (question) => question.tag === options.tag,
          );
          jest
            .spyOn(service, 'readQuestionList')
            .mockResolvedValue(filteredQuestions);
      
          const result = await service.readQuestionList(options);
          expect(result).toEqual(filteredQuestions);
        });
      
        it('should filter by programming language', async () => {
          const options: QuestionListOptionsDto = {
            programmingLanguage: getRandomElement(programmingLanguages),
          };
      
          const filteredQuestions = mockQuestions.filter(
            (question) =>
              question.programmingLanguage === options.programmingLanguage,
          );
          jest
            .spyOn(service, 'readQuestionList')
            .mockResolvedValue(filteredQuestions);
      
          const result = await service.readQuestionList(options);
          expect(result).toEqual(filteredQuestions);
        });
      
        it('should filter by isAdopted', async () => {
          const options: QuestionListOptionsDto = {
            isAdopted: Boolean(getRandomInt(0, 1)),
          };
      
          const filteredQuestions = mockQuestions.filter(
            (question) => question.isAdopted === options.isAdopted,
          );
          jest
            .spyOn(service, 'readQuestionList')
            .mockResolvedValue(filteredQuestions);
      
          const result = await service.readQuestionList(options);
          expect(result).toEqual(filteredQuestions);
        });
      
        it('should filter by tag, programming language, and isAdopted', async () => {
          const options: QuestionListOptionsDto = {
            tag: getRandomElement(tags),
            programmingLanguage: getRandomElement(programmingLanguages),
            isAdopted: Boolean(getRandomInt(0, 1)),
          };
      
          const filteredQuestions = mockQuestions.filter(
            (question) =>
              question.tag === options.tag &&
              question.programmingLanguage === options.programmingLanguage &&
              question.isAdopted === options.isAdopted,
          );
          jest
            .spyOn(service, 'readQuestionList')
            .mockResolvedValue(filteredQuestions);
      
          const result = await service.readQuestionList(options);
          expect(result).toEqual(filteredQuestions);
        });
      
        it('should filter by tag, programming language, and isAdopted and should sort by createdAt, viewCount, and likeCount', async () => {
          const options: QuestionListOptionsDto = {
            tag: getRandomElement(tags),
            programmingLanguage: getRandomElement(programmingLanguages),
            isAdopted: Boolean(getRandomInt(0, 1)),
            sortByCreatedAt: 'asc',
            sortByViewCount: 'desc',
            sortByLikeCount: 'asc',
          };
      
          const filteredQuestions = mockQuestions
            .filter(
              (question) =>
                question.tag === options.tag &&
                question.programmingLanguage === options.programmingLanguage &&
                question.isAdopted === options.isAdopted,
            )
            .sort((a, b) => {
              if (options.sortByCreatedAt === 'asc') {
                return a.createdAt.getTime() - b.createdAt.getTime();
              } else if (options.sortByCreatedAt === 'desc') {
                return b.createdAt.getTime() - a.createdAt.getTime();
              }
              return 0;
            })
            .sort((a, b) => {
              if (options.sortByViewCount === 'asc') {
                return a.viewCount - b.viewCount;
              } else if (options.sortByViewCount === 'desc') {
                return b.viewCount - a.viewCount;
              }
              return 0;
            })
            .sort((a, b) => {
              if (options.sortByLikeCount === 'asc') {
                return a.likeCount - b.likeCount;
              } else if (options.sortByLikeCount === 'desc') {
                return b.likeCount - a.likeCount;
              }
              return 0;
            });
      
          jest
            .spyOn(service, 'readQuestionList')
            .mockResolvedValue(filteredQuestions);
      
          const result = await service.readQuestionList(options);
          expect(result).toEqual(filteredQuestions);
        });
      
  • 조회수 증가 로직을 어떻게 구현해야 할 지 모르겠습니다.

    • 인터셉터를 활용하여 특정 endpoint 요청을 가로채고 이 인터셉터에서 조회수를 증가시킬 수 있는 service 계층을 활용하여 증가시키는게 맞는지 궁금합니다.
    • 위의 경우 GET요청을 가로채서 증가시키는건데 GET의 멱등성이 깨지게 되는 상황이고 멱등성은 깨지면 안된다고 알고 있습니다.
    • 동일 사용자가 특정 게시글을 계속 누르는 경우 조회수가 계속 상승하는데 이런 상황을 막기 위해 ip주소, 쿠키 등을 활용하는 것으로 알고 있는데 각 방법은 장단점이 있습니다. 이런 경우 두가지 중 하나만 선택을 해서 대응을 해야하는지 궁금합니다.
  • 이미 FE와 연결된 API가 있다고 할 때 BE에서 이 API를 수정하면 현업에서는 어떤 방식으로 FE에게 알려주는지 궁금합니다.

    • 카카오톡: 편하긴한데 기록이 안남음(목차(인덱스)같은게 없어서)
    • 노션: 목차(인덱스)가 있지만 노션에 들어가지 않으면 알림을 볼 수 없음
  • CORS는 어디서 처리해주는게 좋은지 궁금합니다.

    • 왜 FE/BE에서 처리했는지 누가 물어본다면 답을 못할 것 같습니다.

✔️ 멘토링 내용

멘토링 시간에 나눈 이야기가 휘발되지 않도록 기록해보세요.

  • unit test는 input에 대한 예상되는 output이 도출되는지 확인하는 것임
    • mocking을 활용하여 output이 잘 나오는지만 확인
    • 실제 어떤 값을 활용하여 test할 필요 없음
    • mock data를 만들 필요 없음
  • 조회수 증가 관련 로직은 해당 프로젝트의 스펙을 정해놓고 진행해야함
    • 이를테면 쇼핑몰 같은 사이트는 조회수가 중요함 따라서 조회수 관련 로직을 정교하게 작성해야함
    • 특정 사용자가 하루에 한번만 특정 페이지의 조회수를 올릴 수 있을 것인지 아니면 여러 번 올릴 수 있을 것인지도 정해야 함
    • 이를 정할 때에는 user의 id, IP, 쿠키 등의 정보를 활용할 수 있음
    • 한 명의 사용자가 여러 device를 사용할 때는 조회 수를 어떻게 변경할 지 정해야 함
    • 클로바노트는 조회할 때 마다 조회 수를 증가시킴
    • 처음부터 완벽하게 만들려고 하는 생각 때문에 병목이 생긴 것임
    • 일단 해당 기능을 만들어 놓고 스펙 업을 시키는 과정이 필요함
  • API 명세 변경점에 대한 알림
    • Release Note를 활용하여 이전 버전과 달라지는 점이 무엇인지 명시해두기 (change 수를 명시)
    • Swagger를 활용하여 실 서버에 배포된 API 명세 알려주기
    • 기존 API와의 호환을 위해 변경 전/후 API를 일정 기간 동안 제공하다가 변경 전 API는 특정 release에서 삭제하기
  • CORS 에러
    • BE에서 해결 안해주는 경우 FE에서는 BFF를 활용할 수 있음
    • 개발 phase에 따라 여러 서버를 가질 수 있는데 개발 or 테스트 페이지에 대해서는 CORS를 모두 허용해주기도 함
  • Release 주기
    • 클로바 노트는 2주 단위로 1년 동안 release 수행
    • 6주 동안 진행되는 프로젝트는 매주 release 찍어도 됨
  • FE에서 CORS 해결
    • FE도 로컬 환경에서 HTTPS 환경을 세팅하는 것을 연습해야함
    • Next.js를 통해 인증서를 넣는 방법이 있음
    • Express.js를 통해 proxy를 할 수도 있음
    • nginx나 apache로 virutal hosting을 통해 해결할 수도 있음
    • nginx나 apache는 local의 docker에서 쉽게 띄울 수 있음
    • 프로젝트마다 CORS 이슈는 생길 수 밖에 없음 이에 대해 유연하게 대처하는 방법을 알고 수행할 수 있어야 함
    • 클로바 노트는 docker에서 nginx를 띄우고 nginx에서 serving하는 방식 사용
    • docker config 또한 github에 올려서 관리함
  • 폼 관리 라이브러리
    • 폼 관리 라이브러리는 여러가지가 있음
    • 버그 없고 유지보수를 계속 해주는 라이브러리면 써도 됨
    • 굳이 안쓰고 만들 수 있다면 만들어도 됨
    • 라이센스가 MIT인지를 확인해야함
    • BSD와 같은 라이센스는 사용할 수가 없을 수도 있음
      • 상업적으로 이용할 경우 자신의 코드를 모두 오픈 해야하기 때문임
  • 문서에디터로 드래프트 라이버리를 사용
    • 페이스북에서 만든 것임
    • 문제가 많음
    • 위직, CK에디터 사용하는 것이 더 나음
    • 드래프트는 커스텀을 하기 어려움 커스텀 시도시 무수히 많은 에러 발생
    • 에디터를 공부하고 싶으면 프로즈 미러 사용해보기
  • zero runtime css
    • 어떤 라이브러리가 인기를 얻든 트렌드일 뿐 답은 아님
    • 트렌드에 맞춰 하면 됨
    • CSS면 CSS에서 처리해야지 JS에서 처리하면 오류가 발생할 수도 있음
    • 대기업은 mark up 개발자가 따로 존재함
    • 혼자 CSS랑 JS 다 하면 styled component가 best임
    • 프로젝트 스펙에 따라 결정할 것
  • OAuth
    • 웹은 무조건 쿠키를 구워야함
    • 로컬 스토리지는 해킹 이슈가 있을 수도 있음
  • PWA
    • 지금 진행하기에는 애매할 수도 있음
    • 프로그래시브 웹 앱이라는 것임
    • 웹을 앱처럼 쓰겠다는 개념
    • 주소창을 숨길수도 있고 오프라인에서 가능하도록 오프라인 기능도 만들어야함
    • 오프라인 기능을 만드려면 로컬 스토리지에 저장한다든지 아니면 캐시 베이스로 한다든지 전략을 세워야하는데 배보다 배꼽이 더 큰 상황
  • SSE
    • FCM이 SSE와 같은 기능임
    • 제일 잘 쓰는 곳이 chat GPT임
    • 응답이 끝날 때 까지 SSE로 서버에서 실시간으로 내려주다 응답이 끝나면 서버가 연결을 끊음. 이와 비슷한 상황에서 사용하는 것이 best