[토이 프로젝트] 코인원 클론코딩 후기

LESA·2023년 5월 8일
0
post-thumbnail

만들어 보고 싶었던 코인 사이트 토이 프로젝트를 완성했다.

개발하면서 느낀점과 무엇이 부족했는지에 대하여 4L(Liked, Learned, Lacked, Longed for) 형식에 맞춰 후기를 작성해보려한다.


💕 Liked, 좋았던 것

강의 없이 직접 만들어보고 고뇌하며 코딩에 익숙해지는 느낌이 너무좋았다.

예를 들어 이론으로만 접했던 CORS, 성능최적화, WebSocket API 등을 실제로 구현해보고 프로젝트에 녹였다는 점이다.

토이 프로젝트지만 강의없이 기획부터 배포까지 나만의 웹 사이트를 보며 보람을 많이 느꼈다.

이번 프로젝트를 개발하며 평소에 눈여겨 보던 라이브러리들을 사용하고 정답은 아닐 수 도 있지만

내가 생각한대로 커스터마이징 해본 경험이 소중했다.



💾 Learned, 배운 것

클론코딩에 대한 강의는 너무 많다.

강의하나만 있으면 누구나 그럴듯한 결과물을 만들어낼 수 있다.

스스로 생각하며 어떤 라이브러리를 사용할지, 상태관리를 어떻게 관리할지, 재활용 가능한 부분이 있는지 등

이외에도 여러 부분이 있지만, 개발하면서 이건 정리해야겠다고 생각이든 부분만 블로그에 남겨보고자한다.


🅰️ WebSocket API

  • 대한민국에서 유명한 거래소하면 생각나는 곳은 Upbit 혹은 Coinone 이다.
  • 두 거래소 모두 open API를 제공하지만 Upbit 에서 제공하는 문서가 넘사벽으로 친절하여 이를 사용하기로했다.
  • REST API 와 WebSocket API 의 차이를 인지하며, 어느 시점에서 어떤 API 선택할지 생각했다.
    나는 실시간으로 제공해야되는 데이터들이 필요했기에 계속 열려있는 WebSocket 을 선택하여 데이터를 받아왔다.

여기서 대부분의 시간을 쏟아부었다.

처음엔 WebSocket API 라는 개념이 없었고, REST API 를 시간 텀을 주고 계속 요청하여 리렌더링 하는 형태로 개발했었다.

하지만 완성하고나니 내가 원하는 결과물과 너무 달랐고 많은 검색과 예시를 보며 WebSocket API 로 리팩토링 하였다.

아래에 이전의 코드가 어떻게 바뀌었는지 간략하게 코드를 첨부한다.

// REST API 를 사용한 데이터 Fetching 함수들

export function getMarketCoins() {
    return fetch('https://api.upbit.com/v1/market/all').then((res) => res.json());
}

export function getBtcAccPrice() {
    return fetch('https://api.upbit.com/v1/ticker?markets=KRW-BTC').then((res) => res.json());
}

export function getDetailCoin(coin: string) {
    if (coin) {
        return fetch(`https://api.upbit.com/v1/ticker?markets=${coin}`).then((res) => res.json());
    } else return null;
}

너무 안일하게 생각했던 코드였고 간단히 데이터를 받아 실시간 처럼 보이게 하려는 속임수를 사용했다.

하지만 Upbit 에서 제공하는 WebSocket API 가 있었다.

아래의 코드는 리팩토링된 부분이다.

useEffect(() => {
    try {
      if (!ws.current) {
        ws.current = new WebSocket(UPBIT_WS_URL);
        ws.current.binaryType = BINARY_TYPE;

        // open
        const openHandler = () => {
          const sendField = [
            { ticket: "coinzero" },
            {
              type: "ticker",
              codes: searchCoin.map((code: any) => code.market),
            },
          ];

          ws.current.send(JSON.stringify(sendField));
        };

        // close
        const closeHandler = () => {
          setBufferData([]);
          setWsData([]);
          bf.current = [];
        };

        // msg
        const msgHandler = (e: any) => {
          const wsData = dataEncoder(e.data);
          if (wsData) bf.current.push(wsData);
          throttled();
        };

        ws.current.onopen = openHandler;
        ws.current.onclose = closeHandler;
        ws.current.onmessage = msgHandler;
      }

      return () => {
        if (ws.current) {
          ws.current.close();
          ws.current = null;
        }
      };
    } catch (error) {
      console.error(error);
    }
  }, [searchCoin]);

