Atomic Design Pattern과 컴포넌트 단위 개발 방식 적용기 : 장점과 단점

YI·2023년 5월 5일
18

0. 들어가며

작년 대학연합 IT 동아리 SOPT의 장기 해커톤 AppJam에서 진행한 팀 프로젝트 '팀블'에서 컴포넌트 단위 개발 방식과 아토믹 디자인 패턴을 적용한 방법과 직접 느낀 장점과 단점에 대해 이야기 해보려고 합니다. 처음 도입을 결정했을 때, 생각보다 도움받을 만한 글이 많지 않아 어려움을 겪었는 데, 같은 고민을 하고 있는 누군가에게 도움이 되는 글이 되면 좋겠습니다.

1. 왜 아토믹 디자인 패턴을 사용했나요?

결론부터 빠르게 말하자면, 저희 팀이 아토믹 디자인 패턴을 사용한 이유는 적은 인원으로 최대한 빠르게 프로젝트를 완료하기 위해서였습니다.

'팀블' 프로젝트는 10개 가량의 View를 가지고 있습니다. 각 View에는 글 작성 및 수정, 이미지 추가, 태그 선택 등이 포함되어 있어 각 페이지가 꽤 큰 볼륨이었습니다. 팀블은 다른 팀들에 비교해서도 프론트엔드 작업 볼륨이 큰 편이었지만, 전체 참가팀들 중 가장 적은 수의 프론트엔드 개발자(3명)로 구성된 팀이었습니다. 프론트엔드 팀원 한 명 한 명이 모두 귀한 상황에서, 팀의 개발 리더가 인턴 근무 issue로 프로젝트 참여가 제한적인 문제가 발생했습니다.

이러한 상황에서 2주라는 프로젝트 기간 동안 최대한 빠르게 프로젝트를 완성시킬 수 있는 방법을 모색했고, 찾은 방법이 아토믹 디자인 패턴이었습니다.

2. 어떻게 아토믹 디자인 패턴을 적용해 개발했나요?

아토믹 디자인 패턴이란?

아토믹 디자인 패턴은 UI 설계 방법 중 하나로, 복잡한 화면을 분해 가능한 가장 작은 단위인 원자 단위까지 나누어, 각 요소간 컴포넌트의 재활용성을 높이고 복잡한 화면 구성을 쉽게 제작하는 접근 방식 중 하나입니다.

아토믹 디자인 패턴에서는 '작은 부품들을 조립해 화면을 만든다'라는 개념을 원자가 모여 분자가 되고 분자가 모여 유기체가 된다는 화학적인 개념을 사용해 설명합니다. 이 때 원자는 절대 쪼개지지 않은 최소 단위이고, 원자끼리 결합하면 분자가 되어 특정 행동을 할 수 있고, 분자들이 결합한 유기체가 모여 의미있는 하나의 단위가 된다는 식으로 비유해 설명하고 있습니다.

  • Atoms(원자) : 더 이상 쪼갤 수 없는 최소 단위.
  • Molecules(분자) : 원자가 모여서 만들어지며, 최소 1가지 기능을 수행한다.
  • Organisms(유기체) : 원자와 분자들을 모아 만들어지고, 사용자에게 의미와 역할이 존재하는 단위
  • Templates(템플릿) : 유기체가 모여 만들어지고, 실제 데이터가 포함되기 전의 컨텐츠의 최종 레이아웃
  • Pages(페이지) : 최종적으로 사용자에게 보여지는 완성된 페이지

아래 이미지를 확인해보면 원자,분자,유기체,템플릿,페이지가 어떤 것인지 쉽게 감을 잡을 수 있을 것 입니다.
더 자세한 설명이 궁금하다면, Atomic Design Methodology를 읽어보시길 바랍니다!


프로젝트에 아토믹 디자인 패턴 적용기

아토믹 디자인 패턴은 규칙이 정확히 정해져 있는 프로세스라기 보다는 일종의 "멘탈 모델"입니다.
이 말을 하는 이유는, 위에서 살펴본 간단 명료한 이론과는 다르게 현실의 프로젝트에서 아토믹 디자인 패턴을 적용하는 데는 꽤 어려움이 존재합니다. 팀블 프로젝트에서 아토믹 디자인 패턴을 사용하며 마주한 문제들을 몇 가지 공유해보고자 합니다.

