CDD(Component-Driven Development), Storybook으로 컴포넌트 개발하기

패키지 npm에서 받아서 사용하는 방법부터, 스토리북을 활용한 아토믹 패턴 컴포넌트 주도 개발 적용해보기

January 23, 2025

cdd 출처: 소프트웨어 개발자 Tom Coleman의 CDD 소개글

앞서 디자인 시스템을 만들었던 건 결국 리액트를 사용하여 컴포넌트 단위로 페이지를 개발하기 위한 기반을 설계를 해둔 것이다.

페이지를 구축하는 방식은 사람마다 다르고, 그만큼 복잡한 코드 및 일관되지 않은 구성으로 프로젝트가 구성된다면 나중에 유지보수하기 굉장히 어려워질 것이다.

이러한 문제점을 해결하기 위해 프로젝트에 CDD(Component-Driven Development), 즉 컴포넌트 주도 개발 방식을 도입했다.

그럼 CDD가 무엇인지 자세하게 알아보고, 프로젝트에 어떻게 적용했는지 살펴보겠다.

컴포넌트 주도 개발(CDD)이란?

cdd 출처: CDD

위의 그림처럼 기본적인 컴포넌트 단위부터 시작하여 UI를 구성하기 위해 점진적으로 결합(조립)해가는 상향적(bottom-up) 방식의 개발 방법이다.

프로젝트를 진행하다보면 컴포넌트의 양이 많아지면서 유지보수가 어려워지는 상황도 있고, 재사용될 거라 판단하여 컴포넌트화했지만 재사용되지 않아 부채가 쌓이는 경우도 있다.

이러한 컴포넌트들의 재용성을 효율적으로 활용하고 부채를 만들지 않기 위해 탄생한 개발 방법이 바로 컴포넌트 주도 개발(CDD)이다.

👀 테스트 중심의 개발 방법론인 "테스트 주도 개발(TDD)" 과는 사용 목적이 다르다.

CDD의 장점

문서에 나와있는 CDD의 장점으로는 다음 표에 나온 것들이 있다. 직접 개발하면서 느낀 장점들이기 때문에 동의할 수 있었다. cdd-positive

또한, 필자가 느낀 장점으로 더욱 구체적인 점들을 나열해보자면,

  • 디자인을 체계화하여 디자이너와 효율적인 협업이 가능해진다. 디자인 토큰, 컴포넌트 분류가 있고 없고는 개발함에 있어서 하늘과 땅의 차이다.
  • 초기에는 컴포넌트와 스토리북을 세팅 및 구현하는데 시간이 들지만, 결과적으로 재사용되는 컴포넌트들을 중복적으로 구현할 일이 없어 전반적인 UI 구현 난이도 및 개발 소요 시간은 줄었다.
  • 팀원들끼리 스토리북으로 배포된 컴포넌트 문서를 참고하며 중복되는 컴포넌트 구현을 피할 수 있었고 따라서 원활한 개발을 진행할 수 있었다.
  • UI 가이드라인이 바뀌는대로 모든 컴포넌트에 바로 적용이 가능하다. 컴포넌트 단 한 곳의 수정 작업만 거치면 된다.
  • UI 디자인 통일성을 체계적으로 가져가서 사용자 경험을 크게 향상시켰다. 이전 프로젝트에선 유명한 UI 라이브러리를 사용했지만 결국 팀원들과 협업하는 과정에서 디자인 통일성이 어긋나는 경험을 몇차례 겪었다.
  • 만약 같은 도메인을 글로벌 서비스로 출시하는 등의 기획에 따라서 서비스의 확장성을 가져갈 수 있다.

아토믹(Atomic) 디자인 패턴 도입

문서를 살펴보면, 컴포넌트부터 페이지 단위까지 가는 bottom-up 과정을 아래의 3단계로 분리했다.

  • 컴포넌트(components)
  • 컨테이너(containers, 2개 이상 컴포넌트 조합)
  • 페이지(pages, 2개 이상 컨테이너 조합)

컴포넌트 단위에서도 어디까지를 컴포넌트로 지정할 지에 대한 의문을 가진 지점이 많았다. 따라서 팀원들과 합의하며 최대한 가장 작은 단위로 나눈 Atom(원자)로 디자인 패턴을 도입했다.

아토믹 패턴에서도 우리 프로젝트에 알맞은 방식으로 디자인 패턴을 활용했는데, 이에 대한 더 자세한 스토리는 아티클을 참고하면 된다.

배포한 스토리북을 사용해서 컴포넌트 주도 개발을 더욱 용이하게 한다. 따라서 실제 애플리케이션의 스토리북, 디자인 시스템의 스토리북 두 개의 스토리북을 번갈아가며 개발을 수행했다.

그럼 디자인 시스템으로 구축해둔 아토믹 패턴의 컴포넌트들을 실제 애플리케이션으로 불러와 사용해보자!

디자인 시스템 통합

