대규모 프론트엔드 구성 LIFT vs. 피처 슬라이스 디자인 딜레마
Lukas Schneider
DevOps Engineer · Leapcell

소개
프론트엔드 애플리케이션의 복잡성과 규모가 커짐에 따라 코드베이스를 구성하는 방식이 매우 중요해집니다. 잘 정의된 구조 없이는 프로젝트가 빠르게 관리 불가능한 혼란으로 이어져 개발 주기 지연, 버그 증가, 신규 팀원들의 높은 학습 곡선을 초래할 수 있습니다. 이러한 과제를 해결하기 위해 두 가지 주요 아키텍처 패턴이 등장했습니다: LIFT(Locating Logic Intuitively Fast)와 피처 슬라이스 디자인(FSD). 둘 다 모듈성과 유지보수성에 대한 고유한 접근 방식을 제공하지만, 서로 다른 철학과 프로젝트 요구 사항을 충족합니다. 이 글에서는 LIFT와 FSD를 분석하고, 원칙, 구현 전략, 적합한 사용 사례를 비교하여 차세대 대규모 프론트엔드 프로젝트에 대한 정보에 입각한 결정을 내릴 수 있도록 돕겠습니다.
핵심 개념 설명
LIFT와 FSD의 복잡한 내용으로 들어가기 전에, 이 논의의 핵심이 되는 몇 가지 기초 용어를 명확히 하겠습니다.
- 모듈성: 시스템의 구성 요소를 분리하고 재조합할 수 있는 정도. 높은 모듈성은 구성 요소가 독립적이고 상호 교환 가능하여 개발 및 유지보수를 용이하게 함을 의미합니다.
- 관심사 분리: 컴퓨터 프로그램을 기능적으로 최대한 겹치지 않는 별개의 기능으로 분할하는 원칙. 각 구성 요소 또는 모듈은 이상적으로 하나의 특정 관심사를 처리해야 합니다.
- 캡슐화: 데이터와 해당 데이터를 연산하는 메서드를 함께 묶고 구성 요소의 일부에 대한 직접적인 액세스를 제한하는 것. 이는 종종 파일 또는 폴더 경계를 통해 달성됩니다.
- 도메인 중심 설계(DDD): 도메인 모델 정의에 중점을 둔 소프트웨어 개발 접근 방식. 프론트엔드에서는 기술적 관심사보다는 비즈니스 개념을 중심으로 코드를 구성하는 것으로 해석됩니다.
- 확장성: 증가하는 작업량을 처리할 수 있는 시스템의 능력 또는 그 성장을 수용하기 위해 확장될 수 있는 잠재력. 코드 구성에서 이는 패턴이 번거로워지지 않고 효율적으로 다수의 파일과 기능을 지원할 수 있음을 의미합니다.
LIFT: 직관적으로 빠르게 논리를 찾기
원칙과 철학
LIFT는 Locating Logic Intuitively Fast의 약자로, Angular에 의해 대중화된 지침 집합입니다. 핵심 철학은 프로젝트 규모에 관계없이 개발자가 관련 코드를 빠르게 찾을 수 있도록 하는 데 중점을 둡니다. LIFT는 네 가지 주요 규칙을 제안합니다:
- 위치: 특정 기능에 대한 파일은 직관적인 단일 위치에 있어야 합니다. 일반적으로 기능과 관련된 파일을 자체 디렉터리에 함께 그룹화하는 것을 의미합니다.
- 식별: 파일 이름은 내용이 무엇인지 즉시 나타내야 합니다. 예를 들어,
user-list.component.ts
는 사용자 목록에 대한 Angular 구성 요소임을 명확하게 식별합니다. - 평면: 기능이 크게 성장할 때까지 폴더 구조를 가능한 한 평평하게 유지합니다. 이렇게 하면 탐색이 번거로울 수 있는 깊은 중첩을 피할 수 있습니다.
- DRY(Don't Repeat Yourself)를 시도하십시오: 중복 코드를 피하십시오.
LIFT의 주요 목표는 개발자 경험과 검색 용이성입니다. 개발자가 특정 기능에 대해 작업해야 할 때, 해당 전용 폴더로 빠르게 이동하여 관련 파일을 모두 찾을 수 있어야 합니다.
구현 및 예시
LIFT 기반 구조에서는 일반적으로 최상위 디렉터리에 기능이 그룹화된 것을 볼 수 있습니다. 각 기능 디렉터리 내부에는 해당 구성 요소, 서비스, 모델 및 테스트가 포함됩니다.
간단한 전자 상거래 애플리케이션을 생각해 보겠습니다. LIFT 구조는 다음과 같을 수 있습니다.
src/
├── app/
│ ├── core/ // 애플리케이션 전체 서비스, 인터셉터 등
│ │ ├── auth/
│ │ │ ├── auth.service.ts
│ │ │ └── ...
│ │ └── ...
│ ├── shared/ // 재사용 가능한 UI 구성 요소, 유틸리티 함수
│ │ ├── components/
│ │ │ ├── button/
│ │ │ │ ├── button.component.ts
│ │ │ │ └── button.component.html
│ │ │ └── ...
│ │ └── pipes/
│ │ └── ...
│ ├── features/ // 주요 비즈니스 기능
│ │ ├── product/ // 'product' 기능
│ │ │ ├── components/
│ │ │ │ ├── product-card/
│ │ │ │ │ ├── product-card.component.ts
│ │ │ │ │ └── product-card.component.html
│ │ │ │ └── product-list/
│ │ │ │ ├── product-list.component.ts
│ │ │ │ └── product-list.component.html
│ │ │ ├── services/
│ │ │ │ └── product.service.ts
│ │ │ ├── models/
│ │ │ │ └── product.model.ts
│ │ │ ├── product.module.ts
│ │ │ └── product.routes.ts
│ │ ├── cart/ // 'cart' 기능
│ │ │ ├── components/
│ │ │ │ ├── cart-item/
│ │ │ │ └── cart-view/
│ │ │ ├── services/
│ │ │ └── ...
│ │ └── user/
│ │ └── ...
│ └── app.component.ts
│ └── app.module.ts
│ └── app.routes.ts
└── environments/
└── main.ts
이 예시에서 product
기능과 관련된 모든 파일(구성 요소, 서비스, 모델, 라우팅)은 src/app/features/product
디렉터리 내에 있습니다. 이렇게 하면 제품 관련 기능에 대해 작업할 때 모든 관련 코드를 직관적으로 찾을 수 있습니다.
애플리케이션 시나리오
LIFT는 특히 다음과 같은 경우에 적합합니다.
- 소규모에서 중간 규모 애플리케이션: 기능 수가 관리 가능하며 개발자가 전체 구조를 쉽게 파악할 수 있는 경우.
- 빠른 기능 탐색을 우선시하는 팀: 코드 검색 속도가 개발자 생산성에 매우 중요한 경우.
- 명확한 도메인 경계가 있는 프로젝트: 기능이 정의되면 모든 내부 부분은 긴밀하게 결합됩니다.
하지만 애플리케이션이 성장함에 따라 기능 간의 경계가 모호해지기 시작하고, 기능 모듈 간의 종속성 관리가 어려워질 수 있습니다. 공유 구성 요소 또는 서비스가 다양한 기능 폴더에 있거나 shared
폴더가 덤프 사이트가 될 수 있습니다.
피처 슬라이스 디자인(FSD)
원칙과 철학
피처 슬라이스 디자인(FSD)은 대규모 및 복잡한 애플리케이션을 위한 고도로 확장 가능하고 유지보수 가능한 아키텍처를 만드는 것을 목표로 하는, 보다 확고하고 구조화된 코드 구성 접근 방식을 취합니다. FSD는 '슬라이스'와 '계층'의 개념을 도입하여 계층 간 통신 및 종속성 관리에 대한 엄격한 규칙을 적용합니다. 핵심 원칙은 다음과 같습니다.
- 계층: 애플리케이션은 특정 책임(예:
app
,pages
,widgets
,features
,entities
,shared
)을 가진 수평 계층으로 나뉩니다. - 슬라이스: 각 계층 내에서 구성 요소는 애플리케이션 도메인의 자체 포함된 부분을 나타내는 '슬라이스'(예:
product-card
,user-profile
)로 그룹화됩니다. - 수직적 결합, 수평적 고립: 특정 기능 또는 슬라이스와 관련된 코드는 계층 전반에 걸쳐 수직으로 그룹화되는 반면, 동일한 계층 내의 별도 슬라이스는 서로 분리됩니다.
- 엄격한 종속성 규칙: 상위 계층은 하위 계층에 종속될 수 있지만, 그 반대는 아닙니다. 이러한 단방향 흐름은 순환 종속성을 방지하고 강력한 아키텍처를 촉진합니다.
- 공개 API: 각 슬라이스는 캡슐화를 유지하고 외부 상호 작용을 제어하기 위해 잘 정의된 공개 API를 노출해야 합니다.
FSD는 아키텍처의 명확성, 캡슐화 및 확장성을 주요 목표로 강조합니다. 엄격한 규칙을 시행하여 코드베이스 엔트로피를 방지하고 애플리케이션이 성장함에 따라 복잡성을 더 쉽게 관리할 수 있도록 합니다.
구현 및 예시
FSD는 일반적으로 프로젝트를 계층에 기반한 계층적 구조로 구성한 다음, 해당 계층 내에서 슬라이스로 further 구성합니다.
src/
├── app/ // 애플리케이션 전체 로직, 라우팅, 전역 스타일
│ ├── providers/ // 전역 컨텍스트 제공자 (예: Redux 스토어, 인증 컨텍스트)
│ │ ├── store.ts
│ │ └── auth.ts
│ ├── layouts/ // 핵심 레이아웃 (예: 헤더, 푸터, 사이드바)
│ │ ├── base-layout/
│ │ │ ├── index.ts
│ │ │ └── ui.tsx
│ │ └── ...
│ ├── index.ts
│ └── styles/
│ └── global.css
├── pages/ // 전체 페이지 구성 요소, 기능/위젯의 조합
│ ├── home/
│ │ ├── index.ts
│ │ └── ui.tsx
│ ├── product-details/
│ │ ├── index.ts
│ │ └── ui.tsx
│ └── profile/
│ └── ...
├── widgets/ // 대시보드 위젯과 같은 관련 기능/엔터티의 조합
│ ├── product-list-widget/
│ │ ├── index.ts
│ │ └── ui.tsx
│ └── user-profile-card/
│ └── ...
├── features/ // 'add-to-cart'와 같은 특정 상호 작용을 포함하는 구체적인 비즈니스 로직
│ ├── add-to-cart/
│ │ ├── index.ts // `addToCartModel`과 같은 기능의 공개 API
│ │ ├── api/ // API 호출
│ │ ├── model/ // Redux 슬라이스, 상태 관리
│ │ ├── ui.tsx // UI 구성 요소, `model`에 의존할 수 있음
│ │ └── lib/ // 유틸리티
│ ├── sign-in/
│ │ └── ...
│ └── filter-products/
│ └── ...
├── entities/ // 'product', 'user', 'order' 와 같은 도메인 특정 엔터티
│ ├── product/
│ │ ├── index.ts // `productModel`과 같은 엔터티의 공개 API
│ │ ├── api/ // 제품 관련 API 호출
│ │ ├── model/ // 제품 데이터에 대한 Redux 슬라이스, 상태 관리
│ │ ├── ui.tsx // 제품 데이터 표시를 위한 UI 구성 요소 (예: 기본 제품 항목)
│ │ └── lib/ // 유틸리티
│ ├── user/
│ │ └── ...
│ └── ...
├── shared/ // 재사용 가능하고 일반적인 코드 (UI 구성 요소, 유틸리티, 구성, 라이브러리)
│ ├── ui/ // 일반 UI 구성 요소 (예: Button, Input)
│ │ ├── button/
│ │ │ ├── index.ts
│ │ │ └── ui.tsx
│ │ └── input/
│ │ └── ...
│ ├── lib/ // 유틸리티 함수 (예: date-formatter)
│ │ └── formatters.ts
│ ├── config/ // 애플리케이션 전체 상수
│ │ └── apiUrl.ts
│ └── api/ // 일반 API 유틸리티
│ └── baseApi.ts
└── index.tsx // 진입점
이 FSD 예시에서:
product-details
page
는product
entity
(데이터 표시용)와add-to-cart
feature
(상호 작용용)를 통합합니다.add-to-cart
기능은product
엔터티(무엇을 추가할지 알기 위해)와 잠재적으로shared/ui
의 버튼에 의존합니다.- 종속성은 항상 아래로 흐릅니다:
pages
는widgets
,features
,entities
,shared
를 사용할 수 있습니다.features
는entities
와shared
를 사용할 수 있습니다.entities
는shared
만 사용할 수 있습니다. 이 엄격한 규칙은 상위 수준 로직이 하위 계층으로 누출되는 것을 방지하고 명확한 경계를 유지합니다.
애플리케이션 시나리오
FSD는 다음과 같은 경우에 매우 효과적입니다.
- 대규모 및 매우 복잡한 애플리케이션: 장기적인 유지보수성과 확장성이 가장 중요한 경우.
- 대규모 팀: 많은 개발자가 애플리케이션의 다른 부분에서 동시에 작업하는 경우, FSD의 엄격한 규칙은 충돌을 최소화하고 아키텍처 일관성을 보장합니다.
- 높은 아키텍처 규율이 필요한 프로젝트: 기술 부채를 방지하고 깨끗한 아키텍처를 시행하는 것이 최우선 순위인 경우.
- 진화하는 요구 사항이 있는 애플리케이션: 모듈성은 중요한 리플 효과 없이 기능의 추가, 제거 또는 수정이 더 쉬워집니다.
FSD의 컨벤션 학습 및 엄격한 구조 설정에 대한 오버헤드가 초기에 높을 수 있어, 이러한 수준의 규율이 정당화되지 않는 매우 작고 빠르게 프로토타이핑되는 애플리케이션에는 덜 적합합니다.
결론
LIFT와 피처 슬라이스 디자인은 둘 다 대규모 프론트엔드 프로젝트를 구성하는 데 유용한 접근 방식을 제공하며, 각기 강점과 절충점을 가지고 있습니다. LIFT는 개발자의 직관과 빠른 코드 위치를 우선시하여 중소 규모 애플리케이션이나 빠른 기능 탐색을 중시하는 팀에 탁월합니다. 대조적으로, 피처 슬라이스 디자인은 매우 구조화되고 확장 가능한 프레임워크와 엄격한 종속성 규칙을 제공하여, 장기적인 유지보수성과 아키텍처 무결성이 중요한 대규모 복잡한 애플리케이션 및 대규모 팀에 이상적입니다. LIFT와 FSD 사이의 선택은 궁극적으로 프로젝트 규모, 팀 구조 및 장기적인 아키텍처 목표에 따라 달라집니다. 핵심 원칙을 이해함으로써, 팀이 강력하고 유지보수 가능한 프론트엔드 애플리케이션을 구축하는 데 가장 적합한 패턴을 선택할 수 있습니다.