1. 디자이너님, 아토믹 디자인 패턴을 아시나요?

프론트엔드 개발자는 원자, 분자 단위의 컴포넌트를 디자이너가 Figma에 작성한 컴포넌트, 컬러팔레트, 타이포그래피 등을 기준으로 개발합니다. 이렇게 할 때의 가장 큰 장점은,개발자와 디자이너가 동일한 네이밍을 가지고 빠르고 명확하게 의사소통을 할 수 있다는 것 입니다. 예를 들어, 기본 로그인 버튼, hover 시 로그인 버튼 대신, btn_login , btn_login_active 라는 명확하고 공통된 네이밍으로 의사소통이 가능합니다.

눈치 채셨을지 모르겠지만, 이러한 명확한고 편리한 의사소통을 위해선 디자이너도 아토믹 디자인 패턴을 이해하고 있다는 전제 조건이 필요합니다. 디자이너가 Figma에 아토믹 디자인 패턴을 적용한 디자인 시스템을 구축하면, 프론트엔드 개발자는 해당 디자인 시스템을 기반으로 개발을 진행합니다.

아래는 팀블의 디자인 시스템의 일부입니다. 디자이너와 프론트엔드 개발자가 아토믹 디자인 시스템에 대한 기본적인 이해를 가지고, 기준에 맞게 컴포넌트를 분리하여 디자인 시스템을 구축하고, 개발자는 이를 보고 개발을 진행합니다. 하지만 이 과정이 생각처럼 쉬운 과정이 아닙니다.
팀블의 디자인 시스템 일부
아토믹 디자인 시스템은 명확한 절차와 기준이 존재하는 프로세스가 아닌, "멘탈 모델"입니다.
그렇기 때문에 개인의 관점에 따라, 디자이너와 개발자의 시선에 따라 등 컴포넌트를 나누는 기준이 천차만별입니다. 팀블에서도 이러한 팀원 간 관점과 생각 차이를 좁히고 모두가 동의하는 우리 팀만의 기준을 세우고 컴포넌트를 분리하는 데 많은 시간을 소비했습니다.

왜 이러한 일이 발생하는 지 한 가지 예시를 들어보겠습니다.
저희 서비스에는 아래와 같은 태그 컴포넌트가 있습니다.

여러분이라면 위의 두 개의 요소를를 각각의 컴포넌트로 분리하실건가요?
원자로 분리하실 건가요, 아니면 분자로 분리하실 건가요?

사람에 따라 다양한 정말 다양한 답변이 나옵니다. 저희 팀도 굉장히 다양한 의견이 나왔고, 각자 나름 합리적인 이유를 가지고 있었습니다. 아토믹 디자인 패턴을 기준으로 컴포넌트를 구분하는 과정에서 이러한 의견 충돌이 정말 수도 없이 나오게 됩니다. 이러한 기준은 뭐가 맞다 틀리다는 정답이 없는 문제입니다. 각자의 상황에 맞게, 팀의 나름의 기준에 맞게 구분해야 합니다.

저희 팀은 왼쪽의 '이메일로 추가' 요소는 원자로, 오른쪽 요소는 분자로 구분했습니다.

이유는 서비스 내에서 '이메일로 추가' 버튼을 더 작은 요소로 쪼갤 이유가 없었기 때문입니다. 예를 들어, + 버튼을 다른 곳에서 사용했다면 +,텍스트,카드 원소로 분리했을 것 입니다. 하지만 서비스 내에서는 '이메일로 추가' 버튼이 그 자체로 가장 작은 단위로 사용되었기 때문에 원소로 구분했습니다.

반면 오른쪽 요소를 분자로 분류한 가장 큰 이유는, 기본 프로필 이미지를 원자로 분류했기 때문입니다. 서비스 전반에 사용되는 기본 프로필 이미지를 크기 별로 조절하기 위해 원자로 분류했습니다. 그렇기 때문에 어쩔 수 없이(혹은 자연스럽게) 원자와 텍스트, 배경이 합쳐져 만들어진 오른쪽 프로필 요소를 분자로 분류하게 되었습니다.

정리하지면, 아토믹 디자인 패턴은 개발자 뿐만 아니라 디자이너도 함께 이해해야 하는 원칙입니다.

