본문 바로가기
💻 개발/Frontend

프론트엔드 프로젝트 구조를 위한 디자인 패턴 알아보기 (Atomic Design Pattern, Feature-Sliced Design)

by llddang 2025. 2. 5.

프론트엔드 프로젝트를 진행할 때, 가장 먼저 고민해야할 것 중 하나는 "어떤 폴더 구조를 사용할 것인가?" 이다.

프로젝트 구조를 잘 설계하면 유지보수성과 확장성이 높아지며, 개발 생산성에도 큰 영향을 미친다.

이번 글에서는 2 가지 프론트엔드 아키택처 패턴을 살펴보며, 각 방식이 언제 적합한지 알아보겠다!

 

 

1. Atomic Design Pattern

Atomic Design은 화학의 원자(Atom) 개념에서 영감을 받은 디자인 시스템이다.

쪼개지지 않는 최소 단위인 원자부터, 원자가 모여 만들어지는 분자, 분자가 결합되어 유기체가 생성된고, 유기체들이 모여서 하나의 템플릿을 구성하게 되며, 이 템플릿에 데이터가 결합되면서 페이지가 완성되는 흐름을 가진다. 

출처: https://www.andela.com/blog-posts/structuring-your-react-application-atomic-design-principles

  • 원자 (Atoms) : 더 이상 분해할 수 없는 최소단위의 디자인
  • 분자 (Molecules) : 두 개 이상의 원자로 구성되며, 하나의 단위로 함께 동작하는 UI요소의 간단한 그룹
  • 유기체 (Organisms) : 원자/분자/유기체로 구성된 비교적 복잡한 UI 구성으로, 인터페이스의 뚜렷한 부분을 형성
  • 템플릿 (Templates) : 레이아웃 내에 구성요소를 배치하고 디자인의 기본 콘텐츠 구조/골격 구조를 형성
  • 페이지 (Pages) : 템플릿의 인스턴스로, 템플릿에 데이터를 결합한 것으로 실제로 보여지는 UI 형성

 

그림을 통해 좀 더 자세히 알아보자!

출처: https://www.andela.com/blog-posts/structuring-your-react-application-atomic-design-principles

 

검색 창이 있는 헤더를 가지는 페이지를 예시로 설명해보겠다!

레이블 및 HTML TextInput 그리고 Button 요소가 원자라고 할 수 있다.

이후 해당 원자들이 모여서 간단한 Search Box를 형성하면 이를 모듈이라고 할 수 있고,

Logo 및 NavigationBar, Serach Box가 모여서 만들어진 Header를 유기체라고 설명할 수 있다.

Header 및 본문 내용, Footer 등이 모여서 하나의 디자인 골격을 보여주는 템플릿을 구성하고,

이후 해당 템플릿에 실제 data가 들어간 페이지를 형성하게 된다!

 

쇼핑몰 프로젝트를 예시로 실제 폴더 구조 형식을 살펴보자!

 

폴더 구조

src/
├── components/
│   ├── atoms/           # 최소 단위 컴포넌트
│   │   ├── Button/
│   │   ├── Input/
│   │   └── Typography/
│   │
│   ├── molecules/       # atoms의 조합
│   │   ├── SearchBar/   # Input + Button
│   │   ├── ProductCard/ # Image + Typography + Button
│   │   └── LoginForm/   # Input + Input + Button
│   │
│   ├── organisms/       # molecules의 조합
│   │   ├── ProductList/ # 여러 ProductCard의 조합
│   │   ├── Header/      # Logo + SearchBar + CartButton
│   │   └── CartList/    # 여러 CartItem의 조합
│   │
│   └── templates/       # 페이지 레이아웃
│       ├── MainTemplate/
│       └── AuthTemplate/
│
└── pages/
    ├── ProductListPage/
    ├── CartPage/
    └── LoginPage/

 

기본적으로는 atoms와 molecules, organisms, templates, pages 폴더로 나누어서 구현하면 된다.

하지만 프로젝트가 커지면 커질수록 서로 다른 그룹에 해당하는 내용을 atoms, molecules, organisms, templates 한 곳에 모아서 사용하기에는 너무 복잡해진다.

따라서 일반적으로 다른 아키텍처 패턴과 결합하여 사용한다.

 

 

 

2. Feature-Sliced Design

Feature-Sliced Design(FSD)은 프론트엔드 애플리케이션을 기능 단위로 분할하여 관리하는 아키텍처 방법론이다.
비즈니스 요구사항 변화에 유연하게 대응하며, 코드의 모듈화와 재사용성을 극대화하는 데 초점을 두고 있다.

 

그럼 FSD의 주요 개념인 계층 구조에 대해 알아보자!

 

출처 : https://feature-sliced.design/kr/docs/get-started/overview

 

세 개의 Layers, Slices, Segemnts가 수직 구조를 이루고 있다.

 

Layers 의 각 계층은 Slices로 이루어져있는데, 유일하게 Layer에서 App과 Shared 계층 만이 Slices가 없고, Segments로 이루어져있다. 그리고 각 Slices들은 Segment로 구성된다.

 

