레벨2 장바구니 - step2

개요

미션
기간
Repository
PR & Review
github pages
storybook

장바구니 1단계

23-05-15 - 23-05-22

미션회고

다음은 장바구니 미션 2단계의 필수 요구 사항이다.

  1. 장바구니 페이지

    • 장바구니 페이지 마크업을 완성하고, 상품 목록 페이지와 함께 모바일 환경 대응

  2. MSW를 활용한 API Mocking

    • MSW를 활용하여 실제 서버와 연동될 수 있는 API Mocking을 구현

    • 단순한 Endpoint 변경으로 실제 API 사용이 가능하도록 작업

  3. 테스트

    • 장바구니 페이지에서 다양한 사용자 인터렉션에 대한 테스트 케이스를 고민하고, 선택한 도구를 이용하여 검증

  4. 사용자 경험

    • 새로고침 해도, 장바구니에 담은 상품 유지

이전 미션에서 장바구니 페이지가 추가되었다. 장바구니 페이지에선 다음과 같은 기능이 필요했다.

  • 전체 선택 / 해제

  • 전체 삭제 / 선택 삭제

  • 상품 수량 증감

  • 결제 예상 금액

기능을 구현하는 것과 새로고침 해도 장바구니 상품이 유지되는 것을 구현하는 것엔 큰 어려움이 없었다. 다만, 내가 과연 효율적으로 상태를 관리를 하였고 재사용 가능한 컴포넌트들을 나누어 사용을 했는지가 큰 관건이었다.

스토리북도 이제 슬슬 손에 익어가는 느낌이 든다. 단, 하고 있는 것만 계속한다는 가정하에... 작은 단위의 컴포넌트를 우선 생각하여 이를 먼저 컴포넌트로 만들고 다양한 상황을 생각하는 연습을 꾸준히 하고 있다. 이 부분은 레벨 2에서 가장 많이 연습한 부분이고 처음 스토리북을 접했을 때 보다 많이 익숙해졌다고 할 수 있다. 하지만 아직 인터렉션은 정복하지 못한 것 중 하나이다.

MSW라는 것을 장바구니 2단계에서 처음 사용해 보았다. 이는 바로 아래의 챕터에서 조금 더 자세히 다룬다.


MSW(mock service worker)

앞으로 서비스를 만들게 된다면 서버는 당연히 필수이다. 하지만 서버가 아직 미완성이라면 어떡할까? 우린 가짜 데이터인 mockData를 사용할 것이다. 하지만 mockData는 비동기로 가져오는 것이 아니라 그냥 파일을 import를 사용하기 때문에 서버 통신과는 거리가 멀다.

보다 서버 통신과 비슷하게 mocking을 할 수 없을까? 이때 유용한 도구가 바로 MSW이다. MSW를 통해 백엔드 없이 요청, 응답을 도입할 수 있다.

그냥 단순히 mockData만 이용하면 되는데 왜, 더 복잡한 비동기 통신을 다루기 위해 MSW를 사용하는 것일까? 이유는 다음과 같다.

  1. 협업 관점에서 백엔드와의 의존성 없이 개발을 해나가기 위해서

  2. 백엔드에서 클라이언트로써 API에 대한 구체적인 피드백을 주기 위해서

  3. 클라이언트 개발자 입장에서도, 1개의 요청을 하고 응답받는 과정에서 발생할 수 있는 다양한 상황을 시뮬레이션을 하기 위해서

  4. MSW를 이용하면, API 요청을 하고 응답을 받는 시간을 조절할 수 있어서

이렇게 유용한 MSW를 사용하지 않을 이유는 없다. 스토리북에 이어 레벨 2에서 MSW이라는 프런트엔드에게 훌륭한 도구를 알 수 있었다.

