[FE] 디자인 시스템에 아토믹 디자인 패턴 도입하기

크루루 서비스는 리액트 웹 어플리케이션으로 Emotion-Styled를 사용하는 구조를 사용하고 있습니다.

서비스 개발 초기 단계에서 우리는 컴포넌트 분리에 대한 명확한 기준을 가지지 않았습니다.

이는 서비스 도메인에 따라 컴포넌트 구조가 변경될 사항이 많을 것이라 예상했기 때문입니다. 이러한 이유로 우리는 유연하게 변화에 대응할 수 있는 컴포넌트를 만들고자 했습니다.

하지만 서비스가 확장됨에 따라 공통 컴포넌트의 수가 늘어나고, 복잡한 컴포넌트들이 생기기 시작했습니다. 이로 인해 코드의 관리가 어려워졌고, 각 컴포넌트 간의 역할과 책임이 모호해지는 문제가 발생했습니다.

우리는 다음과 같은 컴포넌트 구조를 가지고 있었습니다.

📦components
 ┣ 📂appModal
 ┣ 📂applyManagement
 ┣ 📂common
 ┃ ┣ 📂Accordion
 ┃ ┣ 📂ApiErrorBoundary
 ┃ ┣ 📂BaseModal
 ┃ ┣ 📂Button
 ┃ ┣ 📂CheckBox
 ┃ ┣ 📂CheckboxLabelField
 ┃ ┣ 📂ChevronButton
 ┃ ┣ 📂CopyToClipboard
 ┃ ┣ 📂DateInput
 ┃ ┣ 📂Dropdown
 ┃ ┣ 📂DropdownItem
 ┃ ┣ 📂IconButton
 ┃ ┣ 📂InputField
 ┃ ┣ 📂ModalHeader
 ┃ ┣ 📂OpenInNewTab
 ┃ ┣ 📂PopOverMenu
 ┃ ┣ 📂QuestionBox
 ┃ ┣ 📂Radio
 ┃ ┣ 📂RadioField
 ┃ ┣ 📂RadioLabelField
 ┃ ┣ 📂RadioOption
 ┃ ┣ 📂Spinner
 ┃ ┣ 📂Tab
 ┃ ┣ 📂TextEditor
 ┃ ┣ 📂TextField
 ┃ ┣ 📂Toast
 ┃ ┗ 📂ToggleSwitch
 ┣ 📂dashboard
 ┣ 📂postManagement
 ┣ 📂processManagement
 ┣ 📂recruitment
 ┗ 📂recruitmentPost
📦pages
 ┗ // ...

다음은 여러 Input에 대한 사용자 경험 향상을 목적으로 만들어진 컴포넌트 입니다.

우리는 위 컴포넌트를 구현하기위해 다음과 같이 구분하였습니다.

 

보라색에 해당하는 부분을 RadioInputField로 명명하였고, 빨간색 부분을 RadioInputOption 명명 하였습니다.

Radio가 주 기능이 되는 컴포넌트는 많습니다.

보라색 부분에 해당하는 RadioInputField 컴포넌트와 빨간색 부분을 RadioInputOption를 수정하거나 사용할 일이 생겼습니다. 어떤 디렉토리에서 찾을 수 있을까요?

Radio와 관련된 재사용 컴포넌트라 예상하여, Component/common 로직에 위치해 있는 걸 예상하였지만, 사실 위 컴포넌트들은 Component/recruitment 에 위치해 있었습니다.

이로 인해 단순히 컴포넌트를 찾는 과정에서 큰 비용이 발생한다 느꼈습니다. 심지어 찾지 못하는 바람에, 구현되지 않은 컴포넌트라 판단하여 팀원과 중복되는 작업이 발생되는 경우도 있었습니다.

이러한 문제를 해결하기 위해, 우리는 컴포넌트의 역할을 명확하게 분리하고, 아토믹 디자인 패턴 등의 기준을 도입하여 컴포넌트 구조를 체계화하는 것이 필요했습니다.

코드의 재사용성을 높이고, 유지보수가 용이한 컴포넌트 구조, 즉 “잘 대응할 수 있는 구조’를 만들 만들어야 한다 판단했습니다.

아토믹 디자인 패턴을 접하다.

아토믹 디자인 패턴

