안녕하세요. 만화경 프론트엔드 개발자 한민호입니다. realworld.io 오픈소스에 기여한 이야기를 해보려 합니다.

기술 스택 종류

여러분은 새로운 기술을 배울 때 어떤 방법을 선호하시나요?

1. 두꺼운 교과서 같은 책을 사서 처음부터 끝까지 독파한다. (하지만 항상 끝까지 읽지 못한다)

2. 가장 정확하고 최신 정보인 공식 문서를 샅샅이 읽는다. (북마크 해두고 다음에 봐야지…)

3. 간단한 프로그램을 만들어보면서 기술을 익힌다.


1번과 2번을 시도하다가 좌절한 적이 많은 저는 3번째 방법이 가장 마음에 듭니다.

간단한 프로그램으로 숫자야구 같은 게임을 만들어보는 것도 좋겠지만, 프론트엔드 세계에는 Hello World만큼 친숙한 Todo list (할 일 목록) 만들기가 존재합니다.

Todo list는 사람들에게 익숙한 주제일 뿐만 아니라, 스펙이 명확해 최소한의 구현만으로 라이브러리/프레임워크들 간의 장·단점을 비교·분석하기 쉽다는 점에서 Javascript 세계의 로제타 스톤으로 불리기도 합니다.

TodoMVC 사이트 스크린샷

TODO MVC라는 이름의 오픈소스 프로젝트에는 위처럼 React, Vue, Angular 외에도 아예 Javascript가 아닌 다른 프로그래밍 언어들로 구현한 Todo list 예제들도 존재합니다.



저도 과거 React를 처음 접하던 때에 Redux 창시자 Dan Abramov의 저자 직강을 보고 Todo list를 따라 만들며, React와 Redux를 익혔던 기억이 있습니다. 저처럼 동영상 강의를 보고 따라 만들거나, 각종 Todo list 만들기 강좌 블로그 포스팅들을 보고 따라 만들면 누구나 정말 10분 만에 아래와 같은 할 일 목록을 완성할 수 있습니다.

할 일 목록 예제

이제 여러분은 할 일등록하고, 완료하고, 제거할 수도 있습니다! 🎉

Q. 그런데, 이건 너무 쉽잖아요!

A. 네 그렇죠. Todo list는 초심자로서 배울 것이 많은 좋은 주제이지만, 반복해서 수십개의 할 일 목록 프로젝트를 만드는 건 너무 지겨울 것 같습니다. 조금 더 현실적인 걸 만들고 싶네요.



다행히 저 말고도 이런 생각을 하는 사람들이 많았던 것 같습니다. 오래 전부터 React 커뮤니티에서는 수많은 포스팅과 강의들이 간단한 Todo list와 같은 예제들만 재생산하는 현실을 지적하는 목소리가 있었는데요. 그렇지만, 아래 트윗에서 Dan Abramov가 말하는 것처럼 현실적인 여러 이유로 인해 real app example 만들기는 정말 어려웠습니다.

Dan Abramobv 트윗



그럼 Todo list보다 복잡하고 현실에 가까운 예제를 만드는 것은 불가능한 걸까요? 아뇨, 지금으로부터 무려 15년도 전에 Ruby on Rails의 아버지 DHH는 15분만에 블로그 만들기 라이브 코딩을 했는걸요.

영상에서 보듯이 15년 전에는 15분만에 간단하게 만들 수 있는 블로그였지만, 생각해보면 요즘의 블로그 서비스는 충분히 많은 기능들이 들어가 있습니다.

  • 포스팅을 조회할 수 있어야 하고,
  • 포스팅을 등록하려면 회원 로그인을 해야하며,
  • 해당 포스팅을 등록한 사람만 수정, 삭제를 할 수 있습니다.

  • 포스팅이 수십 개로 늘어나면 한 번에 목록을 다 보여주기보다는 페이지네이션을 해서 보여주는 것이 좋겠고,
  • 각각의 포스팅에는 댓글을 달 수도 있으면 좋겠습니다.
  • 물론, 포스팅과 똑같이 댓글을 등록하려면 회원 로그인을 해야하고,
  • 해당 댓글을 등록한 사람만 수정, 삭제를 할 수 있어야겠죠.

  • 댓글만 달 수 있으면 아쉬우니 포스팅에 좋아요를 할 수도 있어야 하고,
  • 글쓴이의 포스팅이 마음에 들면 팔로잉해서 나만의 피드에 모아볼 수 있으면 좋겠죠.
  • 마지막으로 특정 주제의 포스팅을 모아볼 수 있도록 포스팅마다 해시태그를 달 수 있으면 좋겠네요.

