FSD(Feature-Sliced Design) 아키텍처에 대해서 아시나요? 프론트엔드를 위해 탄생한 확장가능한 아키텍처에요. 핵심은 기능 중심으로 프로젝트 구조를 만드는 거에요. 이전에는 기술 중심으로 Components, APIs, Styles, Services 와 같이 나눴다면, FSD에서는 비즈니스 로직과 사용자의 기능 중심으로 나눠요.
FSD 아키텍처는 6가지 기능 중심으로 나눈 Layers로 구성되어 있고, 각 Layer에는 Slices가 존재하고, 각 Slice에는 Segment들이 존재해요.

Layer : 애플리케이션에서 어떤 역할을 하는지 책임(Responsibility) 기준으로 나눈 것 아래와 같이 6가지 구성됨.(조절 가능하며 기준도 다르게 적용 가능)
app: 애플리케이션 초기화, Provider, Router
pages: URL 단위 화면
widgets: 페이지를 구성하는 큰 UI 블록 (여러 features 집합), 보통 페이지에 재사용 가능한 단위로 구성
features: 사용자가 수행하는 행동 (좋아요, 로그인 등)
entities: 핵심 도메인 객체 (User, Product)
shared: 특정 도메인에 속하지 않는 재사용 코드, Slice와 Segment가 존재하지 않음.
Slice: 비즈니스 도메인 기준
user
post
...
Segment: 기술적 분할
ui
api
model
...
FSD 아키텍처는 하위 레이어에서는 상위 레이어를 사용할 수 없는 규칙이 있어요. 그리고 shared 레이어를 제외하면, 같은 레이어간의 cross-import도 허용하지 않아요. cross-import를 허용하는 경우에는 폴더명에 @x와 같은 폴더명을 추가하여, cross import된다는 것을 사용하는 곳에서 알 수 있도록 예외 규칙을 만들어 사용해요.

이제 이해를 돕기위해 FSD 아키텍처 폴더 구조 예시를 보여드릴게요.
src/├── app/ # 앱 초기화, 프로바이더, 라우팅│ └── providers/│├── pages/ # 라우트 단위 페이지 조합│ ├── login/│ └── cart/│├── widgets/ # 독립적으로 동작하는 UI 블록│ └── header/│├── features/ # 사용자 행위(시나리오) 단위│ ├── auth/ # 로그인│ │ ├── ui/│ │ │ └── LoginForm.tsx│ │ ├── model/│ │ │ └── useLogin.ts│ │ └── index.ts # public API (외부엔 여기로만 노출)│ └── add-to-cart/ # 장바구니 담기│ ├── ui/│ │ └── AddToCartButton.tsx│ ├── model/│ │ └── useAddToCart.ts│ └── index.ts│├── entities/ # 비즈니스 도메인 모델│ ├── user/│ │ ├── ui/│ │ │ └── UserAvatar.tsx│ │ ├── model/│ │ │ ├── types.ts│ │ │ └── userStore.ts│ │ ├── api/│ │ │ └── userApi.ts│ │ └── index.ts│ └── product/│ ├── ui/│ │ └── ProductCard.tsx│ ├── model/│ │ └── types.ts│ ├── api/│ │ └── productApi.ts│ └── index.ts│└── shared/ # 도메인을 모르는 공용 코드 ├── ui/ │ └── Button.tsx ├── api/ │ └── apiClient.ts ├── lib/ │ └── formatPrice.ts └── config/ └── env.tsFSD 아키텍처를 5가지 관점으로 평가해볼게요.
응집도 (cohesion)
결합도 (coupling)
탐색 용이성 (discoverability)
멘탈 모델 일치도
점진적 도입
응집도는 관련되어 있는 코드가 얼마나 뭉쳐있는가를 말해요. 흩어져 있으면 응집도가 낮은거에요.
변경할 1가지 이유로 함께 변경되어야하는 것을 관련되어 있는 코드로 보고 응집도가 높은것으로 봐요.
FSD 아키텍처는 이 Cohesion 관점에서 좋지 않다고 생각해요. 왜냐하면 실제로는 1개의 기능을 바꿨을때, 하위 레이어들까지 체크해야하는 경우가 생겨요.