아토믹 디자인 패턴은 그 자체로서 컴포넌트를 나누는 기준이 되지 못하기 때문에 원리를 알고 있다고 바로 간단하게 프로젝트에 적용할 수 없습니다. 아토믹 디자인 패턴을 프로젝트에 적용하기 위해선, 개발자와 디자이너가 함께 머리를 맞대고 팀에 맞는 기준을 정립하고, 협의를 통해 적절히 컴포넌트를 잘 분류하는 일이 반드시 필요합니다.

마지막으로 저희 팀이 원자를 나눈 기준을 간략하게 공유하며 다음 주제로 넘어가보겠습니다.

  1. HTML 태그를 기준으로 나눈다.
  2. <span>, <p> 등 버튼이나 링크가 아닌 문자들은 따로 구분하지 않는다.
  3. 모양이나 크기가 완전히 동일하지 않지만, 상속을 통해 스타일을 변경할 수 있는 경우 따로 구분하지 않는다.

2. 이 컴포넌트는 분자일까요? 아니면 유기체일까요?

누구나 아토믹 디자인 패턴을 적용하면 이 문제를 마주하게 됩니다. 겨우 원자를 구분해 다 만들었는데, 우리는 바로 다음 관문인 분자와 유기체를 만나 "얜 대체 뭐로 구분해야 할까,, 분자?유기체?" 라는 고민을 하게 됩니다.

"원자가 모여 분자가 되고, 분자가 모여 유기체가 된다. 여러 유기체를 결합해 틀을 만들고 여기에 데이터를 결합해 최종 결과물을 완성한다."

이러한 아토믹 디자인 패턴의 기본 이론은 너무나 당연해 보입니다. 하지만 현실은 전혀 그렇지 않습니다.

이론상으로 분자는 원자로 구성되어 SRP(단일 책임 원칙)에 따라 1가지 책임을 지고,
유기체는 원자 + 분자 + 유기체로 구성되어 서비스에서 레이아웃을 기준으로 나눌 수 있는 영역을 말합니다.

하지만 현실의 컴포넌트 구조는 이렇게 명확하게 두 단계로 구분되지 않습니다. 컴포넌트는 매우 다양한 방식으로 재사용될 수 있기 때문에 정확히 어느 단계에 속하는 지 구분하기 쉽지 않고, 프로젝트가 커짐에 따라 분자와 유기체 사이에는 수많은 실제 계층이 존재하게 됩니다.

예를 들어, 아래와 같은 input 창은 분자로 분류할 수도, 유기체로 분류할 수도 있습니다.

해당 input 창은 input, button, label 의 원자들로 구성되어 있습니다. 여러 가지 원자들의 조합으로 만들어졌기 때문에 input 창을 분자로 간주할 수 있습니다.

하지만 위와 같이 input 창이 전체 페이지의 독립적인 블록으로 존재하는 경우, 유기체로 분리하는 것도 틀리다고 할 수 없습니다. 독립적인 검색 블록으로 검색 기능을 제공하고, 페이지 내에서 자체적인 기능을 수행하기 때문입니다.

이렇게 상황에 따라 분자인지 유기체인지 구분하기 애매모호한 경우가 많이 존재합니다. 이런 애매모호한 상황을 피하기 위해 분자와 유기체 사이에 조금 더 많은 단계를 둘 수도 있습니다. 예를 들어, 단순한 유기체는 'Compound(복합체)'라고 부를 수 있습니다. 하지만 이렇게 생각하기 시작하면 계층구조가 끝없이 나오게 될 것입니다.

뿐만 아니라, 이러한 문제들을 잘 해결해서 UI 요소들을 원자,분자,유기체,템플릿,페이지라는 계층 구조로 정확히 분류했다고 해도 문제가 발생할 수 있습니다. 소프트웨어를 단단한 계층구조로 설계를 하다보면, 상위 레벨의 아주 사소한 변경 사항이 그 아래 레벨로 증폭되는 문제가 발생할 수 있습니다.

