어느덧 프로젝트 MVP 개발을 위한 최소한의 기능들과 디자인 적용 단계 전의 와이어프레임이 다 나왔고,
본격적인 구현에 앞서 정해야 할 것들 총 8520159개 중 이번에 정해야 할 주제는 바로바로 '폴더 구조'이다.
이번 포스트에서는 어떤 폴더 구조를 어떠한 과정을 통해 정하게 되었는지 그 과정을 다뤄보려고 한다.
함께 정리해 온 기능 설계서와 디자이너분이 작업해주신 와이어프레임을 쭈욱 살펴보면서 폴더 구조를 어떻게 나눠야 할까.. 고민 중에! FE 개발 팀원이자 스승님인 메타몽님에게서 메세지가 왔다
그렇다! 옛날에 폴더 구조를 찾아봐놓고 까먹고있었던 FSD 아키텍처를 선생님 덕분에 다시 끄집어 올 수 있었다.
개인 프로젝트가 아니라 대규모, 회사의 프로젝트 경우 회사에서 정해진 폴더구조를 이해하고 구현하면 되지만,
내 맘대로 폴더구조를 정해볼 수 있는 사이드 프로젝트인 만큼 다양한 폴더구조에 대한 경험을 늘려보는 것이 좋다고 생각하였고, 협업을 할 수 있는 폴더구조를 정하기위해서는 그 공식문서나 레퍼런스가 많은 아키텍처가 좋을 것이라고 생각하였다. 이미 정해진 틀이나 사용 사례가 많다면 협업을 할 때 보다 더 쉽게 소통이 되기 때문이다!
이러한 연유로 이번 기회에 FSD 아키텍처를 알아보고 우리 프로젝트에 적용하려고 한다.
프로젝트에 적용하기 위해서는 FSD가 무엇인지, 왜 등장하였는지, 어떤 특징을 가지고 있고 어떤 장단점이 있는지를 파헤쳐보기로 하였다. 공식문서와 함께 이미 잘 정리되어있는 블로그 글과 유튜브 등등을 보다가 '이거다!'하고 바로 정착해버린 블로그 글이 있었다.
프론트엔드 개발자 관점으로 바라보는 관심사의 분리와 좋은 폴더 구조 (feat. FSD)
최근 프론트엔드 개발에서 주목받는 FSD 아키텍쳐 폴더 구조를 주제로 소프트웨어 공학 관점에서의 관심사의 분리라는 원칙을 통해 설명하고자 했습니다. 이 글은 그동안 프론트엔드가 복잡성
velog.io
글을 읽다보면 자연스레 위에 던졌던 모든 질문들이 해결되었기에, 해당 글에서의 내용들을 가지고와서 나만의 언어로 다시 정리해보았다. 앞으로 등장할 FSD 개념 관련 모든 내용들은 윗 블로그 글과 함께 공식문서를 참고하였음을 밝힌다.
웹 개발을 위해 우리는 총 세 가지의 언어를 배웠다. 바로 HTML과 CSS, JS이다. 이 언어들은 다음과 같은 역할들을 각자 담당하고 있다.
> HTML : 구조, 틀 > CSS : 표현, 디자인 > JS : 기능, 동작
따라서 세 가지의 언어로 개발한 초창기의 웹은 '역할'별로 분리되는 구조였다. 이에 주목하고 계속 나아가보자.
그리고 여기에 Ajax(Asynchronous Javascript And XML)가 등장하면서 서버와의 통신, 즉 서버와 클라이언트의 역할이 자연스레 명확해졌다. 이렇게 서버와의 통신을 통해 데이터를 주고 받고, 이벤트를 제어하고, DOM을 조작하는 등의 역할을 수행하는 Frontend의 영역이 선명해진 것이다.
점차 Frontend가 해야 할 것들이 많아지면서 HTML + CSS + JS로만 구현되던 웹 페이지는 컴포넌트들의 조합이 되었다. HTML + CSS + JS로 만들어진 하나의 컴포넌트들을 쌓으면서 하나의 페이지를 만들 수 있어졌고, 다음과 같은 구조를 띄게 되었다.
하지만 언제나 문제는 프로젝트가 커지면서 나타난다. 프로젝트의 규모가 커지고 컴포넌트 간의 결합도가 점점 높아지면서 컴포넌트 1개의 크기가 비대해지고, props drilling과 같은 문제가 발생한다. 그러면서 자연스럽게 전역 상태 관리와 다양한 api 기능들이 나타난다. 그렇게 아까부터 주목해 온 많은 역할과 함께 점차 수정되어온 폴더 구조의 최종 형태는 다음과 같고, 이는 곧 널리 사용하게 된다.
기존 그대로 역할대로 아주 깔끔하게 분리가 되었지만, 기능적인 측면에서는 어떠할까?
만약 main 기능을 바꾸려면 역할별로 나눠진 많은 파일들을 찾아가고 수정해야 한다. 어떠한 기능에 어떤 역할들이 사용되었는지 일일히 기록하고 기억해야한다는 것이다. 결국 '역할'보다는 '기능'별로 묶자는 의견들은 점점 커지기 시작하였고, 그래서 등장한 것이 바로 이 FSD 아키텍처이다. 역할을 기준으로 하여 구분하던 구조를 '기능'에 맞춰서 구분하려는 것이다.
FSD 아키텍처
본 글에서 FSD의 내용과 특징에 대해서는 다루지 않으려고 한다. 최소한 각 레이어와 슬라이스, 세그먼트란 무엇인지와 그 특징들을 알고 있다는 전제하에 적어보았다..!
사실 폴더 구조에 전지전능하고 완벽한 정답이 어디있겠냐만은 그럼에도 FSD를 주목해야 하는 이유는 공식적인 형태를 갖추고 문서화가 되어있다는 것이다. 같은 기능을 구현하더라도 이를 구현하려는 사람이 100명이면 서로 다른 구조와 형태들이 100개가 나올 수도 있기에 대규모나 협업을 할 때 지침표와 같은 문서가 있다는 것은 결코 무시할 수 없는 특징이 될 것이다. 물론 공식적인 문서와 자주 사용되는 폴더 구조가 있다고 해서 이를 온전히 따라가려는 것은 옳지 못하다. 각 프로젝트마다 적합한 폴더 구조는 미세하게라도 다를 것이기 때문이다. 따라서 폴더 구조는 프로젝트의 크기, 그리고 팀원과의 소통과 함께 조금씩 변화를 주는 것이 가장 바람직하다.
그렇다면 우리 프로젝트에 FSD를 어떻게 도입하면 좋을까?
일단 MVP 개발을 위해 최소 기능만을 구현하기에 대규모 프로젝트처럼 모든 레이어와 세그먼트를 사용할 필요는 없다고 생각하였다. 우리 프로젝트에 맞도록 폴더 구조를 잘 맞춰보는 것이 중요하였다. 우리는 Next.js 프레임워크를 사용하기 때문에 app/ 디렉토리와 app 레이어의 네이밍이 겹쳐지면 안되었고, pages/ 디렉토리와 pages 레이어의 네이밍 또한 겹쳐지면 안되었다.
따라서 먼저 app/ 디렉토리와 FSD 아키텍처 개발을 위한 src/ 폴더로 나누었다.
app/은 Next.js에서 사용하는 그대로의 App Router용 폴더이며,
src/ 안에 전역 상태나 설정을 다루는 app 레이어와 pages 레이어의 역할을 하는 views 레이어로 나누었다.
├── app/ # Next.js App Router 폴더 (루트 디렉토리)
│ ├── layout.tsx # 글로벌 레이아웃
│ └── page.tsx # 기본 진입 페이지
├── src/ # FSD 기반 소스 폴더
│ ├── app/ # app 레이어 : 글로벌 설정 및 진입점
│ ├── views/ # views 레이어 : FSD 아키텍처의 pages 레이어 네이밍 변경
이후의 폴더 구조는 메타몽 스승님의 의견을 따라가기로 하였다. 우리가 가장 주목한 점은 크게 총 3가지였다. 첫 번째는 페이지 틀(views)와 비즈니스 로직의 분리에 초점을 두어 컴포넌트가 필요 이상으로 비대해지지 않게 하는 것이고, 두 번째는 반드시 위에서만 아래에 있는 레이어의 요소를 가져와 참조할 수 있다는 점이고, 세 번째는 각 레이어의 경계를 명확하게 잡고 갈 것이었다. entities 레이어는 비즈니스 도메인 데이터와 그 로직을 담고 있으며 이를 사용하여 features 레이어에서 재사용이 가능한 최소한의 기능을 넣은 컴포넌트를 만들고, widgets 레이어에서 컴포넌트들의 조합을 생성하여 마지막으로 views 레이어에서 페이지를 만들어 보여주는 것으로 경계를 잡고 갔다.
유독 헷갈리는 entities와 features 레이어는 개발을 진행하면서 코드 리뷰와 회의를 통해 꾸준히 변경해나가기로 하였다. 그렇게 결정된 초기 FSD 아키텍처를 사용한 폴더 구조는 이러하다. (메타몽님의 작품)
├── app/ # Next.js App Router 폴더 (루트 디렉토리)
│ ├── layout.tsx # 글로벌 레이아웃
│ └── page.tsx # 기본 진입 페이지
├── src/ # FSD 기반 소스 폴더
│ ├── app/ # 글로벌 설정 및 진입점
│ │ ├── stores # 전역 상태 설정
│ │ ├── styles # 전역 스타일 설정
│ ├── views/ # 페이지 디렉토리
│ │ ├── Home/ # 홈 페이지
│ │ │ ├── index.tsx # 홈 페이지 컴포넌트
│ │ │ ├── home.module.css # 홈 페이지 스타일
│ │ ├── StudyHome/ # 스터디 홈 페이지
│ │ │ ├── index.tsx # 스터디 홈 컴포넌트
│ │ │ ├── studyHome.module.css # 스터디 홈 스타일
│ │ └── MyPage/ # 마이 페이지
│ │ ├── index.tsx # 마이 페이지 컴포넌트
│ │ └── myPage.module.css # 마이 페이지 스타일
│ ├── widgets/ # 독립적 UI 컴포넌트
│ │ ├── Notification/ # 알림 위젯
│ │ │ ├── Notification.tsx
│ │ │ └── Notification.module.css
│ │ └── SearchBar/ # 검색바 위젯
│ │ ├── SearchBar.tsx
│ │ └── SearchBar.module.css
│ ├── features/ # 재사용 가능한 기능
│ │ ├── Auth/ # 인증 기능
│ │ │ ├── LoginForm.tsx
│ │ │ ├── useAuth.ts
│ │ │ └── authService.ts
│ │ ├── UserProfile/ # 유저 프로필 관리
│ │ │ ├── UserProfile.tsx
│ │ │ └── useUserProfile.ts
│ │ └── StudyManagement/ # 스터디 관리
│ │ ├── StudyForm.tsx
│ │ ├── useStudy.ts
│ │ └── studyService.ts
│ ├── entities/ # 비즈니스 도메인 데이터 및 로직
│ │ ├── User/ # 유저 엔티티
│ │ │ ├── model.ts # 유저 데이터 모델
│ │ │ ├── api.ts # 유저 API 호출
│ │ │ └── service.ts # 유저 비즈니스 로직
│ │ ├── Study/ # 스터디 엔티티
│ │ │ ├── model.ts
│ │ │ ├── api.ts
│ │ │ └── service.ts
│ │ └── Notification/ # 알림 엔티티
│ │ ├── model.ts
│ │ ├── api.ts
│ │ └── service.ts
│ └── shared/ # 공통 유틸리티 및 컴포넌트
│ ├── libs /
│ │ ├── dateUtils.ts
│ │ └── validate.ts
│ └── ui / # 공통 컴포넌트의 ui
│ ├── Avatar.ts
│ ├── Button.ts
│ └── Input.ts
우리는 처음부터 완벽한 FSD 폴더 구조를 구현해내는 것보다도 뷰와 비즈니스 로직의 분리를 가장 우선시하였고, 언제든 의논하여 변경해나갈 수 있도록 하여 끈끈한 협업을 통한 개발을 중점으로 여겼다. 추후 개발을 진행하다가 문제가 발생하면 즉시 의논하여 수정해나가기로 하며 FSD 초기 설정을 마무리할 수 있었다.