티스토리 뷰

엔지니어링에서 완벽한 '은탄환'은 존재하지 않습니다. 모든 기술적 의사결정은 결국 얻는 것과 잃는 것 사이의 트레이드오프(Trade-off)를 결정하는 과정입니다.
회사 서비스를 다양한 고객사의 화이트 라벨링 요구사항에 대응하기 위해 시도했던 FSD(Feature-Sliced Design) 아키텍처 도입과 인프라 기반 설정 주입 전략 역시 그 여정의 연속이었습니다. 단순히 무엇을 구축했는지를 넘어, 어떤 고민을 했고 운영 과정에서 무엇을 느꼈는지에 대한 회고를 공유합니다.
1. 직면한 문제: 기능의 파편화와 예측 불가능한 영향 범위
서비스가 초기 단계를 넘어 고도화될수록 프론트엔드 코드는 필연적으로 비대해졌습니다. 로봇 운영 시나리오가 복잡해지고 고객사가 늘어남에 따라 다음과 같은 문제들이 발생했습니다.
- 비대한 비즈니스 로직: UI 컴포넌트 내부에 비즈니스 로직, API 호출, 상태 관리가 뒤섞여 재사용이 불가능해짐.
- 예측 불가능한 영향 범위: 도메인 간의 의존성이 스파게티처럼 엉켜, 특정 기능 수정이 예상치 못한 페이지의 오류로 이어짐.
- 화이트 라벨링 대응의 한계: 고객사별 브랜드 컬러나 특정 기능 활성화 여부를 관리하기 위해 코드 곳곳에 산재한 조건부 렌더링(
if-else)의 한계.
2. 해결 전략 1: 우리에게 맞는 아키텍처를 찾는 여정
무너진 질서를 바로잡기 위해 아토믹 디자인, 레이어드 아키텍처 등 여러 방법론을 검토했습니다. 특히 "도메인을 외부 인프라로부터 격리한다"는 헥사고날 아키텍처의 철학에 가장 큰 매력을 느꼈습니다. 하지만 현대적인 프론트엔드 프레임워크에 이를 어떻게 실천적으로 녹여낼지에 대한 가이드가 부족했습니다.
우리는 FSD가 헥사고날의 추상화 철학을 프론트엔드적인 폴더 구조로 실현하는 데 가장 최적화된 그릇이라고 판단했습니다.
💡 잠깐, FSD(Feature-Sliced Design)란?
FSD는 프론트엔드 애플리케이션의 복잡성을 제어하기 위해 코드를 비즈니스 가치와 도메인의 추상화 수준에 따라 7개의 계층(Layers)으로 나누는 아키텍처 방법론입니다.
graph TD
app[app: 설정, 스타일, 프로바이더 초기화]
pages[pages: 전체 페이지 구성 및 라우팅]
widgets[widgets: 독립적으로 사용 가능한 UI 유닛]
features[features: 사용자 가치 중심 기능]
entities[entities: 비즈니스 핵심 도메인 모델 및 로직]
shared[shared: 재사용 가능한 공용 컴포넌트 및 유틸리티]
app --> pages
pages --> widgets
widgets --> features
features --> entities
entities --> shared
subgraph "의존성 방향 (Dependency Direction)"
direction[▼ 상위 레이어은 하위 레이어만 참조 가능]
end
style direction fill:#f9f9f9,stroke-dasharray: 5 5
왜 FSD(Feature-Sliced Design)인가? (명과 암)
FSD 도입은 우리 팀에게 강력한 해결책이 되었지만, 명확한 트레이드오프가 존재했습니다.
✅ 우리가 얻은 명확한 장점
- 코로케이션(Collocation)을 통한 생산성: 기능 슬라이스 안에 UI, State, Type을 모아 응집도를 높임으로써 파일 탐색 시간을 획기적으로 줄였습니다.
- 도메인 로직 격리 (Port-Adapter):
entities에서 인터페이스(Port)를 정의하고api에서 구현(Adapter)하는 구조를 통해 실제 로봇이나 서버 규격 변화에 강한 도메인을 구축했습니다.
⚠️ 운영하며 겪은 현실적인 단점
- 높은 초기 온보딩 비용: 폴더 구조가 직관적이지 않아 새로운 팀원이 합류했을 때 코드를 어디에 위치시킬지 결정하는 데 꽤 높은 학습 곡선이 필요했습니다. 이를 위해 '레이어 역할 정의서'라는 별도의 가이드가 필수적이었습니다.
- Widget 레이어의 블랙홀화: 책임이 모호한 결합 로직들이
widgets로 몰려 거대해지는 현상이 발생했습니다. 이를 방지하기 위해 위젯을 더 작은 피처나 엔티티 단위로 쪼개는 지속적인 리팩토링이 동반되어야 했습니다. - 파일 파편화: 코로케이션을 위해 파일을 잘게 쪼개다 보니 파일 개수가 급격히 늘어났습니다. 비록 IDE 검색에 더 의존하게 되었지만, 특정 기능의 맥락을 파악하는 데는 긍정적인 면도 있었습니다.
3. 해결 전략 2: 인프라와 런타임을 아우르는 화이트 라벨링 설계
단순히 코드 내부의 설계를 넘어, AWS S3와 CloudFront를 활용한 'Single-Build, Multi-Config' 전략을 수립했습니다.
① 배포 및 실행 워크플로우
사용자의 호스트 도메인을 식별하여 해당 고객사의 전용 설정을 런타임에 동적으로 주입합니다.
flowchart TD
subgraph Storage [인프라 레이어]
S3App[(AWS S3<br/>공통 앱)]
S3Config[(AWS S3<br/>고객사 설정)]
end
Storage --> CF[AWS CloudFront<br/>배포망]
subgraph ClientLayer [클라이언트 실행 환경]
CF --> Browser[사용자 브라우저]
subgraph AppRuntime [앱 실행 런타임]
Browser --> Identify[호스트 도메인 식별]
Identify --> FetchConfig[고객사 전용<br/>설정 주입]
FetchConfig --> Render[맞춤형 서비스<br/>렌더링 완료]
end
end
style AppRuntime fill:#f0f7ff,stroke:#0052cc
style Render fill:#fff7e6,stroke:#ff8c00
② 관리 전략에 대한 고찰: JSON 주입 vs 서버 관리
S3를 통한 JSON 설정 주입 방식은 빠르고 효율적이지만, 완벽한 솔루션은 아니었습니다.
- JSON 주입의 한계: 고객사가 늘어날수록 클라이언트 스키마 변경 시 과거 설정값들에 대한 마이그레이션 공수가 기하급수적으로 증가합니다.
- 적정 기술의 선택: UI 테마, 로고 등 자주 변하지 않는 시각적 요소들은 S3 관리 방식이 효과적입니다. 하지만 비즈니스 로직을 빈번하게 제어해야 하는 Feature Flag 성격의 요구사항은 운영상 서버(DB 기반 관리)를 통해 제어하는 것이 더 안전하고 유연하다는 결론을 얻었습니다.
4. 결과 및 회고: 은탄환은 없다
아키텍처 개선을 통해 유지보수 효율 향상과 S3 설정 파일 추가만으로 신규 고객사 대응이 가능한 구조를 만들었습니다.
가장 큰 소득은 아키텍처가 단순히 코드를 예쁘게 만드는 것을 넘어, 비즈니스의 확장성과 운영 효율 사이에서 끊임없이 트레이드오프를 결정하는 과정임을 깊이 체감한 것입니다. 복잡한 로봇 물류 도메인에서도 유연함을 잃지 않는 시스템을 구축한 경험은 앞으로 더 큰 성장을 뒷받침하는 든든한 기반이 될 것입니다.
'Dev > 기타' 카테고리의 다른 글
| 정말 그 기능이 필요할까요? : 요구사항 너머의 진짜 고민 (0) | 2026.04.06 |
|---|---|
| AI가 팀의 언어를 바꾸기까지: Claude Code 도입기 (0) | 2026.04.04 |
| 로봇 물류 현장의 네트워크 단절을 극복하는 프론트엔드 전략 (0) | 2026.04.03 |
| 제품 하나를 운영하기 위해 온 팀이 필요한 이유 (0) | 2026.02.11 |
| WSL 환경 구성하기 (0) | 2025.10.24 |
- Total
- Today
- Yesterday
- JavaScript
- RUBY
- 생활코딩
- Python
- Django
- github
- html
- instagram CSS
- Git
- hooks
- 기능추가
- redux-toolkit
- Firebase
- todolist
- 바닐라js
- project
- nrc
- async
- 오버라이딩
- 드림코딩
- NomadCoder
- TypeScirpt
- css
- 트위터 클론
- Class
- nodejs
- object
- React
- 그림판 만들기
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