단순히 API 요청을 대신하는 것을 넘어, 실제 서버와 요청할 때 생길 수 있는 다양한 상황을 재현해 보는 것이 중요하다. 즉, 서버에서 받은 응답에 400번대, 500번대 에러가 있다면 이에 대한 대응도 생각해 보고, 통신 시간이 긴 경우에도 어떻게 대응을 해야 하는지 다양한 상황을 생각해 보는 것이 중요하다.

실제 서버와 연동하기 전 단계라고 생각하면 고려해야 할 점이 많은 것이 당연하다.

황펭이 작성한 MSW 동작 방식에 대한 프롤로그를 작성하여 이를 남기며 MSW에 대한 첫 인상을 마무리한다.


Promise 상태에 따른 UI

이번 미션에서는 Fetch API와 MSW를 이용하여 마치 서버가 존재하는 것처럼 비동기 통신을 하였다. Fetch API가 제공하는 전역 fetch() 메서드로 네트워크의 리소스를 쉽게 비동기적으로 취득을 할 수 있는데, 이때 Prmoise 객체를 반환하기 때문에 3가지 상태에 따른 UI를 만들어야 할 필요가 있었다. 3가지 상태는 다음과 같다.

  • pending

  • fulfilled

  • rejected

이런 3가지의 상태에 따라 사용자에게 보여줄 UI는 달라야 한다. 상황에 알맞은 UI는 사용자 경험을 크게 향상시켜 줄 것이다.

이를 위해 useFetch라는 훅을 만들어 비동기 통신에 필요한 로직과 상태를 만들었다. 로직은 당연히 fetch하는 로직이고 상태는 3가지를 가지고 있다.

1. fetch를 통해 서버에서 가져오는 data

2. 서버와 비동기 통신을 하면서 가질 수 있는 3가지 상태를 관리하는 status

3. 만약 rejected일 때, 오류메시지가 필요하므로 이를 위한 errorMessage

코드는 다음과 같다.

const [data, setData] = useState<T>();
const [status, setStatus] = useState<'pending' | 'fulfilled' | 'rejected'>(
  'loading'
);
const [errorMessage, setErrorMessage] = useState<string | null>(null);

그렇다면 이를 사용하고 있는 부분은 어떻게 될까? 현재 미션에서는 useCartItems이라는 훅에서 사용된다. 해당 훅은 장바구니 상태를 관리하는 훅이다. 해당 훅에서는 다음과 같이 useFetch훅을 사용하여 장바구니 상품을 서버에서 불러온다.

const { data, status, errorMessage, fetchData } = useFetch<CartItemType[]>(
  FETCH_URL.cartItems
);

모두 중요하지만 UI에 필요한 상태는 바로 status이다. status 상태에 따라 리액트 컴포넌트에서는 서로 다른 UI를 렌더링을 해야 한다. 마지막으로 실제 useCartItems훅을 사용하는 리액트 컴포넌트를 살펴보자.

function CartList() {
  // ...
  const { cartItems, status, errorMessage, updateCartItems } = useCartItems();

  if (status === 'rejected') {
    // 서버와의 통신이 실패했을 경우
  }

  if (status === 'pending') {
    // 서버와 통신중일 경우
  }

  // 서버와 통신을 성공적으로 마무리 했을 경우
}

위와 같이 나누어 각각 다른 UI를 렌더링을 할 수 있다.

결국 서버 통신을 하는 경우 여러 상태에 따른 UI를 만들어 렌더링을 했지만 여기서 한 가지 더 생각해볼 점은 계층으로 분리를 했다는 것이다.

1. useFetch: 서버와 직접 통신을 하는 역할

2. useCartItems: 클라이언트와 서버의 통신 연결해 주는 중간 역할(GET 뿐 아니라 수정, 삭제에 관한 로직이 있을 수 있다.)

3. CartList(리액트 컴포넌트): 통신 결과를 바탕으로 UI를 렌더링 하는 역할