Layers의 주요 목적은 책임과 의존성에 따라 코드를 분리하는 것이다.
따라서 각 계층에 대한 명확한 의미가 있는 이름을 가지고, 의미 체계가 표준화되어 있기 때문에 권장되지 않는다.

 

Slices의 주요 목적은 제품, 비즈니스 또는 응용 프로그램에 대한 의미(도메인)에 따라 코드를 그룹화하는 것이다.
따라서 프로젝트에 맞는 비즈니스 도메인으로 직접 결정하도록 네이밍이 표준화되어있지 않다.

 

Segments의 주요 목적은 기술적 특성에 따라 코드를 그룹화하는 것이다.

따라서 몇가지 표준화된 이름(ui, api, lib, model 등)가 있지만 콘텐츠의 목적을 설명하는 새로운 이름을 생성해도 괜찮다.

 

각 계층의 주 목적을 살펴봤으니 이제 각 계층이 어떻게 이루어져있는지 살펴보자!

 

레이어 (Layers)

레이어는 책임과 의존성에 따라 코드를 분리하는 것으로 최상위 폴더의 src 밑에 위치한다.

출처: https://dev.to/m_midas/feature-sliced-design-the-best-frontend-architecture-4noj

  • 계층 구조
    • 총 7개의 표준화된 레이어로 구성되어 있지만, processes 계층은 더 이상 사용되지 않는다.
    • 레이어의 각 계층은 선택적으로 사용할 수 있지만, 계층마다 명확한 의미가 있기 때문에 폴더명을 준수해야한다.
  • import 규칙
    • 상위 계층에서는 하위 계층의 코드를 가져올 수 있지만, 그 반대는 안 된다.
      (예: features는 entities, shared를 참조할 수 있지만, entities는 features를 참조할 수 없다.)
    • 동일한 계층에서 같은 slice의 코드는 참조할 수 있지만, 다른 slice의 코드를 참조할 수 없다.
      (예: features/aaa 폴더는 이름이 "aaa"인 슬라이스이다.
             이때 동일한 aaa 슬라이스 내부의 features/aaa/lib/cache.ts는 참조할 수 있지만,
             동일한 계층의 다른 slice인 features/bbb의 어떤 파일에서도 코드를 참조할 수 없다.)
  • 추상화 수준
    • 상위 계층에서는 구체적인 비즈니스 로직이 구현되고, 
      하위 계층으로 갈수록 범용적이고 재사용 가능한 추상화된 코드가 위치한다.
    • 계층이 낮을수록 재사용성 ↑, 자율성 ↓

이러한 구조를 통해 코드의 재사용성을 높이고, 비즈니스 로직을 명확하게 분리하며, 확장 가능한 아키텍처를 구성할 수 있다!

 

그럼 각각의 레이어들에 대해 더 자세히 알아보자.

 

  1. app
    • 애플리케이션 로직이 초기화되는 곳이며, 애플리케이션의 진입점 역할을 한다.
    • Layer에서 shared 계층과 함께 슬라이스가 아닌 세그먼트로 구성된 계층이다.
    • 일반적으로 routes(라우터 설정), store(글로벌 스토어 구성), styles(전역 스타일), entrypoint(코드 진입점) 등의 세그먼트가 존재한다.
  2. processes (더 이상 사용되지 않음)
    • 여러 단계로 이루어진 등록과 같이 여러 페이지에 걸쳐 있는 프로세스를 처리한다.
    • 이 레이어는 더 이상 사용되지 않지만 여전히 가끔씩 마주할 수 있다.
  3. pages
    • 웹 사이트와 응용 프로그램의 페이지로 구성된다.
    • 일반적으로 페이지는 하나의 슬라이스에 해당하지만, 여러 개의 유사 페이지가 있는 경우 하나의 슬라이스로 그룹화할 수 있다.
      (예: 로그인, 회원가입, 비밀번호 찾는 페이지의 경우 auth라는 폴더 하위에서 관리할 수 있다.)
    • 쉽게 탐색할 수 있는 한 페이지의 슬라이스에 넣을 수 있는 코드 양에 제한이 없다.
      페이지에서만 사용되는 UI 블록은 페이지 슬라이스 내에 존재해도 괜찮다.
    • 페이지의 슬라이스는 ui(page의 ui 및 로딩 상태, 에러 바운더리)와 api(데이터 가져오기/변경요청) 등의 세그먼트가 존재한다.
  4. widgets
    • 페이지에서 독립적으로 사용할 수 있는 대규모 UI 블록으로 구성된다.
    • (: 앱의 어디서나 사용할 수 있는 로그인 다이얼로그가 필요하다면, 위젯으로 만드는 것이 좋다.)
  5. features
    • 사용자가 직접 상호작용하는 기능들로 구성되며, 여러 페이지에서 사용되어야 한다.
    • 예를 들어, 인스타그램에서 피드 검색, 팔로우/팔로워 검색, DM 검색과 같이 여러 페이지에서 공통으로 사용되는 검색창은 features에 정의될 수 있다.
    • 기능의 슬라이스는 ui, api, config, model(유효성 검사 및 내부 상태 관리) 등의 세그먼트가 존재한다.
    • (예: 검색창, 페이지네이션, 좋아요/공유 등)
  6. entities
    • 비즈니스 로직의 핵짐 객체들을 의미하며, 독립적으로 존재할 수 있는 도메인 단위를 의미한다.
    • entity 슬라이스는 ui(객체를 표현하는 컴포넌트), model(타입 정의 및 상태 관리) 등의 세그먼트가 존재한다.
    • (예: 유저 정보의 타입 및 유저 아바타 버튼 등)
  7. shared
    • 특정 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트와 유틸리티가 포함되어 있다.
    • Layer에서 APP 계층과 함께 슬라이스가 아닌 세그먼트로 구성된 계층이다.
    • 세그먼트의 이름은 콘텐츠의 본질이 아닌 목적을 설명해야한다
      따라서 components, hooks, types와 같은 네이밍은 올바르지 않다.
    • 일반적으로 아래와 같은 세그먼트가 존재한다.
      • api: API client 및 인터셉터, 공통된 요청/응답 처리 설정
      • ui: 범용적인 UI 컴포넌트 (Button, Input, Modal 등)
      • lib: 내부 라이브러리의 모음
      • config: 환경 변수, 전역 기능 플래그 및 앱에 대한 기타 전역 구성
      • routes: 경로 설정를 위한 경로 상수 또는 패턴
      • i18n: 번역, 글로벌 번역 문자열을 위한 설정