실제 애플리케이션에도 스토리북을 설치해준다. 설치한 스토리북에서 디자인 시스템으로 배포한 스토리북을 통합하기 위해 배포 주소를 refs에 추가한다.

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  refs: {
    'design-system': {
      title: 'Pov design system',
      url: 'https://www.chromatic.com/apps?accountId=673c240222ea36cf1210b536',
    },
  },
  addons: ['@storybook/addon-onboarding', '@storybook/addon-essentials', '@chromatic-com/storybook', '@storybook/addon-interactions'],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
};

🎯 refs 키를 .storybook/main.js에 추가를 하는 것은, 여러 개의 스토리북을 구성에 맞게 하나로 묶어지게 한다. 이것은 여러 개의 저장소가 분산되어 있거나 다른 기술 스택을 쓰는 규모가 큰 프로젝트를 진행할 때 도움이 된다.

그럼 실제 애플리케이션의 스토리북 로컬 환경에서 디자인 시스템을 확인할 수 있다. storybook-local

☝🏻 이제 애플리케이션을 개발하는 동안 디자인 시스템 컴포넌트와 문서를 참고하면서 할 수 있다.

기능 개발을 하는 도중에 디자인 시스템을 보면 개발자가 본인의 컴포넌트를 구성하는데 시간을 낭비하는 대신 기존에 존재하는 컴포넌트를 재사용할 가능성이 높아진다. 🙌🏻

디자인 시스템 패키지 설치

📌 디자인 시스템과 애플리케이션 작업을 동시에 진행하고 있다면 컴포넌트가 업데이트 되는데로 패키지도 업데이트 작업을 해줘야한다.

npm i pov-design-system@latest

스토리북 전역 스타일 적용 (ft. Vite 설정)

디자인 시스템에 정의한 것과 같은 전역 스타일을 사용할 예정이어서 .storybook/preview.js에서 global decorator을 추가하고,

// .storybook/preview.tsx
import type { Preview } from '@storybook/react';
import React from 'react';

import { PovProvider } from 'pov-design-system';

export const decorators = [
  (Story) => (
    <>
      <PovProvider>
        <Story />
      </PovProvider>
    </>
  ),
];

const preview: Preview= {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

export default preview;

vite.config 파일을 업데이트한다.

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      jsxImportSource: "@emotion/react",
      babel: {
        plugins: ["@emotion/babel-plugin"],
      },
    }),
  ],
  resolve: {
    alias: {
      "pov-design-system": "pov-design-system/dist/index.js",
    },
  },
});

컴포넌트 적용

storybook 뿐만 아니라 실제 애플리케이션에서도 사용할 수 있도록 부모 컴포넌트 최상단에서 PovProvider으로 감싸준다.

// src/main.ts
import { PovProvider } from 'pov-design-system';

import App from '@/App';
const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
  <QueryClientProvider client={queryClient}>
    <PovProvider>
      <App />
      <ReactQueryDevtools initialIsOpen={false} />
    </PovProvider>
  </QueryClientProvider>
);

이제 Button 컴포넌트를 import해서 실제 페이지에 구현해보자.

import { Button } from 'pov-design-system';

const index = () => {
  return (
    <div>
      <Button variant="primary" size="small">
        버튼
      </Button>
    </div>
  );
};

export default index;

design-system 패키지의 컴포넌트를 import 해오면 디자인 시스템에서 구축한 Button 컴포넌트를 보여줄 것이다.

추가적으로 디자인을 수정하거나, 또는 prop나 variant 등을 추가하고 싶은 부분이 있다면, Button 컴포넌트를 업데이트해서 배포할때마다 패키지를 업데이트 하면 변경된 부분이 실제 애플리케이션에 반영될 것이다! 🌟😀


Conclusion

연구 논문에 따르면 재사용 코드는 42-81% 시간절약과 40%의 생산성 향상이라는 결과를 가져올 수 있다고 한다.

사용자 인터페이스 코드(user interface code) 의 공유를 촉진시키는 디자인 시스템이 개발자들 사이에서 인기가 급상승 중인 것은 어찌 보면 당연한 일이고, 필자 또한 프로젝트에 도입하면서 이점을 많이 느꼈다.

그렇다고 해서 모든 프로젝트에 디자인 시스템을 구축하는 것이 좋다는건가? ❌

그렇지 않다. 디자인 시스템을 만들고 유지하는 데에는 기회 비용이 든다. 규모가 작은 경우에는 디자인 시스템으로 인해 절약되는 시간보다 만드는 데 소요되는 노력이 더 큰 것은 사실이다.

또한, 개발자와 디자이너가 협업을 정말 '잘'해야지만 무리 없이 시스템을 구축할 수 있을 것이다. 커뮤니케이션의 오류나 디자인 시스템을 구축하는 데에 이해도가 다르다면 어려울 수 있다.

하지만 이러한 기회 비용이 들 지 언정, 개발자의 경험을 크게 향상시킬 뿐만 아니라 결국엔 사용자 경험까지 챙기는 시스템이라고 보면 될 것 같다.

위에서 나열한 장점을 경험해보고 싶다면 디자인 시스템을 도입하는 것을 적극 추천한다 ~ 👍🏻🚀