아쉬운 점은 타입을 조금 더 빡빡하게 잡지 않았다는 것이다. 위의 CartList를 보면 cartItems은 status가 fulfilled일 때만 존재한다. 때문에 모든 조건문을 통과한다면 cartItems는 무조건 있는 것이 당연하다. 하지만 타입상에선 null일 수 있기 때문에 한 번 더 있는지 검사를 해야 한다. 이러한 점을 해결했으면 더 좋았을 것이다.


👨‍💻 PR Reveiw

2단계 미션은 백엔드 서버를 붙이기 전 MSW를 이용하여 모킹 하는 것이 큰 포인트였다. 때문에 크론에게 비동기 피드백을 많이 받았다. 피드백을 통해 내가 놓치고 있는 것이 무엇인지 알 수 있어 좋았다.


예외 처리를 유연하게 하기

서버와 통신을 하게 된다면 의도치 않는 서버 오류, 클라이언트 오류로 인해 원하는 동작을 하지 않을 때가 있다. 이때 다양한 상태 코드를 사용하여 서버와 클라이언트가 서로 소통을 하는데 그중 가장 유명한 상태코드는 바로 404일 것이다.

때문에 응답 상태가 404일 때, 다음과 같이 에러를 발생시키도록 하였다.

if (res.status === 404) throw new Error('Not Found');

하지만 이외의 응답 상태에 따른 예외 처리는 하지 않았다. 과연 서비스에서 404 이외의 상태코드는 받지 않을까? 그렇지 않다. 때문에 리팩터링을 통해 다양한 상태 코드에 따른 예외 처리를 실시하였다.

이를 위해, 어떤 상태 코드가 있고 언제 발생하는지를 알아야 하기 때문에 이를 알아보았다. MDN의 HTTP 상태 코드를 참고하였다.