예를 들어,아토믹 디자인 패턴에서, 원자 컴포넌트 1개를 개발한 뒤 명세서에 변화가 생기면 1개의 원자 컴포넌트만 수정하면 됩니다. 하지만 유기체 단위까지 개발을 마친 뒤 명세서가 변경되거나 단순히 버튼의 padding 값이 변경되는 등의 변경사항이 발생하면, 해당 컴포넌트에 의존하는 수많은 컴포넌트들이 동시에 변경이 되는 side effect가 발생합니다. 이러한 문제는 의도적으로 side effect를 발생시키고 그에 따른 변경을 모두 통제할 수 있을 때는 굉장히 유용한 기능일 수 있습니다. 하지만 아토믹 디자인 패턴과 같이 5단계로 이루어진 계층 구조의 side effect를 모두 예측하고 컨트롤 하는 것은 쉽지 않은 일입니다.

개발 초기, 저희 팀은 개발 명세서가 나오기 이전에 원자 컴포넌트 개발을 시작했습니다. 아직 확정된 개발 명세서가 나오기 이전이었기 때문에 mock data를 사용하여 Storybook 테스트를 진행했습니다. 그로 인해 개발 명세서가 확정되기까지 여러 번의 mock data 수정이 필요했습니다. 처음에 원소 컴포넌트의 mock data를 변경하는 것은 매우 간단한 일이었고, 전혀 문제가 되지 않았습니다. 하지만 시간이 지나고 계층이 추가될 수록 변경은 점차 복잡해졌고, 간단한 변경사항에도 변경에 대응하는 데 시간이 많이 소요되었습니다.

빠르게 컴포넌트를 개발하기 위해 도입한 아토믹 디자인 패턴인데, 오히려 아토믹 디자인 패턴 때문에 컴포넌트 개발이 느려지는 아이러니한 상황이 발생했습니다. 이는 아토믹 디자인 패턴의 개념을 완전히 이해하지 못하거나, 적절하지 않은 방식으로 패턴을 적용하거나, 프로젝트의 규모와 복잡도가 적합하지 않을 경우 등이 원인이 되어 발생한 문제입니다. 그렇기 때문에 아토믹 디자인 패턴을 적용할 때는, 아토믹 디자인 패턴을 도입하려는 고민을 하는 분들이라면, 프로젝트의 규모와 복잡도에 맞게 적절한 레벨의 구성 요소를 선택하고, 구성 요소 간의 관계와 흐름을 고려하여 구성하는 것이 중요합니다.

정리하자면, 아토믹 디자인 패턴은 단일 책임 원칙(SRP)에 따라 UI 컴포넌트를 구분합니다.
그러나 현실에서는 컴포넌트의 구조가 명확하게 두 단계로 나누어지지 않기 때문에, 분자와 유기체 사이에는 수많은 실제 계층이 존재하게 됩니다. 어떤 단계에 속하는 지 구분하기 애매한 경우가 많고, 명확하게 분리한다고 해도 다양한 문제들이 발생할 수 있습니다.

그렇기 때문에 아토믹 디자인 패턴 도입 전, 이러한 문제점들을 인지하고 해결 방법을 고민해 본다면 더 나은 프로젝트가 되리라 생각합니다.

3. 비즈니스 로직은 대체 어디에 둬야해?

아토믹 디자인 패턴에서는 원자와 분자 단위에서는 가능한 비즈니스 로직을 두지 않은 것이 바람직합니다.
예를 들어, input 창과 submit 버튼으로 구성된 분자가 있다고 가정하겠습니다. 분자는 props로 전달받은 handleClick 핸들러를 사용할 수 있지만, 분자 내에서 input 값에 따라 특정 일을 수행하도록 동작하는 것은 바람직하지 않습니다.

아토믹 디자인 패턴에서 기억해야할 중요한 개념은 "컴포넌트는 가능한 멍청해야 한다" 는 것 입니다. 특히 원자,분자와 같은 low level의 컴포넌트들은 애플리케이션의 비즈니스 로직에 대해 전혀 알지 못하고, 그저 상위 컴포넌트가 렌더링하라고 지시하는 내용만 렌더링해야 합니다. 이렇게 해야 low level 컴포넌트들의 재사용성이 극대화 될 수 있습니다.

팀블의 분자 컴포넌트를 하나를 예시로 들어보겠습니다.
아래의 코드는 프로젝트에 새로운 멤버를 추가하는 모달입니다. 분자 level인 이 모달은 상위 컴포넌트에게 props로 전달받는 값과 handler 함수만을 사용해 동작합니다. 이렇게 시키는 대로만 동작하도록 '가능한 멍청하게' 동작하는 분자는 나중에 동일한 모달에 text가 바뀌거나, handler 동작이 변경되어도 그대로 가져가 재사용할 수 있다는 큰 장점이 있습니다.