아토믹 패턴은 위 이미지와 같습니다. 더 이상 나눌 수 없는 Atom형식에서 부터, Atom으로 구성된 복합 컴포넌트인 Molecules, Organisms와 페이지를 나눈 형태인 Template형태를 통해 Page를 구성하는 것입니다.

크루루 서비스에서 역시 컴포넌트 구조가 흔들렸던 이유가 바로 Common 컴포넌트와 Domain 컴포넌트의 분리 기준이 모호했기 때문이라 판단했습니다.

기존에는 모든 컴포넌트를 Feature단위로 나누고, “재사용”되는 컴포넌트를 Common으로 나누는 기준을 적용하고 있었습니다. 하지만 이는 Common디렉토리에 존재하는 컴포넌트의 개수가 무한정 늘어나는 부작용을 만들어 냈습니다.

이는 Moueclues와 Organisms를 구분하는 기준이 모호했기 때문으로 바꿔말할 수 있겠습니다. 카카오 아티클에서도 언급된 내용으로 이곳에서 살펴보세요. 우리는 이에 대한 기준을 변경할 필요가 있었습니다.

크루루 컴포넌트에 아토믹 디자인 패턴 접목하기

현재 크루루 컴포넌트의 구조는 다음과 같은 기준으로 구분되었습니다.

  1. component내부에 Feature단위로 구분하여 컴포넌트를 분리합니다.
  2. 많은 곳에서 사용되는 기본 컴포넌트(재사용 되는 컴포넌트)는 component/common에 분리합니다.

이런식으로 분리하다보니. common 폴더 내부에 많은 수의 컴포넌트가 쌓이기 시작했습니다.

Atom과 Molecule 구분하기

우선적으로 common디렉토리 내부에 있는 컴포넌트를 atom과 molecule로 나누는 작업을 진행했습니다. 이 과정에서도 모호한 기준을 가진 다음과 같은 컴포넌트도 있었습니다.

import React, { useState } from 'react';
import { HiChevronDown, HiChevronUp, HiOutlineClipboardList } from 'react-icons/hi';

import S from './style';

interface AccordionProps {
  title: React.ReactNode;
  children: React.ReactNode;
}

function Accordion({ title, children }: AccordionProps) {
  const [isOpen, setIsOpen] = useState(true);

  const toggleAccordion = () => {
    setIsOpen(!isOpen);
  };

  return (
    <S.Container>
      <S.Header onClick={toggleAccordion}>
        <S.Title>
          <HiOutlineClipboardList />
          <S.TitleText>{title}</S.TitleText>
        </S.Title>
        {isOpen ? <HiChevronDown size={24} /> : <HiChevronUp size={24} />}
      </S.Header>
      {isOpen && <S.List>{children}</S.List>}
    </S.Container>
  );
}

Accordion.ListItem = S.ListItem;

export default Accordion;

위 코드는 크루루 서비스에서 사용하고 있는 아코디언 컴포넌트입니다. 이는 여러개의 컴포넌트인 ‘styled-component’에 의존하고 있는 하나의 통합된 컴포넌트이지만, 어디에서도 우리 팀원이 정의한 atom을 사용하는 컴포넌트는 아닙니다. 그렇다고 이 컴포넌트를 atom으로 정의하면 될까요?

이 아코디언의 모습을 보면 molecule에 가깝습니다.

직관적으로 molecule에 가까운 Accordion 컴포넌트

styled-component를 사용하고 있는 개발 환경에서는, molecule을 atom의 집합으로 정의하는 것은 모호한 부분이 있다고 생각했습니다. 우리는 이 모호한 기준을 좁혀나가야할 필요성을 느꼈습니다.

저는 아토믹 디자인 패턴을 도입함으로써 얻는 이점은 인지비용의 절감이라 생각했습니다. 따라서 아코디언을 떠올렸을때, “atom”이라고 인지되는지, “molecule”이라고 인지되는지에 따라서 아코디언을 구분하고자 했습니다.

우리 팀원들은 직관적으로 아코디언을 molecule로 생각했고, 이에 따라 구분하였습니다.

Organism 구분하기

기존의 feature단위로 컴포넌트를 구분한 기준은, 해당 기능 컴포넌트에서만 사용되는 컴포넌트라면 feature로 명명하였습니다.