위와같이 응집도가 낮다고 볼수 있기 때문에 아래와 같은 문제점이 생겨요.
인지 부하: 더 열어봐야할 파일이 많다는 것은, 더 많은 컨텍스트를 개발자가 갖고있어야 해요.
숨겨진 의존성: 1개의 레이어를 수정하면, 다른 레이어까지 영향이 갈 수 있어요. 하위 레이어든, 상위 레이어든 말이죠.
느린 코드 리뷰: 리뷰어는 하나의 변경을 이해하기 위해 여러 폴더를 체크해야할 필요성이 있어요.
어려운 온보딩: 새롭게 투입된 개발자는 하나의 폴더를 보고 어떻게 기능이 동작하는지 이해할 수 없어요.
결합도는 모듈이 서로 얼마나 독립적인지 나타내는 척도에요. 하나의 모듈을 변경할 때, 다른 모듈에 얼마나 영향 미치는지 나타내요.
어떤 모듈을 변경할때 다른 모듈에 영향을 줘서 기능하지 못하도록 만들었다면, 숨은 의존성을 가지고 결합도가 높은것을 의미해요.

FSD 아키텍처에서는 서로의 영역을 침범해야하는 기능이 아키텍처의 한계 때문에 원칙을 깨야하는 경우가 생겨요.(위 그림 예시 참고하면 슬라이스간 연결성이 생길 수 밖에 없는 것을 알 수 있음)
위 방법들 말고도 그러면 Widget에서 Add to Cart 컴포넌트를 합치면 되지 않느냐고 할 수 있지만, 그러면 결국 Layer를 나눈 기준을 하나씩 깨부수는거고 오히려 더 복잡해지겠죠?

위 그림을 보면 응집도와 결합도에 따른 의존성 및 응집도를 매트릭스로 나눠서 보여줘요. FSD 아키텍처의 경우에는 구조 자체로는 결합도가 낮아 보일 수 있지만 실제 사용해보면 그 한계로 약간 애매해요. 그래서 중간 정도의 결합도를 가진 아키텍처라고 생각해요. 결론적으로는 낮은 응집도의 중간 결합도 위치라고 생각해요.
탐색 용이성이 크면 어떤 기능과 역할에 대한 코드를 찾을때, 폴더 구조만 보고 금방 찾을 수 있어요. FSD 아키텍처는 탐색 용이성에 아주 심각한 문제가 있어요. 그 원인은 크게 2가지에요.
개발자마다 다른 layer 위치 선정으로 인한 문제 - 심지어 리뷰하는 사람이 레이어 선택 자체에 동의하지 못하고, 코드에 집중 못할 수 도 있음
낮은 응집도로
예를 들면 "검색창" 컴포넌트를 만든다고 해봐요. 이 "검색창"은 widget으로 볼지, feature로 볼지, entity로 볼지 개발자마다 다르게 생각해요. 그러면 A라는 개발자가 "검색창"을 만들어 놨을때, B라는 개발자는 어느 레이어에서 "검색창"을 찾을 수 있을지 확신할 수 없어요.
멘탈 모델은 팀이 생각하는 제품 개념과 폴더 구조가 일치하는지를 나타내요.
코드 구조는 팀이 제품에대해 어떻게 생각하는지 반영해요. 팀원들이 생각하는 제품과 코드가 일치하면 팀원들은 어떤 기능에 대해 만들거나 읽을때 아키텍처의 어느 부분을 나타내는지 생각할 필요가 없어요.

