@@ -5,8 +5,7 @@ sidebar_label: 기존 아키텍처에서 전환하기
55
66# 기존 아키텍처에서 FSD로의 마이그레이션
77
8- 이 가이드는 기존 아키텍처를 ** Feature-Sliced Design(FSD)** 으로 단계별 전환하는 방법을 설명합니다.
9-
8+ 이 가이드는 기존 아키텍처를 ** Feature-Sliced Design(FSD)** 으로 단계별 전환하는 방법을 설명합니다.
109아래 폴더 구조를 예시로 살펴보세요. (파란 화살표를 클릭하면 펼쳐집니다).
1110
1211<details className =" file-tree " >
@@ -54,18 +53,18 @@ Feature-Sliced Design(FSD)이 **정말 필요한지 먼저 확인하세요.**
5453### 전환을 고려해야 할 징후
5554
56551 . 신규 팀원이 프로젝트에 적응하기 어려워하는 경우
57- 2 . 코드 일부를 수정할 때, 관련 없는 다른 코드에 오류가 발생하는 경우가 ** 잦은** 경우
56+ 2 . 코드 일부를 수정할 때, 관련 없는 다른 코드에 오류가 발생하는 경우가 ** 잦은** 경우
58573 . 새 기능을 추가할 때 고려해야 할 사항이 너무 많아 어려움을 겪는 경우
5958
6059** 팀의 합의 없이 FSD 전환을 시작하지 마세요.**
61- 팀 리더라도 전환의 이점이 학습· 전환 비용을 상회한다는 점을 먼저 설득해야 합니다.
60+ 팀 리더라도 전환의 이점이 학습/ 전환 비용을 상회한다는 점을 먼저 설득해야 합니다.
6261또한, 개선 효과가 바로 눈에 띄지 않을 수 있으므로 ** 팀원** 및 ** 프로젝트 매니저(PM)** 의 승인을 사전에 확보하고 이점을 공유하세요.
6362
6463:::tip PM 설득 시 고려할 사항
6564
66- - FSD 전환은 단계적으로 진행할 수 있어 기존 기능 개발을 중단하지 않아도 됩니다.
67- - 명확한 아키텍처 구조는 신규 개발자 온보딩 시간을 단축합니다.
68- - 공식 문서를 활용하면 별도 문서 유지·관리 비용을 절감할 수 있습니다.
65+ - FSD 전환은 단계적으로 진행할 수 있어 기존 기능 개발을 중단하지 않아도 됩니다.
66+ - 명확한 아키텍처 구조는 신규 개발자 온보딩 시간을 단축합니다.
67+ - 공식 문서를 활용하면 별도 문서 유지·관리 비용을 절감할 수 있습니다.
6968
7069:::
7170
@@ -75,50 +74,48 @@ Feature-Sliced Design(FSD)이 **정말 필요한지 먼저 확인하세요.**
7574
7675## 1단계: 페이지 단위로 코드 분리하기 {#divide-code-by-pages}
7776
78- 대부분의 커스텀 아키텍처는 규모와 관계없이 이미 어느 정도 페이지 단위로 코드를 나누고 있습니다. ` 📁 pages ` 폴더가 있다면 이 단계를 건너뛰어도 됩니다.
79-
77+ 대부분의 커스텀 아키텍처는 규모와 관계없이 이미 어느 정도 페이지 단위로 코드를 나누고 있습니다.
78+ ` 📁 pages ` 폴더가 있다면 이 단계를 건너뛰어도 됩니다.
8079
8180위에 예시 폴더처럼 ` 📁 routes ` 만 있다면 다음 순서를 따르세요.
8281
83- 1 . ` 📁 pages ` 폴더를 새로 만듭니다.
84- 2 . ` 📁 routes ` 에 있던 ** 페이지용 컴포넌트** 를 가능한 한 모두 ` 📁 pages ` 폴더로 옮깁니다.
85- 3 . 코드를 옮길 때마다 해당 페이지 전용 폴더를 만들고 그 안에 ` index ` 파일을 추가해 entry를 노출합니다.
82+ 1 . ` 📁 pages ` 폴더를 새로 만듭니다.
83+ 2 . ` 📁 routes ` 에 있던 ** 페이지용 컴포넌트** 를 가능한 한 모두 ` 📁 pages ` 폴더로 옮깁니다.
84+ 3 . 코드를 옮길 때마다 해당 페이지 전용 폴더를 만들고 그 안에 ` index.tsx ` 파일을 추가해 ** 진입점(entry point) ** 를 노출합니다.
8685
8786::: note
8887
89- 이 단계에서는 ** Page A에서 Page B의 코드를 import** 해도 괜찮습니다. 나중 단계에서 이러한 의존성을 분리할 예정이니, 우선 ** 페이지 폴더를 만드는 것** 에 집중하세요.
88+ 이 단계에서는 ** Page A에서 Page B의 코드를 import** 해도 괜찮습니다.
89+ 나중 단계에서 이러한 의존성을 분리할 예정이니, 우선 ** 페이지 폴더를 만드는 것** 에 집중하세요.
9090
9191:::
9292
93- route file:
93+ ** 📁 Route File **
9494
95- ``` js title="src/routes/products.[id].js"
96- export { ProductPage as default } from " src/pages/product"
95+ ``` js title="route file: src/routes/products.[id].js"
96+ export { ProductPage as default } from " src/pages/product" ;
9797```
9898
99- page index file:
99+ ** 📁 Page Index File **
100100
101101``` js title="src/pages/product/index.js"
102- export { ProductPage } from " ./ProductPage.jsx"
102+ export { ProductPage } from " ./ProductPage.jsx" ;
103103```
104104
105- page component file:
105+ ** 📁 Page Component File **
106106
107107``` jsx title="src/pages/product/ProductPage.jsx"
108108export function ProductPage (props ) {
109- return < div / > ;
109+ return < div / > ;
110110}
111111```
112112
113113## 2단계: 페이지 외부 코드를 분리하기 {#separate-everything-else-from-pages}
114114
115- 1 . ** ` 📁 src/shared ` 폴더를 만든다.**
116- - ` 📁 pages ` 또는 ` 📁 routes ` 를 ** import하지 않는** 모든 코드를 이곳으로 이동한다.
117- 2 . ** ` 📁 src/app ` 폴더를 만든다.**
118- - ` 📁 pages ` 또는 ` 📁 routes ` 를 ** import하는** 코드를 이곳으로 옮긴다. 라우트 파일도 여기에 포함한다.
115+ ** 📁 src/shared 폴더를 만들고,** 📁 pages 또는 📁 routes를 import하지 않는 모든(파일)은 이 폴더로 모읍니다.
116+ ** 📁 src/app 폴더를 만들고,** 📁 pages 또는 📁 routes를 import하는 모듈과 라우트 정의 파일은 이 폴더에 배치합니다.
119117
120- > ** Shared layer에는 slice가 없다.**
121- > 따라서 segment 간 import는 자유롭다.
118+ > ** Shared layer는 slice 개념이 존재하지 않기 때문에,** 서로 다른 segment 간에도 자유롭게 import할 수 있습니다
122119
123120이제 폴더 구조는 다음과 같아야 합니다:
124121
@@ -189,39 +186,40 @@ export function ProductPage(props) {
189186 </ul>
190187</details >
191188
192- ## 3단계: 페이지 간의 cross-imports 해결 {#tackle-cross-imports-between-pages}
189+ ## 3단계: 페이지 간 cross-imports 해결 {#tackle-cross-imports-between-pages}
193190
194191<!-- A good way to approach this is by setting up [Steiger][ext-steiger], the linter for FSD. -->
195192<!-- TODO: add instructions once the new config format is standardized -->
196193
197- 한 페이지가 다른 페이지의 코드를 가져오고 있다면 두 가지 방법으로 의존성을 제거한다.
198-
199- | 방법 | 사용 시점 |
200- | ------| -----------|
201- | ** A. 코드 복사** | 페이지마다 로직이 달라질 가능성이 있거나, 재사용성이 낮을 때 |
202- | ** B. Shared로 이동** | 여러 페이지에서 공통으로 쓰일 때 |
194+ 한 페이지가 다른 페이지의 코드를 직접 import하고 있다면, 아래 두 가지 방식 중 하나로 의존성을 정리합니다.
203195
196+ | 방법 | 사용 시점 |
197+ | ----------------------------------- | -------------------------------------------------------------- |
198+ | ** A. 코드 복사하여 독립시키기** | 페이지별로 로직이 달라질 가능성이 높거나, 재사용성이 낮은 경우 |
199+ | ** B. Shared로 이동하여 공통화하기** | 여러 페이지에서 반복적으로 사용되는 경우 |
204200
205201- Shared 이동 위치 예시
206- - UI 구성 요소 → ` 📁 shared/ui `
207- - 설정 상수 → ` 📁 shared/config `
208- - 백엔드 호출 → ` 📁 shared/api `
202+ - UI 구성 요소 → ` 📁 shared/ui `
203+ - 설정 상수 → ` 📁 shared/config `
204+ - 백엔드 호출 → ` 📁 shared/api `
209205
210206::: note
211207
212- 코드 복사는 잘못이 아니다. ** 중복보다 의존성 최소화** 가 더 중요할 때가 많다.
213- 다만 비즈니스 로직은 중복을 피해야 하며, 복사 시에도 DRY 원칙을 염두에 둔다.
208+ 코드를 복사하는 것은 잘못이 아닙니다.
209+ 경우에 따라서는 ** 중복을 허용하더라도 페이지 간 의존성을 줄이는 것** 이 더 중요합니다.
210+ 다만, 비즈니스 로직처럼 변경 가능성이 큰 핵심 부분은 중복을 피하고, 복사할 때에도 가능한 한 DRY 원칙을 고려합니다.
214211
215212:::
216213
217- ## 4단계: Shared 레이어 정리하기 {#unpack-shared-layer}
214+ ## 4단계: Shared Layer 정리하기 {#unpack-shared-layer}
218215
219- - ** 한 페이지에서만 쓰이는 코드** 는 해당 페이지 ** slice** 로 이동한다 .
220- - ` actions / reducers / selectors ` 도 예외가 아니다. ** 사용처와 가까이 ** 두는 편이 좋다.
216+ ** 한 페이지에서만 사용되는 코드** 는 해당 페이지의 ** slice** 로 이동합니다 .
217+ ` actions, reducers, selectors ` 역시 예외가 아니며, ** 사용되는 위치와 가까운 곳 ** 에 두는 것이 가장 좋습니다.
221218
222- Shared는 모든 layer가 의존할 수 있는 ** 공통 의존점** 이므로, 코드를 최소화해 변경 위험을 낮춘다.
219+ Shared는 모든 layer가 의존할 수 있는 ** 공통 의존 지점이** 기 때문에,
220+ 이곳에 코드를 과도하게 쌓아두지 않고 최소한으로 유지하는 것이 변경 위험을 줄이는 핵심 원칙입니다.
223221
224- 최종 폴더 구조는 다음과 같아야 합니다 :
222+ 이 단계를 마치면 폴더 구조는 아래와 같은 형태가 되는 것이 자연스럽습니다 :
225223
226224<details className =" file-tree " open >
227225 <summary>📁 src</summary>
@@ -280,49 +278,45 @@ Shared는 모든 layer가 의존할 수 있는 **공통 의존점**이므로,
280278
281279## 5단계: 기술적 목적별 segment 정리 {#organize-by-technical-purpose}
282280
283-
284- | segment | 용도 예시 |
285- | ----------| -----------|
286- | ` ui ` | Components, formatters, styles |
287- | ` api ` | Backend requests, DTOs, mappers |
288- | ` model ` | Store, schema, business logic |
289- | ` lib ` | Shared utilities / helpers |
281+ | segment | 용도 예시 |
282+ | -------- | ---------------------------------- |
283+ | ` ui ` | Components, formatters, styles |
284+ | ` api ` | Backend requests, DTOs, mappers |
285+ | ` model ` | Store, schema, business logic |
286+ | ` lib ` | Shared utilities / helpers |
290287| ` config ` | Configuration files, feature flags |
291288
289+ > ** 무엇인지** 가 아니라 ** 무엇을 위해 존재하는지** 를 기준으로 폴더를 구분합니다.
290+ > 따라서 ` components ` , ` utils ` , ` types ` 처럼 목적이 모호한 폴더 이름은 지양합니다.
292291
293- > “** 무엇인지** ”가 아니라 “** 무엇을 위해** ” 존재하는지를 기준으로 나눈다.
294- > 따라서 ` components ` , ` utils ` , ` types ` 같은 이름은 지양한다.
295-
296- 1 . ** 각 페이지** 에 ` ui / model / api ` 등 필요한 segment를 만든다.
297- 2 . ** Shared** 폴더를 정리한다.
298- - ` components·containers ` → ` shared/ui `
299- - ` helpers·utils ` → ` shared/lib ` (기능별 그룹화 후)
300- - ` constants ` → ` shared/config `
301-
292+ 1 . ** 각 페이지 내부** 에서, 필요한 ` segment(ui, model, api 등) ` 를 구성합니다.
293+ 2 . ** Shared 폴더는 공통 기능만 남기도록 정리합니다.**
294+ - ` components/containers ` → ` shared/ui `
295+ - ` helpers/utils ` → ` shared/lib ` (기능별 그룹화 후)
296+ - ` constants ` → ` shared/config `
302297
303298## 선택 단계 {#optional-steps}
304299
305300### 6단계: 여러 페이지에서 재사용되는 Redux slice를 Entities / Features layer로 분리하기 {#form-entities-features-from-redux}
306301
307- - 여러 페이지에서 재사용되는 Redux ** slice** 는 주로 ** product, user** 같은 ** business entity** 를 표현합니다.
308- 이 경우 ** Entities layer** 로 옮기고 , ** entity** 마다 폴더를 하나씩 만듭니다 .
309- - 댓글 작성처럼 ** 사용자 행동(action)** 을 다루는 ** slice** 는 ** Features layer** 로 이동합니다 .
302+ 여러 페이지에서 반복적으로 사용되는 Redux ** slice** 는 대부분 ** product, user** 처럼 명확한 ** business entity** 를 표현합니다.
303+ 이러한 slice는 ** Entities layer** 로 이동하며 , ** entity** 마다 별도의 폴더를 구성합니다 .
304+ 반대로, 댓글 작성처럼 ** 사용자의 특정 행동(action)** 을 중심으로 한 ** slice** 는 ** Features layer** 로 옮겨 독립적으로 관리합니다 .
310305
311- ** Entities** 와 ** Features** 는 서로 독립적으로 사용될 수 있도록 설계되어 있습니다 .
312- Entitles 간 연결이 필요하면 [ Business-Entities Cross-Relations 가이드] [ business-entities-cross-relations ] 를 참고하세요 .
313- 해당 ** slice** 와 연관된 API 함수는 ` 📁 shared/api ` 에 그대로 두어도 무방합니다 .
306+ ** Entities** 와 ** Features** 는 서로 의존하지 않고 사용할 수 있도록 설계해야 합니다 .
307+ Entity 간의 관계가 필요하다면 [ Business-Entities Cross-Relations 가이드] [ business-entities-cross-relations ] 를 참고해 구조화하면 됩니다 .
308+ 해당 ** slice** 와 연관된 API 함수는 ` 📁 shared/api ` 에 그대로 두어도 괜찮습니다 .
314309
315310### 7단계: modules 폴더 리팩터링 {#refactor-your-modules}
316311
317- ` 📁 modules ` 는 과거에 비즈니스 로직을 모아 두던 곳으로 , 성격상 ** Features layer** 와 비슷합니다.
318- 단 , 앱 Header처럼 ** large UI block** (예: global Header, Sidebar)이라면 ** Widgets layer** 로 옮기는 편이 좋습니다.
312+ ` 📁 modules ` 는 과거에 비즈니스 로직을 모아두던 공간으로 , 성격상 ** Features layer** 와 비슷합니다.
313+ 다만 , 앱 Header처럼 ** large UI block** (예: global Header, Sidebar)이라면 ** Widgets layer** 로 옮기는 편이 좋습니다.
319314
320315### 8단계: shared/ui에 presentational UI 기반 마련하기 {#form-clean-ui-foundation}
321316
322317` 📁 shared/ui ` 에는 비즈니스 로직이 전혀 없는, 재사용 가능한 presentational UI 컴포넌트만 남겨야 합니다.
323-
324- - 기존 ` 📁 components ` · ` 📁 containers ` 에 있던 컴포넌트에서 비즈니스 로직을 분리해 상위 layer로 이동합니다.
325- - 여러 곳에서 쓰이지 않는 부분은 ** 복사(paste)** 해서 각 layer에서 독립적으로 관리해도 괜찮습니다.
318+ 기존 ` 📁 components / 📁 containers ` 에 있던 컴포넌트에서 비즈니스 로직을 분리해 상위 layer로 이동시킵니다.
319+ 여러 곳에서 쓰이지 않는 부분은 ** 복사(paste)** 해서 각 layer에서 독립적으로 관리해도 문제 없습니다.
326320
327321## 참고 자료 {#see-also}
328322
0 commit comments