이는 도메인 로직(React-Query, 혹은 useParams와 같은 Hook을 사용, props로 도메인 정보를 받는 컴포넌트)을 사용하는 컴포넌트에 해당하는 것이었습니다.

위 컴포넌트는 지원자 정보를 담은 ApplicantCard 컴포넌트. 해당 컴포넌트를 molecule로 구분할 수도 있겠지만, 도메인 정보를 담고 있다고 판단하여 Organism으로 구분하였다.

위 컴포넌트는 지원자 정보를 담은 ApplicantCard 컴포넌트. 해당 컴포넌트를 molecule로 구분할 수도 있겠지만, 도메인 정보를 담고 있다고 판단하여 Organism으로 구분하였다.

molecule과 organism을 구분하는 기준으로, 도메인 로직을 사용하는지에 대한 여부로 구분하였고, 이는 기존의 feature단위로 나눠진 컴포넌트를 organism으로 구분하는 것이 좋다 판단하였습니다.

아토믹 디자인 패턴을 도입한 결과

📦components
 ┣ 📂_common
 ┃ ┣ 📂atoms
 ┃ ┃ ┣ 📂ApiErrorBoundary
 ┃ ┃ ┣ 📂Button
 ┃ ┃ ┣ 📂CheckBox
 ┃ ┃ ┣ 📂CopyToClipboard
 ┃ ┃ ┣ 📂DateInput
 ┃ ┃ ┣ 📂DropdownItem
 ┃ ┃ ┣ 📂IconButton
 ┃ ┃ ┣ 📂OpenInNewTab
 ┃ ┃ ┣ 📂QuestionBox
 ┃ ┃ ┣ 📂Radio
 ┃ ┃ ┣ 📂Spinner
 ┃ ┃ ┣ 📂TextEditor
 ┃ ┃ ┣ 📂Toast
 ┃ ┃ ┗ 📂ToggleSwitch
 ┃ ┗ 📂molecules
 ┃ ┃ ┣ 📂Accordion
 ┃ ┃ ┣ 📂CheckBoxOption
 ┃ ┃ ┣ 📂CheckboxLabelField
 ┃ ┃ ┣ 📂Dropdown
 ┃ ┃ ┣ 📂InputField
 ┃ ┃ ┣ 📂PopOverMenu
 ┃ ┃ ┣ 📂RadioField
 ┃ ┃ ┣ 📂RadioLabelField
 ┃ ┃ ┣ 📂RadioOption
 ┃ ┃ ┣ 📂Tab
 ┃ ┃ ┗ 📂TextField
 ┣ 📂ApplicantModal
 ┣ 📂applyManagement
 ┣ 📂dashboard
 ┣ 📂postManagement
 ┣ 📂processManagement
 ┣ 📂recruitment
 ┗ 📂recruitmentPost
📦pages
 ┗ // ...

스토리북 디렉토리는 다음과 같이 변경되었습니다.

아토믹 디자인 패턴을 통해 디렉토리를 분리하면서 많은 수의 컴포넌트를 분리하는 기준이 생겼습니다. 이를 통해서 추후에 쌓이게 되는 컴포넌트들을 보다 깔끔하게 관리할 수 있게 되었습니다.

atom, molecule, organism을 구분하는 기준은 명확하지 않습니다. 이러한 구분은 프로젝트의 특성과 개발팀의 작업 방식에 따라 달라질 수 있습니다.

이에 대한 기준을 잡는 것이 프론트엔드 팀의 큰 과제중 하나라고 생각합니다. 일관된 기준을 마련하는 것은 코드의 유지보수성과 재사용성을 높이는 데 중요한 역할을 합니다. 따라서 팀원들이 공통된 이해를 바탕으로 구성 요소를 설계하고 확장 가능하게 만드는 것이 궁극적인 목표가 되어야 합니다.

결국 우리는 유연하고 확장가능성 있는 컴포넌트를 만드는 것이 목표니깐요!

Reference

 

아토믹 디자인을 활용한 디자인 시스템 도입기 | 카카오엔터테인먼트 FE 기술블로그

정호일(harry) 카카오페이지에서 웹 프론트엔드를 개발하고 있습니다. 집보다 밖에 돌아다니는 걸 좋아합니다.

fe-developers.kakaoent.com

 

 

Thinking About React, Atomically ⚛

Utilizing Brad Frost’s Atomic Design principles to better architect React applications

medium.com