export function AddMemberModal(props: AddMemberModalProps) {
  const { value, onChange: handleChange, onClick: handleClick, onOpen: handleOpen } = props;

  return (
    <StyledAddMember>
      <StyledModal>
        <StyledClose onClick={handleOpen} />
        <StyledInputForm>
          <input type="email" value={value} onChange={handleChange} />
          <StyledAdd onClick={handleClick} />
        </StyledInputForm>
      </StyledModal>
    </StyledAddMember>
  );
}

지금까지의 글을 보면 '비즈니스 로직을 어디에 두는 것이 뭐가 고민이야?' 할 수 있습니다.
아토믹 디자인 패턴에서 늘 문제는 컴포넌트가 조금씩 복잡해지기 시작할 때 발생합니다. '원자, 분자 단위까지는 비즈니스 로직을 두지 마세요!' 라고 명확하게 말할 수 있었지만, UI 컴포넌트가 복잡해지기 시작하는 유기체에서는 그렇지 않습니다.

아토믹 디자인 패턴에서 가능한 원자, 분자,유기체 단위에는 비즈니스 로직을 두지 않는 것이 가장 이상적입니다.

하지만 유기체 단계에서는 어떠한 경우에는 비즈니스 로직을 둬야할 수도, 어떠한 경우에는 필요 없을 수도 있습니다.
예를 들어, 아래의 네비게이션 bar는 비즈니스 로직이 필요 없습니다. 위에 살펴본 팀블의 코드 처럼 필요한 값과 handler를 유기체에 전달해 충분히 구성할 수 있습니다.

하지만, 쇼핑몰 페이지의 상품들을 grid 형태로 보여주고 pagination을 해야하는 경우에는 유기체에도 비즈니스 로직이 필요할 수 있습니다.

가능한 원자, 분자, 유기체 단위에 비즈니스 로직을 두지 않기 위해 가능한 UI를 작게 나누거나, HOC(High Order Component)에서 비즈니스 로직을 처리하고 필요한 데이터를 아래로 전달하는 방법을 사용할 수 있습니다. 하지만, 첫 번째 방법은 1,2번 주제에서 언급한 문제들을 계속하여 마주할 수 가능성이 많고, 두 번째 방법은 과도한 Props drilling이 발생하여 유지보수에 불편함을 겪을 가능성이 존재합니다.

팀블의 경우 과도한 props drilling으로 인한 문제들을 마주했었습니다.
HOC를 사용해 최대한 유기체 단위에서는 비즈니스 로직을 처리하지 않도록 했지만, 유기체, 템플릿, 페이지로 점점 단계가 올라감에 따라 컴포넌트 레벨이 깊어져 페이지에서 원자사이에 많은 컴포넌트가 존재했습니다. 과도한 Props drilling으로 인해 props가 전달되는 과정이나 수정 과정에서 의도치 않은 human error들이 발생할 수 있는 데, 이 문제는 TypeScript를 도입하여 해결했습니다.

TypeScript 뿐 아니라 DIP(의존성 역전 원칙)을 사용하여 과도한 props drilling 문제를 해결하기도 했습니다.
팀블의 코드 일부를 통해 어떻게 DIP를 적용했는지 예시를 보여드리겠습니다.

아래는 프로필 Page 컴포넌트입니다. 그리고 <MyPageMain />은 Template 컴포넌트입니다.

export function ProfileById(props: ProfileByIdProps) {
	/* API call, state 등의 코드들 ... */
  return (
      <MyPageMain
          profileBox={<ProfileBox user={userInfo} editControl={profileEditControlBox} />}
          tendencies={<Tendencies user={userInfo} metaType={meta.type} isEditing={false} />}
          fields={<Fields field={userInfo.field} />}
          viewer={<DocumentViewer value={userInfo.description} />}
          ....
       />
   )
  };
 