이 정도 스펙이라면 DHH가 15분만에 만들었던 블로그보다는 복잡하면서도, 집중한다면 짧은 시간 내에 충분히 만들어 볼만한 주제일 것 같습니다.

위의 주제와 거의 동일한 내용으로 Redux #1353 Issue를 통해 시작된 real world app 만들기 프로젝트는 realworld.io라는 오픈소스로 완성되었습니다.

프론트엔드 구현을 돕기 위해서 css stylesheet를 제공하고, 백엔드 구현을 돕기 위해 Postman Collection을 제공합니다. 처음에는 React+ReduxNode.js+express 구현체 밖에 없었지만, 수많은 개발자들의 기여로 현재는 총 70여개 기술 스택의 예제가 존재합니다. 특히, 프론트엔드의 경우 benchmark 결과까지 제공하는데요 아래와 같습니다.

위의 벤치마크 결과와 구현된 예제의 소스코드를 통해 저 나름대로 라이브러리/프레임워크 마다 장·단점을 파악할 수 있었고, 보일러 플레이트 코드가 적고 reselect, redux-saga3rd party library 없이 @computed 속성과 flow API만으로 모든 구현이 가능한 Mobx에 마음이 끌려 만화경 서비스를 Mobx 기반으로 구현하기도 하였습니다. (딜리버리플랫폼팀 김찬정님의 우아한형제들 기술블로그 포스팅을 보고도 많은 도움을 받았습니다. 이 자리를 빌어 감사의 말씀 드립니다.)

realworld example의 좋은 점은 실무에 바로 적용하기 망설여지는 기술들을 직접 사용해보며 익힐 수 있다는 점 외에도, 일반적인 사이드 프로젝트와 달리 기획이나 디자인 과정이 필요없이 realworld contribution guide에 정해진 스펙대로 개발만 하면 여타 기술 스택과 성능 및 코드 라인 수 등을 비교해 볼 수 있다는 점이 있습니다. (직접 비교해 본 결과 프론트엔드 기술 스택 중 Next.js + Redux-toolkit이 현 시점에 가장 좋은 선택지라고 판단되어, 만화경 웹사이트를 Redux-toolkit 기반으로 개편 작업 중입니다.)



제가 처음 realworld 프로젝트를 발견했을 당시에는 수많은 프론트엔드 예제 중에서 Next.js 예제가 없었습니다. 2019년 2월 달에 Next.js Issue가 open 되었지만, 미완성인 상태로 1년의 시간이 흘러가 버린 것입니다. 나름 production 서비스에서 Next.js를 사용해 본 저는 용기를 내어 Github 이슈에서 담당자를 넘겨받고, 바로 구현에 들어갔습니다.

Remember: Keep your codebases simple, yet robust


realworld 오픈소스 contribution guide에 나오는 원칙입니다. 해당 기술의 초심자가 10분 안에 충분히 구조를 파악할 수 있도록 기능적으로 완전하고 안정적으로 구현하면서도 오버 엔지니어링을 피해야 했습니다. 따라서 Next.js에만 집중하기 위해 상태 관리를 위한 ReduxMobx 라이브러리 사용은 선택지에서 제외되었습니다. (ReduxMobx를 사용하고 싶다면 CRA 기반으로 구현된 React+Redux 예제나 React+Mobx 예제에 직접 기여하는 게 좋다고 생각합니다. 마침 해당 예제들의 업데이트가 늦어져 각각 redux-toolkitmobx-react v6로 기여할 수 있는 기회가 있네요.)

  1. 상태는 최대한 React local state를 활용하고
  2. 전역으로 사용할 상태는 Context API를 통해 구현하였습니다.
  3. remote data fetch는 Next.js를 만든 zeit의 SWR을 활용했습니다.



완성된 결과물은 아래와 같습니다.



전체 소스 코드는 아래에서 확인 가능합니다.

remote data fetchSWR을 활용했기 때문에 각 컴포넌트마다 아래처럼 코드도 훨씬 줄일 수 있었습니다.