[HTTP 상태 코드 - HTTP | MDN

HTTP 응답 상태 코드는 특정 HTTP 요청이 성공적으로 완료되었는지 알려줍니다. 응답은 5개의 그룹으로 나누어집니다: 정보를 제공하는 응답, 성공적인 응답, 리다이렉트, 클라이언트 에러, 그리고

developer.mozilla.org](https://developer.mozilla.org/ko/docs/Web/HTTP/Status)

클라이언트 에러 응답

400번대 상태코드는 클라이언트 에러 응답이다. 즉, 클라이언트에서 잘못된 요청을 보내는 경우이다. 다음은 기존 상태코드와 더불어 리팩터링에 추가한 상태 코드는 다음과 같다.

  • 400: Bad Request, 잘못된 문법으로 인하여 서버가 요청을 이해할 수 없음을 의미

  • 401: Unauthorized, 비인증을 의미, 클라이언트는 요청한 응답을 받기 위해서는 반드시 스스로 인증을 해야 함

  • 403: Forbidden, 클라이언트는 콘텐츠에 접근할 권리를 가지고 있지 않음을 의미, 401과 다른 점은 서버는 클라이언트가 누구인지 알고 있음

  • 404: Not Found, 요청받은 리소스를 찾을 수 없음을 의미, 브라우저에서는 알려지지 않은 URL를 의미

서버 에러 응답

500번대 상태코드는 서버 에러 응답이다. 즉, 서버에서 무언가가 잘못되었다고 생각하면 된다. 이번 리팩터링을 통해 500번 상태코드를 추가하였는데 500번 상태코드(Internal Server Error)의 의미는 "서버가 처리 방법을 모르는 상황"을 뜻한다. 즉, 처리할 수 없는 내부 오류가 발생했다는 의미이다.

그 외의 참고할 만한 서버 에러 응답 상태 코드는 다음과 같다.

  • 501: Not Implemented, 요청 방법은 서버에서 지원되지 않으므로 처리할 수 없음을 의미한다.

  • 503: Service Unavailable, 서버가 요청을 처리할 준비가 되지 않았음을 의미한다. 일반적인 원인은 유지보수를 위해 작동이 중단되거나 과부하가 걸렸을 때이다.

과연 모든 상태 코드에 대한 예외 처리를 해줘야 하는가?

MDN HTTP 상태 코드 문서를 살펴보면 404번 상태 코드에 다음과 같은 설명이 적혀있다.

서버들은 인증받지 않은 클라이언트로부터 리소스를 숨기기 위해 이 응답을 403 대신에 전송할 수도 있습니다.

이는 무엇을 뜻할까? 상태 코드에 의하면 로그인을 하지 않는 사용자에겐 401번 상태 코드에 대한 예외 처리인가되지 않는 사용자에겐 403번 상태 코드에 대한 예외 처리를 하는 것이 맞다. 하지만 무조건적인 친절한 UI 보다는 서비스에 맞는 예외 처리를 하는 것이 좋다. 즉, 적당히 가릴 건 가려서 사용자에게 알려줘야 한다. 깃허브도 로그인하지 않은 사용자에겐 404 상태 코드를 보여준다.


HTTP 메서드 더 살펴보기

리뷰를 통해 아직 HTTP 메서드에 대한 지식이 부족하다는 것을 깨닫게 되었다. 크론이 코드 리뷰를 통해 여러 질문을 해주셨고 이를 찾아보며 댓글을 남겼다. 하지만 여러 곳에 분산이 되어있기 때문에 이를 정리해 볼 필요가 있다고 생각했다.

서버와 통신하기 위해서 적절한 HTTP 메서드를 사용해야 한다. 물론 HTTP 메서드에 더 빠삭한 백엔드 팀원들과 함께 이야기하며 명세서를 만들겠지만 그래도 원활한 소통을 위해선 HTTP 메서드엔 무엇이 있으며 어떤 특징을 가지고 있는지 알고 있어야 한다. 다음은 이번 미션을 진행하면서 학습한 HTTP 메서드에 관한 내용이다. MDN문서를 참고하였다.

멱등성

멱등성은 HTTP 메서드를 살펴보기 전, 짚고 넘어가야 할 개념이라고 할 수 있다. MDN에서 설명하는 멱등성은 다음과 같다.

동일한 요청을 한 번 보내는 것과 여러 번 연속으로 보내는 것이 같은 효과를 지니고, 서버의 상태도 동일하게 남을 때, 해당 HTTP 메서드가 멱등성을 가졌다고 말한다.

올바르게 구현을 했다면 멱등성을 가진 HTTP 메서드는 GET, PUT, DELETE가 있고 멱등성을 가지지 않는 HTTP 메서드는 POST이다.

멱등성을 따질 땐 실제 서버의 백엔드 상태만 보면 된다. 즉, 응답 코드는 다를 수 있다. DELETE 메서드를 살펴보면 이해가 된다.

처음 어떤 게시글을 삭제한다고 가정해 보자. DELETE 메서드를 통해 게시글을 삭제하면 해당 게시글은 DB에서 삭제된다. 그 이후 다시 똑같은 DELETE 메서드를 요청하게 되면 어떻게 될까? 지울 수 있는 게시글은 없기 때문에 DB는 변하지 않는다. 하지만 상태코드는 이전과 다르게 잘못된 요청을 했기 때문에 클라이언트 에러 응답에 대한 코드일 것이다.

하지만 만약 DELETE 메서드를 요청할 때마다 게시글이 계속 지워지게 된다면 이는 멱등성을 위배하는 것이기 때문에 잘못된 구현이라고 할 수 있다.

GET

HTTP GET 메서드는 특정한 리소스를 가져오도록 요청한다. GET 요청은 데이터를 가져올 때만 사용해야 하고 멱등성을 가지고 있다. 즉, 여러 번 호출해도 클라이언트가 받는 응답은 동일하다.

POST

HTTP POST 메서드는 서버로 데이터를 전송한다. 전송된 데이터를 바탕으로 변경사항을 만든다. 예를 들어 게시글을 새롭게 만들 때, POST 메서드를 통해 게시글의 데이터를 헤더의 Content-Type에 포함시켜 보내게 된다. 매번 요청할 때마다 새로운 게실글이 생성되기 때문에 POST 메서드는 멱등성을 가지고 있지 않다.

PUT

HTTP PUT 메서드는 새로운 리소스를 생성하거나, 대상 리소스를 나타내는 데이터를 대체한다. 즉, 두 가지의 기능을 수행한다고 볼 수 있다.

  1. 대상 리소스가 없는 경우: 새롭게 하나를 생성한다. 이때, 클라이언트에 201 응답을 보내야 한다.

  2. 대상 리소스가 있는 경우: 요청받은 데이터를 바탕으로 리소스를 수정한다. 이때, 200 또는 204 응답을 보내야 한다.

PUT 메서드는 POST 메서드처럼 새로운 리소스를 생성할 때 사용되지만 멱등성에서는 차이가 있다. POST 메서드는 계속 여러 번 리소스를 생성하여 멱등성을 가지지 않지만 PUT 메서드는 이미 리소스가 있다면, 여러 번을 연속으로 보내어도 리로스를 추가로 생성하지 않기 때문에 멱등성을 가진다.

주의할 점은 이미 대상 리소스가 있는 경우, 모든 자원을 보내지 않을 경우 해당 리소스는 초기값 또는 Null이 된다. 때문에 PUT 메서드를 사용할 땐, 리소스에 대한 모든 자원을 넘겨야 한다. 이는 다음으로 살펴볼 PATCH 메서드와 다른 점이다.

PATCH

HTTP PATCH 메서드는 리소스의 부분적인 수정을 할 때에 사용된다. 이는 HTTP PUT 메서드와 다른 점이다. 즉, 필요한 모든 자원을 넘길 필요가 없는 것이다(수정을 해야 하는 자원만 넘기면 된다.)

이런 PATCH 메서드는 멱등성을 가질 수도 그렇지 않을 수도 있다. 두 개의 예시를 통해 멱등성을 가질 때와 그렇지 않을 때를 비교해 보자. 김영한님의 어떤 강의의 댓글을 참고하였다.

  1. 멱등성을 가질 때: {name: "kim"} 여러 번 요청을 해도 같은 결과이다.

  2. 멱등성을 가지지 않을 때: {"operantion": "add", "age": 10} 요청을 할 때마다 age가 계속 10씩 증가한다.

DELETE

HTTP DELETE 메서드는 지정한 리소스를 삭제한다. 이는 위에서 살펴본 것과 같이 멱등성을 가지며 만약 멱등성을 가지지 않는다면 이는 잘못 설계했다고 할 수 있다.

DELETE 메서드를 성공적으로 적용한 후에 사용할 수 있는 응답 상태 코드를 다음과 같이 몇 가지가 있다.

  1. 202: 명령을 성공적으로 수행할 것 같으나 아직 실행하지 않는 경우

  2. 204: 명령을 수행했고 더 이상 제공할 정보가 없는 경우

  3. 200: 명령을 수행했고 응답 메시지가 이후의 상태를 설명하는 경우


👍 잘한 점

  1. 비동기를 다룰 때 발생할 수 있는 3가지 상태에 따른 UI를 만들었다.

  2. MSW를 활용하여 발생할 수 있는 다양한 상황에 대해 대응하고자 노력했다.

👎 아쉬운 점

  1. 시간이 부족하여 비동기 피드백 외의 것에 대해 깊게 살펴보지 못했다.

  2. 서버에서 받아온 데이터를 바탕으로 상태를 다루는 것이 아직 뜬구름으로 느껴진다.

👊 앞으로의 다짐

  1. 모르는 건 상관없지만 모르지만 안다고 하지 말 것


📅 2023-06-06

Last updated