![[React] 웹 프로젝트에서의 클린아키텍쳐 : 다들 이미 사용중이었다](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdna%2FZFZHu%2FbtsQsslhdqI%2FAAAAAAAAAAAAAAAAAAAAACx3HwclJfs13ZzIaDNGEoZxDiEUolVctt58GuNDa535%2Fimg.png%3Fcredential%3DyqXZFxpELC7KVnFOS48ylbz2pIh7yKj8%26expires%3D1759244399%26allow_ip%3D%26allow_referer%3D%26signature%3DGSvKCnPtvrjHyKqc7DGG0J02%252Bo4%253D)
개요
클린 아키텍처를 웹 프로젝트에 도입해보려고 했다. Flutter에서 꽤 괜찮은 경험이었기에 React 기반의 웹 프로젝트에도 자연스럽게 잘 들어맞을 것이라고 생각했다. 한동안 웹을 모바일 앱과 같이 만드는 SPA방식이 대세였기에 구조적으로 비슷하기에 적용이 잘 되지 않을까 싶었다.
하지만 막상 프로젝트에 적용해보니, "이게 과연 웹에 맞는 구조인가?" 라는 의문이 강하게 들었다. 디렉토리는 복잡해지고, 코드 응집도는 떨어지고, 생산성은 낮아졌다. 무엇보다 초기 프로젝트 세팅에 너무나도 많은 시간이 들어가게 된다.
글로도 기록을 남겼었는데, 처음에 도입을 했을 때는 꽤나 좋았다. 하지만 시간이 지날 수록 의문이 많이 들었다. ▼
[Next.js][Develop] Next.js 클린 아키텍처 적용기
개요 Next.js로 웹 프로젝트를 시작하면서 구조를 어떻게 설계할지 많은 고민이 있었다. 그동안의 프로젝트들은 빠르게 기능을 만드는 데에 집중했었다. 배우면서 작업한 것도 있었고, React와 Next.
noguen.com
"모두가 하니까 나도 해야지" 라는 생각이 좋은 건 아니지만, 거의 대부분의 웹 프로젝트에서 클린 아키텍쳐를 도입하지 않는다. 다른 개발자들이 몰라서 도입을 안했을리는 없기에 왜 도입을 안하는지에 대한 이유를 생각해봤다.
왜 웹 프로젝트에서 클린 아키텍처는 부자연스러웠나
'웹에서도 도메인 로직은 분리하고, 상태관리는 추상화하며, 외부 의존은 인터페이스에만 두자'로 시작했다. 그리고 이런 경우 보통 아래와 같이 계층을 나눈다. ▼
- domain/
- entity/
- value_object/
- usecase/
- getUser.ts
- updateProfile.ts
- interface/
- HomePage.tsx
- UserProfile.tsx
- infra/
- userRepositoryImpl.ts
- api/
이론적으로는 매우 깔끔하다. 하지만 개발을 진행하면서 점점 답답함을 느꼈다. 단순한 UI 변경도 여러 계층을 타고 들어가야 했고, 상태는 분리됐지만, 흐름을 이해하기가 더 어려워졌다.
심지어 협업을 할 때 onboarding 시간이 더 늘어났다. 나의 지식을 과시하고자 클린 아키텍쳐를 도입한 게 아닌데, 팀원들에게 강연을 해줘야할 정도였다. 프로젝트에 대한 강연이 아닌, 프로젝트 구조에 대한 강연을 했다는 것 자체만으로 엄청나게 복잡하다는 것을 암시한다. 당연하게도 구조를 유지하는 데 시간이 들고, 빠른 피드백 루프가 무너졌다.
간결함이 중요한데... 간결하지 못하다
최근 웹 개발계에서는 SPA의 사용에 회의감을 느끼고 있다. 이 글이 꽤나 와닿을 정도로 간결한 개발이 필요시 되고 있다. ▼
이제 모던 CSS가 SPA를 대체할 때입니다 | GeekNews
View Transitions API 같은 모던 CSS 기능의 등장으로, 이제 매끄러운 페이지 전환을 위해 SPA 구조가 필요 없는 상황임대부분의 SPA 사이트는 실제로 기대한 만큼의 성능이나 부드러운 경험을 제공하지
news.hada.io
이렇듯 웹은 최대한 간결한 개발이 중요시 되는데, 클린 아키텍쳐의 도입으로 인해 프로젝트가 웹의 특성과 충돌하고 있다는 것을 깨달았다.
커피챗: "왜 웹에서는 클린 아키텍처를 잘 쓰지 않을까요?"
이런 고민을 갖고, 스터디 모임에서 만난 프론트엔드 개발자 분과 커피챗을 가졌다. "왜 웹에서는 클린 아키텍처를 잘 도입하지 않을까요?" 라는 질문을 던졌고, 돌아온 대답은 아래와 같았다.
“웹은 너무 빠르게 변해요. 구조적으로 따라가기 힘들고, 오히려 생산성을 해치는 경우가 많아요.
그리고 React스럽게 사고하는게 가장 중요한데 그러지 못해요.”
이 짧은 대화 속에 모든 핵심이 담겨 있었다.
웹에서 클린 아키텍처가 통하지 않는 이유 (기술적 관점)
Hook이 UseCase를 대체한다
React 스럽게 사고하는 것에 대해 조금 더 깊게 공부를 하고 프로젝트 코드를 작성하면서 왜 웹에서 "표준적인" 클린 아키텍쳐를 사용하지 않는지를 이해했다. 그 핵심적인 이유는 다들 이미 클린 아키텍쳐의 개념을 사용하고 있던 것이었다.
커스텀 훅은 UI 레이어와 비즈니스 로직 사이를 자연스럽게 이어준다. 전통적인 클린 아키텍처라면 다음처럼 계층을 나눈다.
UI → Controller / Presenter → UseCase → Repository → Domain Entity
그러나 React의 함수형·선언적 모델에서는 훅 하나로 이를 흡수할 수 있다.
왜 훅이 곧 UseCase인가?
훅을 클린 아키텍쳐와 비교하면 이렇게 분류가 된다. ▼
입력 수집 | Controller | 훅 매개변수 (const { id } = props) |
비즈니스 호출 | UseCase | 내부 fetch / mutate |
외부 의존성 | Repository | 내부 API 호출 함수 |
상태 보존 | Entity/State | useState, useReducer |
결과 노출 | Presenter | 훅의 return 값 (data, error, isLoading) |
- 응집도 — 컨트롤 플로·부수효과·상태·에러 처리가 한 파일 안에서 해결된다.
- 재사용성 — 컴포넌트 간 로직 복사 대신 훅 import 한 줄.
- 테스트 용이성 — @testing-library/react-hooks로 훅을 "함수"처럼 단위 테스트.
- 타입 안정성 — 입·출력이 제네릭으로 명시되므로 DTO ↔ Entity 변환이 자연스럽다.
한마디로, 서비스 레이어를 React 함수 시그니처로 옮긴 것이 커스텀 훅이다.
예시 – useLogin
export function useLogin() {
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle');
const login = async (id: string, pw: string) => {
setStatus('loading');
try {
const token = await post('/auth/login', { id, pw });
setToken(token); // side‑effect
setStatus('idle');
return token; // 결과 반환 → 컴포넌트에서 await
} catch (e) {
setStatus('error');
throw e;
}
};
return { login, status };
}
전통적 구조였다면 AuthUseCase → AuthRepository → AuthService 등으로 분리했을 내용을, 훅 하나에 순서·의존성 주입·상태 전이까지 응집시켰다.
계층화가 생산성과 유지보수성을 해친다
의외일 수 있다. 클린 아키텍처의 목적은 유지보수성을 높이는 것이다. 하지만 현실은 다르다.
- 코드를 추적하는 경로가 너무 길다. (interface → usecase → domain → infra)
- 작은 변경도 여러 파일에 ripple effect를 만든다.
- 디렉토리 구조만 봐도 '진입 장벽'이 된다.
특히 UI 위주의 반복이 많고, 실험이 자주 필요한 웹 프로젝트에서는 빠르게 변경하고 빠르게 확인하는 루프가 핵심인데, 표준 방식의 클린 아키텍처는 이 루프를 방해한다.
또한 웹은 너무 빠르게 진화 중이다
React의 상태 관리 패러다임만 봐도 그렇다.
Redux → Context → Recoil → Zustand → Jotai → Valtio → Signal…
이렇게 빠르게 바뀌는 환경에선, 구조를 정형화하는 것 자체가 리스크가 된다. "지금 만든 이 구조, 3개월 뒤에도 유효할까?" 라는 불안이 항상 있다. 구조를 계속해서 바꾸는게 좋은 것은 아니지만, 더 좋은 구조가 나왔을 때 언제든 쉽게 마이그레이션 할 수 있는 환경을 만드는 것은 중요하다.
이런 점들을 통해 React에서는 이미 클린 아키텍처의 개념을 응축해서 사용하고 있다는 것을 알고 있다.
Flutter에서는 왜 클린 아키텍처가 통했을까?
Flutter에서 클린 아키텍처를 시도했을 때는 꽤 잘 맞았었다. 왜 그랬을까? 정리하자면 "상태 관리 방식이 비교적 안정적이었기 때문"이다. Flutter는 크게 보면 3~4개의 상태관리 패턴이 주류를 이룬다. Provider, Riverpod, BLoC, GetX...
물론 이 안에서도 진화는 있었지만, 기본적으로는 DI + 상태의 분리 + ViewModel 계층이 자연스럽게 설계된다.
- BLoC: UseCase, Repository, State를 명확히 구분
- Riverpod: DI Container + 상태 + 외부 의존성 주입 구조가 안정적
- Provider: ViewModel 구조와 연결되기 쉬움
무엇보다도, Flutter의 위젯 트리는 계층화된 아키텍처에 어울리는 UI 구조다. UI 변경이 빠르게 일어나지 않고, 한 번 설계한 구조를 오래 유지하는 성격도 강하다.
즉, 클린 아키텍처가 가진 계층적 특성이 Flutter와는 "맞물리는" 느낌이었다.
반면, 웹은 언제나 "변화에 최적화된 설계"를 요구한다.
그럼 웹에서는 어떻게 해야 할까?
내가 얻은 결론은 이렇다. 고연차나 실력이 출중한 독자들이 보기엔 꽤나 당연한 이야기들일 수 있다.
무조건적인 클린 아키텍처 도입은 지양하자
- 구조적 비용이 너무 크다
- React의 본질과 어긋난다
- 유지보수가 오히려 더 어려워진다
클린 아키텍처의 철학만 취하고, 구조는 React에 맞춰 설계하자
- 도메인 개념을 적용하되, feature 단위로 응집시키자
- Hook은 UseCase라고 받아들이고, 잘게 쪼개고 조합하자
- API, 비즈니스 로직, 상태 관리 책임만 명확히 분리하자
- 테스트가 필요한 부분만 명시적으로 추상화하자
마무리 정리
클린 아키텍처는 언뜻 보기에 개발 전반에 적용이 될 거 같고, 적용해야만 할 것 처럼 보여진다. 하지만 그렇다고 무조건 좋은 것이 아니다.
환경에 맞는 구조가 따로 있다. 약이 모든 사람에게 좋게 작용하는 것이 아닌 것 처럼, 무언가를 도와주는 것들이 모든 것을 도와줄 수 있는 것은 아니다.
웹에서는 다음 기준이 무엇보다 중요하다:
- 빠르게 만들 수 있어야 하고
- 자주 바꿀 수 있어야 하며
- 누구나 쉽게 유지보수할 수 있어야 한다
웹은 변화가 기본값이고, React는 빠른 구성과 확장을 전제로 설계되었다. 그런 환경에선 클린 아키텍처가 오히려 방해가 될 수도 있다. 그래서 나는 이렇게 정리했다.
"React에서 클린 아키텍처를 구현하려 하지 말고,
클린한 구조를 React스럽게 표현하는 걸 고민하자."
'Develop > Web' 카테고리의 다른 글
[Web][Issue] 이중 인코딩으로 인한 사진 누락 (0) | 2025.07.17 |
---|---|
[Next.js][Develop] Next.js 클린 아키텍처 적용기 (0) | 2025.07.10 |
[React] Tailwind-css를 써보면서 느낀점 (0) | 2025.02.20 |
[React][Error] tailwind css 설치 오류 (2) | 2025.02.08 |
[JS] CommonJS와 ES모듈 (0) | 2024.12.13 |