const CommentList = () => {
  const [comments, setComments] = React.useState([]);
  const [error, setError] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(true);

  const router = useRouter();
  const {
    query: { pid },
  } = router;

  React.useEffect(() => {
    fetch(`${SERVER_BASE_URL}/articles/${pid}/comments`)
      .then((res) => res.json())
      .then((data) => setComments(data))
      .catch((error) => setError(error))
      .finally(() => setIsLoading(false));
  }, []);

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error.message} />;
  return (
    <>
      <CommentInput />
      {comments.map((comment: CommentType) => (
        <Comment key={comment.id} comment={comment} />
      ))}
    </>
  );
};

React local state 기반 remote data fetch 방식 예제



const CommentList = () => {
  const router = useRouter();
  const {
    query: { pid },
  } = router;

  const { data, error } = useSWR(
    `${SERVER_BASE_URL}/articles/${pid}/comments`,
    fetcher
  );

  if (!data) <LoadingSpinner />;
  if (error) <ErrorMessage message={error.message} />;

  return (
    <>
      <CommentInput />
      {data?.comments.map((comment: CommentType) => (
        <Comment key={comment.id} comment={comment} />
      ))}
    </>
  );
};

export default CommentList;

SWR 기반 remote data fetch 방식 예제



const { data, error, isValidating, mutate } = useSWR(key, fetcher, options);

useSWR hook의 기본 동작

  1. 첫번째 파라미터인 key에는 remote data를 fetch할 떄 사용할 고유한 식별자를 넘겨줍니다. (REST API의 엔드 포인트 URL 등)
  2. 두번째 파라미터인 fetcher에는 key를 넘겨받아 API fetching 혹은 storage getItem 등을 수행 후 value를 return할 함수를 넣어줍니다.
  3. 마지막 파라미터인 options에는 SSR을 위한 initial data 주입이나 revalidation, error, retrying 등을 핸들링할 config를 넘길 수 있습니다.



Next.js Realworld example app을 통해 여러분은 pages level에서 initial datafetch하여 Server Side Rendering을 하는 Next.js의 기본적인 동작 방식을 파악할 수 있고, components level에서는 React.useEffect에서 remote datafetch 후 상태로 관리하는 방식이 아닌, useSWR hook을통해 remote data fetch를 하여 internal cache layer에 있는 data에 곧바로 접근하여 사용하는 방식을 익힐 수 있습니다. (API가 graphql 서버라면 react-apollo를 사용하겠지만, REST API 서버에서는 제가 사용한 SWR이나 react-query가 좋은 선택일 것 같습니다.)

물론 제가 완성한 위의 프로젝트가 Next.js를 대표하는 완벽한 예제는 아닙니다. 하지만, Todo list를 완성한 후에 그 다음 단계로 나아가고 싶은 초심자 분들께 도움이 될 수 있으면 하는 마음으로 해당 프로젝트를 구현하였고, realworld.iopull request를 날려 maintainer들의 reviewmerge가 되어서 현재는 오픈소스의 README.md 파일 프론트엔드 기술 스택에 올라가 있습니다.

혹시, “나는 이걸로 부족해! 더 개선시키고 싶어”라고 생각하시는 분들에게 제안하는 Roadmap은 다음과 같습니다.

  1. 테스트 코드를 작성하세요. 특히, util function들은 jest 기반 unit test로 검증을 해보는게 좋고, form validation이나 page navigation 관련해서는 cypressE2E test를 해보세요.
  2. 현재는 인증 tokenlocalStorage에 저장 후 사용하고 있습니다. 다음 가이드를 참고해 httpOnly&sameSite cookieinMemory variabletoken을 관리하는 방식으로 고쳐보세요. (다만, 이 경우에는 API 서버access token과 함께 refresh token을 발급해주는 방식으로 변경되는게 좋겠습니다.)
  3. 섣부른 최적화는 좋지 않지만, 모든 걸 다 완료했다고 생각한다면 최적화 작업을 해보세요. Chrome Audits score를 모두 100점으로 만들어보는 것도 좋고, React.useCallbackReact.memo를 사용해 렌더링 최적화도 해보세요.



이 글을 다 보시고 realworld.io 프로젝트에 관심이 생기셨다면, 지금 바로 시작해보세요. 시작이 반이다라는 말도 있잖아요.

P.S. 모바일 개발자 분들은 기여하시면 곧바로 은메달을 먹을 수 있습니다ㅎㅎ

모바일 앱 구현 목록