26. Redux 프로젝트 구조 - 확장 가능한 설계
15편, 20편, 25편에서 실습마다 features/counter/, features/todos/, features/posts/ 같은 기능(feature) 단위 폴더 구조를 계속 사용해왔습니다. 이 편은 그 선택의 근거를 정리하고, 프로젝트가 커질 때 이 구조를 어떻게 확장하는지 다룹니다.
학습 목표
- 파일 타입별 구조와 기능 단위 구조의 차이를 설명하고, 후자가 대규모 앱에서 유리한 이유를 설명할 수 있다.
- 여러 기능이 공유하는 로직을 위한
app/,shared/계층을 설계할 수 있다. - 배럴 파일(
index.jsre-export)의 편의성과 함정을 함께 설명할 수 있다.
파일 타입별 구조: 왜 흔히 보이지만 문제가 되는가
Redux 초기 문서와 예제에서 흔히 보이는 구조는 파일의 역할(타입)로 폴더를 나누는 방식입니다.
| |
이 구조의 문제는, counter 기능 하나를 수정하려면 actions/, reducers/, components/ 세 폴더를 오가며 파일을 찾아야 한다는 것입니다. 기능이 10개, 20개로 늘어나면 이 탐색 비용이 누적됩니다. 07편에서 액션·리듀서·selector가 항상 같은 기능을 다룬다는 것을 봤듯, 함께 바뀌는 코드는 함께 위치해야 탐색과 수정이 쉽습니다.
기능 단위 구조: 15·20·25편에서 써온 방식
| |
counter 기능을 수정할 일이 생기면 features/counter/ 폴더 하나만 열면 됩니다. 이 원칙을 Redux 공식 문서는 “Ducks 패턴"이라고도 부르며(액션·리듀서를 기능별로 하나의 파일/폴더로 묶는 커뮤니티 컨벤션에서 유래), RTK의 createSlice가 액션과 리듀서를 애초에 하나로 묶어 반환하는 것도 이 철학과 맞닿아 있습니다.
여러 기능이 공유하는 로직: app/과 shared/
기능이 늘어나면 여러 기능이 공통으로 쓰는 로직이 생깁니다. 이를 위한 계층을 분리합니다.
| |
app/: Store 조립, 앱 전역 설정처럼 “애플리케이션 자체"에 대한 코드.features/: 각 도메인 기능. 서로 직접 import하지 않는 것이 원칙(예:todos가counter의 내부 파일을 직접 참조하지 않는다).shared/: 여러 feature가 함께 쓰는, 특정 도메인에 속하지 않는 코드.
이 분리는 08편에서 강조한 “예측 가능성"을 구조 수준으로 확장한 것입니다. 어떤 코드를 찾을 때 “이건 특정 기능의 것인가, 앱 전역 설정인가, 공용 유틸인가"를 폴더 이름만으로 짐작할 수 있습니다.
경계를 코드 리뷰가 아니라 도구로 강제하기
“features끼리 서로 직접 import하지 않는다"는 규칙은 팀원 모두가 기억하고 지켜야 하는 약속으로만 두면, 프로젝트가 커질수록 조금씩 어겨지기 쉽습니다. eslint-plugin-boundaries 같은 ESLint 플러그인은 이 규칙을 정적 분석으로 강제해, 어기는 import가 있으면 커밋 전에 에러로 잡아냅니다.
| |
이렇게 하면 “리뷰어가 눈으로 발견해야 하는 규칙"이 “빌드가 실패하는 규칙"으로 바뀌어, 팀 규모가 커져도 경계가 조용히 무너지는 것을 막을 수 있습니다.
RTK Query의 baseApi 분리 패턴
25편에서 postsApi를 만들 때 createApi를 직접 호출했지만, 여러 API 슬라이스가 있는 프로젝트에서는 injectEndpoints로 기본 설정을 공유하는 것이 일반적입니다.
| |
| |
injectEndpoints를 쓰면 baseQuery, tagTypes 같은 공통 설정을 한 곳(baseApi.js)에서만 관리하면서, 각 기능은 자신의 엔드포인트만 선언할 수 있습니다. Store에는 baseApi의 리듀서/미들웨어 하나만 등록하면 됩니다.
배럴 파일: 편의성과 함정
기능 폴더의 내부 파일들을 하나의 진입점으로 재수출하는 배럴 파일(index.js)은 import 문을 짧게 만들어줍니다.
| |
| |
편리해 보이지만, 배럴 파일에는 실무에서 자주 부딪히는 함정이 있습니다.
- 번들 크기: 일부 번들러 설정에서는 배럴 파일의 export 하나만 써도 파일 전체가 번들에 포함될 수 있다(트리 셰이킹이 항상 완벽하지 않음).
- 순환 참조:
features/todos/index.js가features/posts를 참조하고,posts의 배럴 파일이 다시todos를 참조하면 순환 의존성이 생기기 쉽다. - 탐색 어려움: “이 함수가 실제로 어느 파일에 정의되어 있는가"를 IDE의 “정의로 이동” 없이 육안으로 찾기 어려워진다.
이런 이유로, 큰 프로젝트에서는 배럴 파일을 아예 쓰지 않거나, feature 최상위 딱 한 단계에서만(내부 하위 폴더까지 전파하지 않고) 제한적으로 사용하는 경우가 많습니다. 프로젝트 규모가 작다면 배럴 파일의 편의성이 더 크므로, 이는 “항상 옳은 규칙"이 아니라 규모에 따라 판단할 트레이드오프입니다.
실무 체크리스트
- 새 기능을 추가할 때 하나의
features/<기능명>/폴더 안에서 대부분의 작업이 끝나는가? - 여러 기능이 공유하는 코드가 특정 feature 폴더 안에 우연히 놓여있지 않고
shared/로 분리되어 있는가? - “feature끼리 직접 import하지 않는다"는 규칙을 사람의 리뷰에만 의존하지 않고, ESLint 같은 도구로 강제하고 있는가?
- 배럴 파일을 쓴다면, 프로젝트 규모와 번들러 설정을 고려해 의도적으로 선택한 것인가?
연습 과제
기초(★☆☆)
- 25편의
postsApi,commentsApi관련 코드를features/posts/,features/comments/폴더로 재배치해보세요.
중급(★★☆)
- 여러 feature가 공통으로 쓰는
formatDate유틸 함수를shared/utils/로 분리하고, 각 feature에서 상대 경로로 import해보세요.
고급(★★★)
baseApi.injectEndpoints패턴으로postsApi와commentsApi를 하나의baseApi위에 분리해 구현해보세요.
요약
- 기능 단위(feature) 폴더 구조는 함께 바뀌는 코드를 함께 위치시켜, 파일 타입별 구조보다 대규모 앱에서 탐색·유지보수가 쉽다.
app/(앱 전역),features/(도메인별),shared/(공용) 세 계층으로 나누면 책임 경계가 명확해진다.- 배럴 파일은 편의성과 번들 크기·순환 참조 위험 사이의 트레이드오프이므로, 프로젝트 규모에 맞게 판단한다.
참고 문헌 및 출처(추천)
- Redux 공식 문서, “Structuring Reducers, Reducer Structure Approaches”
- Redux Toolkit 공식 문서, “Redux Essentials, Part 6: Performance and Normalizing Data” (프로젝트 구조 관련 서두)
- Redux Toolkit 공식 문서, “RTK Query, Code Splitting” (injectEndpoints)
![Featured image of post [Redux] 26. Redux 프로젝트 구조 - 확장 가능한 설계](/post/redux/redux-project-structure/wordcloud_hu_d416235a031d1f28.webp)
![[Redux] 24. RTK Query - 데이터 페칭의 혁명](/post/redux/rtk-query/wordcloud_hu_1807a958ecd17a19.webp)
![[Redux] 25. 실습: RTK Query로 블로그 앱 만들기](/post/redux/practice-blog-app/wordcloud_hu_1c4aa9e1677f5dbb.webp)
![[Redux] 26. Redux 프로젝트 구조 - 확장 가능한 설계](/post/redux/redux-project-structure/wordcloud_hu_379134b467bc38d9.webp)
![[Redux] 27. 정규화(Normalization) - 복잡한 데이터 관리](/post/redux/normalization/wordcloud_hu_f70eb8fa31f8f97c.webp)
![[Redux] 28. Redux와 TypeScript - 타입 안전한 상태 관리](/post/redux/redux-typescript/wordcloud_hu_5c9ae286e05acf8c.webp)
![[Tutorial] Learn Prompting - 프롬프트 엔지니어링 무료 가이드 정리](/post/2022-12-30-learn-prompting/wordcloud_hu_6a9d105de4834753.webp)
![[Hardware] LattePanda Alpha에 Ubuntu 16.04 LTS 설치 가이드](/post/2018-12-06-install-ubuntu-16.04-on-lattepanda/wordcloud_hu_fc536f8de2cbd4bf.webp)
![[Rust] Comprehensive Rust 무료 강의 정리 및 코스 구조](/post/2022-12-30-comprehensive-rust/wordcloud_hu_d1420ff38434cdb6.webp)
![[Redux] 00. 커리큘럼 - 학습 로드맵](/post/redux/getting-started-redux/wordcloud_hu_2ed9b2e29c50fe99.webp)
![[Programming] HTML은 프로그래밍 언어인가? html-lang.org 중심 탐구](/post/2025-03-05-html-as-a-programming-language-exploration-centered-on-html-lang-org/index_hu_df0ffb4c27de506f.webp)