기본적으로 Recoil 에서 관리되는 searchCoin 을 바라보고있고 사용자가 코인을 검색할 때

searchCoin 의 값을 변경하여 체결량, 시세, 차트 등의 데이터가 반환되는 값으로 나타난다.

1초에 몇백개의 데이터가 들어오다보니 사이트가 멈추거나 수많은 리렌더링이 일어났다.

버퍼를 사용하여 들어온 데이터를 버퍼에 쌓아두고 데이터를 내보내는 형태로 개발됐다.

제일 힘들었던 부분이 이부분이었다. 검색되는 해결방안이 거의 없었고

Open Library 를 통하여 직접 코드를 분석하여 참고했다.


🅱️ Skeleton UI

  • 데이터가 fetching 중일 때, Loading bar 를 보통 사용해서 시각적으로 사용자에게 편의를 제공한다.

  • 나는 처음에 Spinner 형태로 로딩 진행 상황을 표시했지만, 유튜브와 당근마켓 어플을 사용하다가 좋은 예시를 적용해봤다.

  • Youtube-shorts 의 skelton UI

쇼츠를 보다 이런 화면을 많이보았고 자주 사용하던 당근마켓 앱도 이와 비슷한 UI 로 로딩을 관리하였다.

훨씬 더 세련하고 어느 위치에 데이터가 표현될지 예측할 수 있는 장점이 있는것 같아서 내 프로젝트에 녹여보기로했다.

  • 내 프로젝트에 적용된 Skeleton UI

🅾️ 모르는 라이브러리 사용에대해 겁내지않기

  • 유명한 라이브러리들에 대한 사용법들은 너무 잘 정리되어있다.
  • 코인 차트 그래프를 사용하기위해 두 가지 라이브러리를 사용했다.
    - chart.js, klinecharts
    chart.js 는 주간 다운로드수가 260만에 달하는 인기 라이브러리이고 klinecharts 는 겨우 60회이다.
    candle graph 가 아닌 아래의 체결 볼륨까지 표현하고 싶은 와중 klinecharts 라는 라이브러리를 발견해서 적용했다.
  • 프로젝트에 적용된 klinecharts 의 indicator 모델

비교적 최신 라이브러리이지만 공식문서를 통해 그래프의 styling 까지 적용시켰다.

거래소 클론코딩글을 찾아보면 실제 거래소에서 구현되어있는 미니차트들은 구현되어있지않았다.

나는 이부분까지 구현하기위해 chart.js 를 사용하여 미니차트를 구현했다.

  • chart.js 를 사용한 미니차트
    - 데이터를 받아올때 상승(RISE), 보합(EVEN), 하락(FALL) 을 구분하여 코인을 선택했을때 아래와같이 변화한다.

💀 Lacked, 부족했던 것

  • 전체적으로 부족한게 많았지만 그 중에서도 몇가지를 뽑아보자면 리렌더링, 성능최적화, 재사용 높이기 등이 있다.

✅ 리렌더링, 성능최적화

  • WebSocket API 로 부터 매초 수백개의 데이터가 들어온다. 그럼 수백번의 리렌더링이 일어나야할까 ?
  • 실시간 데이터와 통신을한다는것은 그만큼 사용자에게 보여지는 화면이 바뀐다는 것을 의미한다.
    • 수백번은 아니지만 수많은 리렌더링이 일어난다.
    • 렌더링 시키는 컴포넌트 안에서 데이터를 fetching 하면 안된다.

처음엔 코인 정보, 차트, 체결정보 등 각각의 컴포넌트에서 불러오는 형태로 개발을 했다.

로컬 환경에서 Loading Spinner 를 통해 대기화면을 만들고 해당 작업을 하니 어느정도 참아줄만한 시간이었다.