FSD 아키텍처는 기술 중심으로 Layer를 나누고 있어요. 그리고 기술 중심 Layer들은 실제 사용해보면 사람마다 생각하는 바가 다르기 때문에 확신을 가질 수 없어요. 그래서 항상 생각을 해야해요. 하지만 기능 중심으로 먼저 나눈다면, 어떤 기능을 생각할때 더 쉽게 폴더 구조와 생각을 매칭할 수 있어요.
만약 FSD 아키텍처의 Layer 기준으로 멘탈 모델을 제품 개념과 일치시켜야한다면, 많은 합의와 교육이 필요할 거라 생각해요. 그래야만 겨우 일치시킬거라 생각해요.
실제로도 멘탈 모델과 일치시키기 위해 FSD 아키텍처를 변형하고 개념화해서 쓰는경우가 매우 많아요.
개발 초기부터 아키텍처를 확정지을 수 없어요. 새롭게 요구사항이 들어오면서 아키텍처를 수정해야하는 경우가 발생해요.
아키텍처는 제품이 변화하면서 함께 변화해요. 그래서 보통 간단한 아키텍처에서 시작해서 점점 제품에 맞게 아키텍처가 변화해요.
하지만 FSD 아키텍처는 처음부터 6개 레이어, 슬라이스, 세그먼트를 적용해야해요. 물론 FSD 공식 문서를 보면 점진적 도입 가이드가 있지만 사실 기존 코드와 구조 자체가 다르게 시작하기 때문에 적용하기 매우 어렵다고 생각해요.
장점
Slice 레벨의 응집력이 좋아요.
순환 의존성이 없어요
FSD 아키텍처에 대한 문서화가 잘 되어있어요.
단점
기능 레벨에서 응집력이 안좋아요.
레이어 선택 결과가 달라질 수 있어요.
폴더 구조가 기술 중심으로 강제되요
점진적 도입이 사실상 힘들어요.
저는 FSD 아키텍처의 대안으로 Layered Architecture와 Vertical Slice를 활용하는 것을 제안드려요.
Layered Architecture(계층형 아키텍처)는 보통 소~중 규모의 아키텍처에서 추천되는 아키텍처에요. 계층형 아키텍처는 FSD와 같이 기술적 책임에 따라 코드를 구성하는 아키텍처에요. 웹 프로젝트에서는 보통 Shared, Presentation, Business Logic, Data Access, Router 등 역할에따라 나눠요. 백엔드 기준으로는 Controller, Service, Repository로 나누겠네요.
Layered Architecture(계층형 아키텍처)는 보통 소~중 규모의 아키텍처에서 추천되는 아키텍처에요. 계층형 아키텍처는 FSD와 같이 기술적 책임에 따라 코드를 구성하는 아키텍처에요. 웹 프로젝트에서는 보통 Shared, Presentation, Business Logic, Data Access, Router 등 역할에따라 나눠요. 백엔드 기준으로는 Controller, Service, Repository로 나누겠네요.

그러면 계층형 아키텍처는 5가지 관점에서 종합 평가하면 어떨까요?
응집도 (cohesion) - 기능에 따라 나뉘지 않아 응집도가 낮음
결합도 (coupling) - 팀이나 프로젝트에 따라 달라짐
탐색 용이성 (discoverability) - 단순한 구조로 쉽게 탐색 가능
멘탈 모델 일치도 - 기술적으로 구조를 나눠 멘탈 모델과 일치하진 않음
점진적 도입 - 쉽게 시작 가능
Vertical Slice는 아키텍처를 기술 중심으로 먼저 나누지 않고, 기능 중심으로 나눠요. 보통 layered Architecture에서 하나의 기능들이 너무 커지면 이를 Vertical Slice로 나누기 시작한다고 보면 되요.

응집도 (cohesion) - 하나의 기능을 위한 코드가 하나의 폴더에 있어 높음
결합도 (coupling) - 팀/프로젝트에 따라 달라짐
탐색 용이성 (discoverability) - 하나의 기능에 대한 코드가 하나의 폴더에 모두 있ㄷ음
멘탈 모델 일치도 - 비즈니스 기능과 폴더가 쉽게 매칭됨
점진적 도입 - 하나의 기능 폴더로 쉽게 시작 가능
FSD는 아키텍처는 순환 의존성 문제를 해결하고, 깔끔한 규칙으로 인한 슬라이스 단위의 좋은 응집도로 장점을 가지지만, 계층 선정 문제, 낮은 기능단위 응집도, 어려운 점진적 도입과 같은 문제들을 가져요. 그래서 실제로는 FSD 아키텍처 그대로 쓰기보다는 변형하고 재정의해서 쓰는 경우도 매우 많아요.
저 또한 FSD 아키텍처를 회사에서 사용하지만 사실 마음에 들지 않아요. 오히려 어떤 레이어에 위치해야 하는가에 대한 고민이 많아지고 소통 비용만 커지는 거 같아요. 그렇다고 소통하지 않으면 계속 애매한 상태로 흘러갈 수 밖에 없더라고요. 더군다나 어느 기능을 보려면 어떤 폴더를 통해 가야하는지 찾는게 훨씬 어려워지기도 했어요.
그래서 이럴바엔 Layered Architecture에 Feature Slice를 활용하여 도메인 별로 나누면 훨씬 유연해질거라 생각해요.
아무튼 여기에 더해 저는 다음 글로는 MFA(Micro Frontend Architecture)에 대해서도 소개할까 해요. 팀의 규모가 점진적으로 대규모가 되면 배포와 코드 관리까지 모두 나눠야하는 경우가 생길 수 있잖아요? 그래서 한차원 더 높은 단계에서 나누는 방법론에 대해 이야기하겠습니다.