최근 프론트엔드 커뮤니티를 중심으로 Feature-Sliced Design에 대한 관심이 이전에 비해 많아졌다고 느낍니다.
저같은 경우에는 클린아키텍처를 프론트엔드에 적절히 적용할 방법을 고민하다가 접하게된 방법론이기도 한데 꽤 마음에 드는 방법론이었어서 개인 프로젝트는 물론 사내 프로젝트도 점차 Feature-Sliced Design을 따르는 형태로 리팩토링한 경험이 있습니다.
이렇게 제가 처음 Feature-Sliced Design을 접하고 지금까지 꽤 많은 프로젝트를 Feature-Sliced Desgin을 통해 진행하면서 느꼈던 인사이트들을 공유해보고자 글을 작성해봅니다.
Feature-Sliced Design이란?
https://feature-sliced.design/docs/branding
놀랍게도..(?) Feature-Sliced Design에 대한 공식적인 Branding Guidelines이 존재합니다.
- ✅ Correct: Feature-Sliced Design, FSD
- ❌ Incorrect: Feature-Sliced, Feature Sliced, FeatureSliced, feature-sliced, feature sliced, FS
해당 문서에 따르면 다음과 같은 표기법이 Correct 하다고 표현합니다.
눈치채셨을지 모르겠지만 해당 글 또한 위 브랜딩 가이드라인을 존중하여 모든 표기는 Feature-Sliced Design, FSD로 작성되었습니다.
표기에 대한 이야기는 이정도면 충분한 것 같으니 아래 사진으로 넘어가봅시다.
공식문서에서 다루는 FSD의 기본적인 아키텍처 구조를 도식화한 사진입니다.
FSD를 다루는 대부분의 포스트에서 다룰만큼 이해가 쉽도록 작성된 사진입니다.
문서에서는 각 계층을 이렇게 다루고 있습니다. 간단히 표로 작성해보면 다음과 같은 형태인데요
app | 앱 전체의 설정을 하는 곳으로 style, provider가 들어갑니다. |
processes | 복잡한 페이지간의 시나리오 (인증) 등을 배치하는 레이어입니다. |
pages | 말그대로 페이지입니다. |
widgets | 엔티티와 기능을 의미 있는 블록으로 결합하는 레이어입니다. |
features | 사용자 상호작용을 수행하는 레이어입니다. |
entities | 우리의 도메인이 들어갈 레이어입니다. |
shared | 공통 유틸리티와 같은 코드들이 들어갑니다. |
여기에 추가적으로 몇가지의 규칙을 더합니다.
1. 각 레이어는 자신보다 상위에 있는 레이어만 의존할 수 있다. (app은 모든 레이어에 의존가능하지만 shared는 다른 레이어에 의존할 수 없습니다. )
2. index.ts 파일을 통해 하위 레이어가 진입할 수 있는 코드를 명시한다
이 규칙들을 기본으로하며 각 레이어에서는 크게 4개의 하위 폴더구조를 지닐 수 있습니다.
다음 사진이 해당 예시입니다. 일반적으로 사용되는 코드들의 배치 가이드라고도 볼 수 있는데요
lib는 선택 세그먼트라고 강조하는 반면.. 실제로 프로젝트에 적용해보면 lib이 굉장히 많이 사실상 제일 많이 필요했습니다.
생각해보면 프론트엔드가 복잡한 이유는 보통 model을 정의하기가 어렵다거나 api 연동이 어렵다거나 하는게 아니라
사용자 상호작용의 밸리데이션, 그에 따른 적절한 UI 인터랙션, 사용자 상호작용 값을 API가 요구하는 스펙으로 변환 등의 일로 인해 발생하는 경우가 상당히 잦은데 api | model | ui 만으로 나누어보았을 때에는 그런 로직들을 배치할 곳이 마땅치않죠
그러다보니 자연스럽게 lib 파일이 커지는 감이 있었습니다.
아무튼 이렇게 코드들을 역할과 성격에 따라 vertical 하게 쪼개두고 의존성의 방향을 한 방향으로 제한하는걸 통해 코드를 유지보수하기 쉽게 만들자! 가 기본 골조라고도 생각할 수 있습니다.
기본골조 이야기가 나왔으니 위와 같은 방법론을 살짝 접어두고 FSD의 기본 원칙을 잠시 확인해봅시다.
Feature-Sliced Design 에 대한 개념은 2018년에 발표되었습니다. 그 이후 processes 레이어가 Deprecated되는 등 방법론에는 여러 변화가 일어났지만 Feature-Sliced Design에 대한 기본적인 원칙은 변하지 않았습니다. 아래는 그 원칙들을 다룹니다.
1. 표준화된 프론트엔드 프로젝트의 구조 구축
2. 비즈니스 로직을 우선하여 애플리케이션을 분할
3. 드러나지않는 사이드이펙트 , 순환종속성을 방지하기 위한 레이어 격리
4. 모듈의 내부를 드러내지 않기 위한 Public API 사용 (여기서 내보내지않은 것은 private하다고 봅니다.)
위에서부터 잠깐잠깐 다루었던 Public API란 다음과 같은 코드를 의미합니다.
**/**/index.ts
export { Form as AuthForm } from "./ui"
export * as authFormModel from "./model"
이렇게 배럴 익스포트의 형태로 각 레이어에서 외부에 공개하고자 하는 코드들을 export 하는 행위를 의미해요
FSD가 무엇인지, 어떻게 사용하는지에 대한 예시나 그를 다루는 글들은 많은 편이니 더이상의 세부적인 내용을 다루기보다는 이제 직접 사용하면서 느꼈던 장점과 단점을 이야기해보겠습니다.
직접 Feature-Sliced Design을 사용해보면서 느꼈던 단점
1. 단체 프로젝트의 경우 팀원들을 온보딩 시키는 시간이 필요하며 초기 생산성이 낮아진다.
어떤 아키텍처 방법론을 적용하여도 동일하게 발생하는 문제입니다.
방법론은 복잡성을 가지고 있으며 이를 사용하기 위해 일종의 러닝커브가 반드시 필요합니다.
저같은 경우에도 처음 FSD를 적용하면서 특정 코드를 어디에 배치하는게 옳은지에 대해 오랜 고민을 하기도 했었는데요
모든 팀원들이 이렇게 어떻게하면 좋을지를 고민하는데 시간을 쏟거나, 잘 알고 있는 팀원 한명에게 코드를 배치하고 작성하는 일을 의존하게된다면 팀 전체의 생산성은 일시적으로 매우 낮아집니다.
2. 계층 분리 과정에서 Colocation이 깨진다.
규모가 작은 프로젝트일수록 치명적이게 다가오는 단점입니다.
주로 entities와 features, widgets를 분리하는 과정에서 Colocation이 깨지게되는 경우가 잦은데요
규모가 작은 프로젝트에서는 명확한 계층 분리로 인한 장점보다 관련있는 코드간의 Colocation이 깨지게되면서 얻는 단점이 더 큰 경우가 많았습니다.
3. 요구사항은 아키텍처를 생각해주지 않는다.
요구사항이 FSD를 준수하지 못하도록 유도하거나 억지로 FSD를 준수하게하면서 비효율적인 코드를 작성하게 되는 경우가 있습니다.
4. Barrel export를 이용하는 것으로 인한 문제
FSD는 barrel export 를 통하여 Public API를 정의합니다.
그런데 이 barrel export를 이용하는 과정에서 자바스크립트의 모듈시스템은 barrel export에서 가져오는 파일들을 한번씩 실행시키는 문제가 존재하는데요.
이것으로인한 사이드이펙트는 물론 next.js app router를 이용하는 경우 코드를 수정해야하는 일도 있었습니다.
예를 들면 use client 지시문이 포함된 클라이언트에서만 사용하는 코드들이 배럴 익스포트 과정에서 서버에서도 실행되게되며 에러가 발생하는 일들을 겪기도했습니다.
5. index.ts 파일로 진입점을 설정하는 것으로 인한 리팩토링 문제
저는 vscode만 사용하다보니 다른 IDE의 경우는 모르겠지만 vscode의 경우에는 해당 코드에 직접 의존하는게 아니라 index.ts에 의존하다보니 폴더 구조를 바꾸거나 네이밍을 바꾸는 과정에서 오버헤드가 생기는 경험을 했습니다.
예를 들면 함수의 이름을 리팩토링하는 경우 해당 함수의 이름만 리팩토링하는게 아니라 index.ts의 내보내기까지 해당 이름으로 리팩토링해주어야하며 결국 외부 파일들은 index.ts에 의존하기 때문에 index.ts를 지우게되면 경로 resolve를 일일히 다 해주어야하는 점이 리팩토링에 큰 스트레스로 다가왔습니다.
물론 처음부터 코드를 바꿀 일 없게 작성하면 발생하지 않는 문제이지만 실제로 그런 일은 일어나지 않는다는 것을 우리 모두가 알고있지않나요?
직접 Feature-Sliced Design을 사용해보면서 느꼈던 장점
위에서 잔뜩 단점들을 나열했지만 그와 반대로 장점도 많이 발견할 수 있었습니다.
1. 도메인 로직과 공유 로직의 명확한 계층 분리
어떤 코드가 내 프로젝트의 맥락과 상관이 없는지, 어떤 코드가 내 프로젝트의 맥락과 상관이 있는지가 매우 명확해졌습니다.
따라서 좀 더 비즈니스 맥락에 집중할 수 있고 특정 비즈니스 맥락을 수정하기 위해 어디로 가야하는지도 매우 명확해졌습니다.
2. 특정 맥락의 코드들의 배치가 너무나도 명확해짐
provider, configs, global style 등의 코드들은 어떤 프로젝트를 하든 고정적으로 있어야하는 코드들이라고 생각합니다.
이런 코드들의 배치가 매우 명확해지다보니 자연스럽게 해당 코드들의 위치를 쉽게 예측 가능하고 배치하기가 편리해졌습니다.
3. colocation 효과를 볼 수 있음
단점 부분에서는 계층 분리 과정에서 코로케이션이 깨지는 것을 꼽았지만 또 다른 관점에서는 코로케이션 효과가 강화됩니다.
특정 비즈니스 맥락의 수정이 필요할 때 이미 그 비즈니스 맥락을 기준으로 폴더를 구분해두었기 때문입니다.
내가 느꼈던 FSD의 단점들을 보완할 방법이 없을까?
단점에서는 구구절절 이게 안좋다 이게 안좋다 비판했지만 그럼에도 불구하고 FSD가 제시하는 구조는 기존 구조에 비해 장점이 있었습니다.
따라서 FSD의 단점이라고 느꼈던 부분들을 수정하여 저만의 새로운 규칙을 만들었습니다.
FSD의 철학에는 반하는 행동이기 때문에 "아.. 이렇게 생각해서 규칙을 바꿨구나" 정도로 이해해주시면 감사하겠습니다.
1. index.ts는 사용하지 않는다.
배럴 익스포트를 사용하면 외부 계층에 공개할 api를 명시한다.와 import 구문이 깔끔해진다. 라는 점이 장점으로 다가옵니다.
하지만 이런 장점에 비하여 배럴 익스포트를 사용하는 것은 단점이 더 크다고 판단했습니다.
폴더구조 변경 , 함수 이름 변경 등의 작업에 있어 리팩토링의 용이성이 줄어들고 배럴 익스포트를 사용하는 것으로 인한 문제까지 감당해야합니다.
따라서 index.ts는 사용하지 않기로 했습니다.
2. widgets, features 계층의 사용을 최소화하고 가능하다면 entities에서 모든 로직을 정의한다.
엄격한 featuers / widgets 계층 분리는 충분히 큰 프로젝트에서는 효과가 있을 수 있지만 프로젝트가 충분히 커지기 전에는 코로케이션이 깨짐으로 인해 발생하는 손해가 더 크다고 판단했습니다.
또한 실제로 코드를 구성해보다보면 widgets, features를 나중에 entities에서 분리하는 작업도 크게 어렵지 않겠다는 생각이 들더라구요
3. 요구사항이 FSD의 레이어 구조를 준수하면서 구현하는 것이 불가능한 경우에 한해 레이어 구조를 위반하거나 shared 계층으로 상향시킨다.
실제로 구현을 하다보면 비즈니스 요구사항에 따라 계층구조를 어기거나 계층 구조에 맞게 코드 구조를 크게 변경하지 않으면 구현이 불가능한 경우가 종종 있었습니다.
이런 경우에는 계층 구조를 해당 부분에 한해 어기거나 해당 코드를 shared 계층으로 상향시키는 식으로 처리를 했습니다.
Bonus. 계층 구조와 import 구문의 순서가 똑같으면 자연스럽게 계층 구조를 인식하기 쉬워지지 않을까?
라는 생각으로 다음과 같은 오픈소스 라이브러리를 만들었습니다.
https://github.com/xionhub/fsd-prettier
prettier plugin을 이용하여 다음과 같이 import 구문을 자동으로 정렬할 수 있는 구성을 만들었습니다.
관심이 생기신다면.. 한번 써주시면 좋겠네용
마치며
FSD를 적용해보면 적용해볼수록 모든 프로젝트에 적합한 실버불렛같은 방법론은 아니라는 생각이 듭니다.
그래서 저는 FSD의 철학에는 반할 수 있겠지만 어느정도 자신의 프로젝트에 맞게 규칙들을 변경하거나 추가하여 사용하는 것도 괜찮은 방법이 될 수 있을 것이라고 생각해요.
읽어주셔서 감사합니다.
'frontend' 카테고리의 다른 글
모노레포에서 tailwindcss를 쓰는 경우엔 설정을 어떻게 해야할까? (1) | 2024.06.20 |
---|---|
모노레포에서 Internal Packages를 관리하는 3가지 방법 (0) | 2024.06.13 |
변경에 자유로운 프론트엔드 코드 작성하기 (1) | 2024.05.25 |
프론트엔드 테스트 환경 설정하기 (0) | 2024.05.22 |
내가 쓰는 프론트엔드 코딩 컨벤션과 네이밍 컨벤션 폴더구조 (0) | 2024.05.10 |