하지만 컴포넌트들이 늘어나며 대기시간이 비약적으로 길어져 잘못됐음을 깨닳았다.

  • 어떻게 대기시간을 줄이고 리렌더링을 최소화 했나 ?
    • 재사용 가능한 data 를 컴포넌트에 공통으로 props 뿌려주기
    • 버퍼(buffer) 를 사용하여 버퍼에 데이터를 쌓아 보내고 보낸 데이터를 비우는 메커니즘 반복

서비스 사용자들에게 제공되는 코인 정보, 체결량, 차트 등의 컴포넌트들을 감싸는 부모 컴포넌트를 생성한다.

부모 컴포넌트에서 data fetching 이 이루어지고 받은 데이터를 자식 컴포넌트에게 넘겨주는 형태로 개발했다.

// 코인, 차트, 실시간 체결량, 코인목록

<CoinListsFrame>
  {coinNames && lineData && renderTimer ? (
   <>
     <CoinSummary coinNames={coinNames} lineData={lineData} />
     <CoinChart wsCoin={searchCoin[0].market} />
  	 <TradingVolume daysData={lineData.slice(0, 49)} coinName={searchCoin[0].market} />
  	 <SimpleSearch marketCodes={marketCodes} />
  </>
  ) : (
    <Skeleton />
  )}
</CoinListsFrame>

이전의 개발 형태보다 덜 무거워졌지만 하나의 문제가 생겼다.

나는 지금 Upbit API 를 사용하고 있기 때문에 여러번의 API 를 호출할 수 밖에 없었다.

한번에 코인들의 정보가 넘어오는게 아닌, 기존 코인들의 market 데이터를 한번 수신해야한다.

// result
{
  market: "KRW-BTC",
  korean_name: "비트코인",
  english_name: "Bitcoin",
}

result 의 market 값으로 다시 요청해야한다.

이처럼 두 번의 요청을 최소화 하기위해 market 의 정보는 recoil 을 사용하여 전역으로 상태관리한다.

✅ 컴포넌트 재사용성 높이기

  • 코딩을 시작할때 부터 내가 지키기 시작한 습관이다.
    • 분할정복(Divide and Conquer) 을 지향하며 개발하는편이다.
      • 장점: 어느 부분을 수정해야할지 금방 찾을 수 있다. 유지보수에 용이함
      • 단점: 프로젝트가 커지면 그만큼 많은 파일이 생기고 정리해놓지 않으면 이후 힘들어진다.

아래의 구조처럼 컴포넌트를 각각의 styled, util, hook 으로 관리하였다.


🌼 Longed for, 바라는 것

프로젝트를 마칠때마다 드는 생각이있다.

코딩을 좀더 잘하고싶다 라는 생각인데, 공부할게 너무 많은것같다.

쌓인 강의, 책을 처리해야하는데 쉽지않다.

이번 프로젝트를 하면서 새로운 라이브러리를 접하거나 WebSocket API 를 사용한게 큰 도움이 되는것 같다.

언제가 이 프로젝트도 다시 리팩토링 하고싶다는 생각이 든다.

한번에 일취월장하는 기적을 바라지않고 꾸준히 계속 하는 코딩습관을 길러야겠다.

수많은 구글링을 하며 많은 도움을 받았고 그들 처럼 도움이 되는 개발자가 되고싶다.

Coinzero 라는 프로젝트는 사실 엄청 오래전에 틀만잡아두고 최근에 끝낸 프로젝트이다.

그사이 퇴사도하고 부트캠프도하고 어쩌면 완성하지못할 프로젝트였는데 완성하게되서 기분이좋다.

별거아닌 프로젝트지만 다음엔 더욱더 완벽한 프로젝트 후기를 쓸 수 있도록 노력해야겠다.

나만의 기록용으로 남기는 글이지만 혹시나 정독해주신 분이 있다면 너무 감사하다고 말하고싶다 !

profile
Always All ways

2개의 댓글

comment-user-thumbnail
2023년 6월 19일

잘봤습니다. 혹시 깃허브주소 알려주실수있나요?!

1개의 답글