일반적으로 Page에서 필요한 데이터를 Template으로 내려주고, Template에서 또 유기체,분자 등의 하위 컴포넌트로 내려주는 방식은 과도한 Props drilling을 야기할 수 있습니다. 이를 최대한 줄이기 위해, 팀블에서는 profileBox={<ProfileBox user={userInfo} .../>와 같이 Page 컴포넌트에서 Template에 사용되는 분자, 유기체 등에 필요한 데이터를 직접 넣어 children으로 내려주었습니다.

Template 컴포넌트인 MyPageMain에서는 아래 예시와 같이 단순히 전달받은 profileBox와 같은 분자나 유기체 컴포넌트를 렌더링해주기만 하면 됩니다.

/* template/MyPageMain */
export function MyPageMain(props: MyPageMainProps) {
  const { className, profileBox, tendencies, intro, fields, viewer, submit } = props;

  return (
    <StyledMyPageMain className={className}>
      {profileBox}
      <StyledBody>
      	....
        <StyledBodySection>
          {tendencies}
          {fields}
          {viewer}
          {submit}
        </StyledBodySection>
      </StyledBody>
    </StyledMyPageMain>
  );
}

이러한 방식을 사용하면 분자나 유기체 등의 하위 level의 컴포넌트는 필요한 데이터들을 props로 한 단계씩 전달 받을 필요가 없어 불필요한 props drilling이 감소합니다. 뿐만 아니라, Template 컴포넌트의 경우에도 비즈니스 로직에 대해서 전혀 상관할 필요없이 그저 렌더링만 해주면 되기 때문에 관리하기 간편해집니다.

관련 내용을 자세히 보고싶다면 DIP(Dependency Inversion Principle : 의존 역전 원칙
여기를 참조해주세요!

정리하지면, 아토믹 디자인 패턴에서 원자,분자,유기체 단위에는 가능한 한 비즈니스 로직을 두지 않는 것이 바람직합니다. 하지만 경우에 따라선 유기체 단위에 비즈니스 로직이 포함될 수 있으며, 이로 인해 다양한 문제들이 발생할 수 있습니다.
그렇기 때문에 앞서 언급한 문제들을 잘 이해하고, 각 상황에 맞는 적절한 해결 방법을 고민하면 아토믹 디자인 패턴의 장점을 극대화하여 사용할 수 있으리라 생각합니다!


3. 직접 사용하고 느낀 장점 👍

  • 적은 인원으로 빠른 개발이 가능하다.
  • 효율적인 협업, 디자이너와 개발자 간의 빠른 피드백이 가능하다.
  • 컴포넌트의 재사용성이 높다.
  • 체계적인 컴포넌트 관리가 가능하다.
  • 일관성 있는 디자인으로 개발이 가능하다.

4. 직접 사용하고 느낀 단점 👎

  • 개발자와 디자이너 모두에게 매우 큰 러닝 커브
  • 명확하지 않은 기준들로 인한 의견 충돌이 매우 많다.
  • 프로젝트의 규모가 커질 수록 유지보수와 관리가 급속도로 어려워진다.

5. 마무리

처음으로 아토믹 디자인 패턴을 접하고, 직접 프로젝트에 적용하며 정말 많은 시행착오를 겪었지만, 그 과정에서 많은 것을 배울 수 있어 매우 의미있는 경험이었습니다. 시행착오의 과정 속에서 배운 것들을 최대한 자세하게 공유하여 누군가에게 도움이 되고자 하는 마음에 글이 매우 길어졌음에도 불구하고, 여기까지 와주신 분들께 감사의 말씀을 전합니다.

끝으로, 이 글이 아토믹 디자인 패턴에 대한 고민을 가진 분들에게 조금이나마 도움이 되길 바라며, 잘못된 내용이나 궁금한 점이 있다면 언제든 편하게 알려주세요. 감사합니다!

6. Reference

https://atomicdesign.bradfrost.com/chapter-2/
https://yozm.wishket.com/magazine/detail/1531/
https://fe-developers.kakaoent.com/2022/220505-how-page-part-use-atomic-design-system/
https://danilowoz.com/blog/atomic-design-with-react
https://overthecode.io/the-meaning-and-limits-of-atomic-design-from-a-software-design-perspective/
https://medium.com/@wheeler.katia/thinking-about-react-atomically-608c865d2262

profile
Junior Frontend Developer

1개의 댓글

comment-user-thumbnail
2024년 10월 30일

설계 하는데 잘 참조 했읍니다.

답글 달기