출처: https://dev.to/m_midas/feature-sliced-design-the-best-frontend-architecture-4noj

 

 

지금까지 FSD가 어떻게 계층을 형성하는지 알아보았다.

쇼핑몰 프로젝트를 예시로 실제 폴더 구조 형식을 살펴보자!

 

폴더 구조

src/
├── app/
│   ├── index.tsx
│   └── styles/
├── pages/
│   └── product/
│       ├── list/
│       │   └── index.tsx
│       └── detail/
│           └── index.tsx
├── features/
│   └── search/         # 검색 기능
│       ├── ui/
│       │   ├── SearchBar.tsx
│       │   └── SearchResults.tsx
│       └── hooks/
│           └── useSearch.ts
└── shared/             # 공통 컴포넌트/유틸리티
    ├── ui/
    │   └── Button.tsx
    ├── layout/
    │   ├── Header.tsx
    │   └── Footer.tsx
    └── api, route, etc

 

Layer의 모든 계층을 전부 사용할 필요는 없다. 프로젝트의 규모와 목적에 맞게 적절한 Layer를 설계하여 사용하면 된다.

 

 

 

이번에 Atomic Design Pattern 과 Feature-Sliced Design 에 대해 알아보았다.

하지만 이 방법 외에도 명확한 폴더 구조 규칙을 가지고 있지는 않지만 페이지 별로 나누어서 개발하는 방식도 있고, 페이지가 아닌 도메인 별로 나누어서 개발하는 방식 등 다양하게 있다.

Atomic Design Pattern에서도 단순히 원자,분자,유기체 들로 나누면 프로젝트가 커졌을 때, 파일을 찾기 힘들다.
따라서, 도메인/페이지 별로 폴더를 나눈 뒤 Atomic Design Pattern을 주로 적용하거나, 각각의 폴더에서 도메인/페이지별로 나누어 관리할 것 같다.

또한 Feature-Sliced Design의 경우 각 Slice를 나누는 기준이 도메인 혹은 페이지가 될 것 이다.

 

이처럼 각각의 패턴을 적용하더라도, 컴포넌트의 독립성과 확장성, 재사용성 측면에서 별도의 기준(도메인/페이지)로 나누게 되는 것 같다!

 

 

후기

하... Atomic Design Pattern 을 처음에 봤을 때 각각의 컴포넌트를 나누는 기준은 애매모호하지만 이해하는데 어렵다고 느끼지는 않았다.

하지만 Feature-Sliced Design을 찾아보니까, 각 계층마다 명확한 의미가 있어 각 의미가 무엇인지 이해해야하며, 어떻게 설계해야하는지 방안도 다 나와있어서 하나의 프레임워크를 공부하는 느낌을 받았다. 확실히 사용하기 위해서 공부해야하는 양은 엄청나지만 기준이 명확하고, 예제 및 공식문서도 있어서 다른 사람과 협업하기는 좋을 것 같다.

또한 Domain Driven Design도 찾아봤지만 자세한 내용은 백엔드에 사용하기에 적절한 내용 같았다. 또한 프론트엔드에서 도메인 주도 개발을 한다고 했을 때 정해진 폴더 구조 없이 그저 도메인 기준으로 폴더를 나누어서 개발한다는 의미인 것 같아.. 작성하려다가 삭제하였다.

 

 

참조