41장: 데이터베이스는 세부사항이다에서 데이터 접근 기술을 Repository 인터페이스로 분리하는 원칙을 보았다. 이 장은 같은 원칙을 입출력 경로, 즉 웹에 적용한다. 웹은 아키텍처에서 세부사항이다. 비즈니스 규칙은 데이터가 웹으로 전달되는지, 콘솔로 전달되는지 신경 쓰지 않아야 한다.
GUI의 진자 운동
마틴은 GUI 역사를 진자 운동(Pendulum)에 비유한다. 컴퓨팅 파워가 중앙과 클라이언트 사이를 왔다 갔다 한다.
flowchart LR
subgraph Pendulum [진자 운동]
CENTER[중앙 집중]
CLIENT[클라이언트 분산]
CENTER -->|시간 흐름| CLIENT
CLIENT -->|시간 흐름| CENTER
end
1960-70년대: 중앙 집중
메인프레임 시대에는 컴퓨팅 파워 자체가 비쌌기 때문에, 모든 처리를 한 대의 중앙 컴퓨터에 집중시키고 사용자는 문자만 주고받는 저렴한 단말기로 접속했다. 화면에 무엇을 어떻게 표시할지 결정하는 로직조차 클라이언트가 아니라 메인프레임에 있었다.
flowchart LR
T1[터미널]
T2[터미널]
T3[터미널]
MF[메인프레임]
T1 --> MF
T2 --> MF
T3 --> MF
| 특징 | 설명 |
|---|---|
| 클라이언트 | 덤 터미널 (글자만 표시) |
| 처리 | 모두 메인프레임에서 |
| 네트워크 | 느린 시리얼 연결 |
| |
1980-90년대: 클라이언트로 분산
PC의 가격이 내려가고 성능이 오르자, 진자는 반대로 움직였다. 각 PC가 자체적으로 화면을 그리고 비즈니스 로직까지 실행하는 팻 클라이언트(fat client)가 등장했고, 서버는 데이터베이스 역할만 남았다. 문제는 비즈니스 로직이 각 PC의 실행 파일 안에 흩어져, 로직을 바꾸려면 모든 사용자의 PC에 새 버전을 재배포해야 했다는 점이다.
flowchart TB
subgraph Clients [팻 클라이언트]
PC1["PC + GUI"]
PC2["PC + GUI"]
PC3["PC + GUI"]
end
SERVER[서버
DB만]
Clients --> SERVER
| 특징 | 설명 |
|---|---|
| 클라이언트 | 팻 클라이언트 (많은 로직) |
| 처리 | 대부분 클라이언트에서 |
| 기술 | Visual Basic, Delphi |
| |
2000년대: 다시 중앙 집중 (웹 1.0)
배포 문제를 해결한 것은 웹 브라우저였다. 재배포 없이 URL 하나로 항상 최신 버전을 실행할 수 있다는 점이 결정적이었다. 그 대가로 처리는 다시 서버로 돌아갔다 — 브라우저는 서버가 만들어 보낸 HTML을 그대로 표시하는 씬 클라이언트(thin client)가 되었고, 페이지를 조작할 때마다 서버를 다시 왕복해야 했다.
flowchart LR
subgraph Clients [씬 클라이언트]
B1[브라우저]
B2[브라우저]
B3[브라우저]
end
SERVER[웹 서버
모든 처리]
Clients --> SERVER
| 특징 | 설명 |
|---|---|
| 클라이언트 | 씬 클라이언트 (HTML만 표시) |
| 처리 | 서버 사이드 렌더링 |
| 기술 | PHP, JSP, ASP |
| |
2010년대: 다시 클라이언트로 (SPA)
AJAX가 페이지 전체를 다시 불러오지 않고도 서버와 데이터를 주고받을 수 있게 하면서, 진자는 다시 클라이언트로 향했다. React·Angular·Vue 같은 프레임워크는 브라우저 안에서 HTML을 직접 생성하는 단일 페이지 애플리케이션(SPA)을 가능하게 했고, 서버는 화면이 아니라 데이터(JSON)만 응답하는 API로 축소되었다.
flowchart LR
subgraph Clients [SPA]
R1[React App]
R2[Angular App]
R3[Vue App]
end
API[REST/GraphQL
API 서버]
Clients --> API
| 특징 | 설명 |
|---|---|
| 클라이언트 | 많은 JavaScript 로직 |
| 처리 | 클라이언트에서 렌더링 |
| 기술 | React, Angular, Vue |
| |
2020년대: 또 서버로? (SSR/SSG)
SPA는 초기 로딩이 느리고 검색 엔진이 빈 HTML만 보게 된다는 약점이 있었다. Next.js(2016년 출시)로 대표되는 서버 사이드 렌더링(SSR) 프레임워크는 최초 화면은 서버에서 미리 그려 보내고, 이후 상호작용은 클라이언트에서 처리하는 하이브리드 방식으로 두 세계를 절충했다. 진자가 순수 클라이언트 렌더링에서 다시 서버 쪽으로 조금 움직인 것이다.
flowchart LR
subgraph Hybrid [하이브리드]
NEXT["Next.js
서버 렌더링 + 클라이언트"]
NUXT[Nuxt.js]
REMIX[Remix]
end
| 특징 | 설명 |
|---|---|
| 렌더링 | 서버 + 클라이언트 혼합 |
| 기술 | Next.js, Nuxt.js, Remix |
| 목표 | SEO + 인터랙티브 |
웹은 또 바뀔 것이다
진자는 계속 흔들린다:
timeline
title 웹 기술의 변천 (연도는 대표 기술의 최초 공개 시점 기준)
1993 : CGI 명세 (NCSA)
1995 : PHP
1999 : JSP
2005 : AJAX (용어 등장)
2006 : jQuery
2010 : Angular 1
2013 : React
2014 : Vue
2016 : Next.js
2020 : SSR 회귀 트렌드 본격화
각 전환은 이전 기술의 약점에 대한 반작용이었다는 공통점이 있다.
| 트렌드 | 반작용의 이유 |
|---|---|
| 서버 렌더링 → 클라이언트 렌더링 | 서버 왕복 없이 즉각적인 상호작용을 원했다(SPA) |
| 클라이언트 렌더링 → SSR 하이브리드 | SPA의 느린 초기 로딩과 SEO 약점을 보완하려 했다(Next.js) |
| REST → GraphQL | 클라이언트마다 다른 응답 형태 요구를 하나의 유연한 쿼리로 해결하려 했다 |
“웹 기술은 계속 변한다. 비즈니스 규칙은 이 변화에 영향받지 않아야 한다.”
아키텍처에서의 위치
지금까지 살펴본 다섯 시대는 모두 “GUI가 어디서 실행되는가"의 문제였다. 클린 아키텍처의 관점에서 보면 이 질문 자체가 세부사항이다 — 비즈니스 규칙(Use Case, Entity)은 결과를 어떻게 표현할지 정하는 Presenter Interface만 알면 되고, 그 인터페이스를 웹 컨트롤러가 구현하는지 CLI가 구현하는지는 알 필요가 없다.
flowchart TB
subgraph Core [비즈니스 규칙]
UC[Use Case]
ENT[Entity]
PI[Presenter Interface]
UC --> ENT
UC --> PI
end
subgraph Details [세부사항 - 웹]
WEB[Web Controller
REST API]
CLI[CLI Controller]
GQL[GraphQL Controller]
GRPC[gRPC Controller]
end
WEB -->|구현| PI
CLI -->|구현| PI
GQL -->|구현| PI
GRPC -->|구현| PI
코드 예시
OrderPresenter 인터페이스는 코어(비즈니스 규칙)에 위치하며, Use Case가 결과를 어떤 방식으로 표현할지는 이 인터페이스의 구현체에 위임한다. 다음은 그 인터페이스 정의다.
| |
동일한 인터페이스를 웹 컨트롤러가 구현하면 REST API로, CLI 클래스가 구현하면 콘솔 출력으로 결과가 나타난다. 두 구현체 모두 getOrderUseCase를 호출하는 방식은 같고, 결과를 사용자에게 보여주는 방식만 다르다.
| |
동일한 OrderPresenter를 CLI 클래스가 구현하면, getOrderUseCase를 호출하는 흐름은 그대로 두고 결과를 콘솔에 출력하는 방식만 바뀐다.
| |
Use Case는 동일
WebOrderController와 CliOrderController가 각자 다른 방식으로 결과를 표시해도, 그 결과를 만들어내는 GetOrderUseCase는 동일하다. Use Case는 OrderPresenter 인터페이스만 알 뿐, 그 뒤에 웹 컨트롤러가 있는지 CLI가 있는지 전혀 알지 못한다.
| |
웹 기술 교체 시나리오
앞서 41장의 DB 교체 시나리오와 같은 논리가 여기서도 성립한다. REST API를 GraphQL로 바꾸는 상황을 가정해보면, OrderPresenter를 구현하는 새 리졸버 하나만 추가하면 되고 GetOrderUseCase는 손댈 필요가 없다.
flowchart LR
subgraph Before [REST API]
UC1[Use Case]
REST[REST Controller]
end
subgraph After [GraphQL로 변경]
UC2[Use Case]
GQL[GraphQL Resolver]
end
UC1 -.->|동일| UC2
GraphQL 리졸버도 동일한 OrderPresenter를 구현해 GetOrderUseCase를 재사용한다 — 달라지는 것은 결과를 GraphQL 응답 형태로 반환하는 방식뿐이다.
| |
흔한 오해
“웹은 세부사항이다"라는 말은 웹 기술을 소홀히 다뤄도 된다는 뜻이 아니다. 사용자 경험, 접근성, 브라우저 호환성은 실무에서 여전히 중요한 과제다. 마틴이 강조하는 것은 웹의 중요도가 아니라, 비즈니스 규칙이 HTTP 요청 형식이나 특정 프론트엔드 프레임워크의 존재를 알 필요가 없다는 의존 방향이다. OrderPresenter 인터페이스가 있어도 WebOrderController는 여전히 신중하게 설계해야 한다.
또 다른 오해는 GUI의 진자 운동(중앙 집중 ↔ 클라이언트 분산)이 “결국 다시 원점으로 돌아온다"는 뜻이라고 해석하는 것이다. 실제로는 매번 이전 세대보다 더 나은 도구(정적 타입, 컴포넌트 모델, 서버 컴포넌트)를 갖고 돌아온다 — 반복이 아니라 나선형 발전에 가깝다. 이 장의 요지는 “웹 기술을 예측하지 말라"가 아니라, 어떤 기술이 승리하든 비즈니스 규칙 코드는 한 줄도 바뀌지 않아야 한다는 것이다.
학습 목표
이 장을 읽은 후 다음을 스스로 점검한다.
- “웹은 세부사항이다"라는 명제가 웹이 중요하지 않다는 뜻이 아니라 의존 방향에 대한 명제임을 설명할 수 있는가?
- GUI 역사의 진자 운동(중앙 집중 ↔ 클라이언트 분산)을 최소 세 시기의 구체적 기술로 설명할 수 있는가?
OrderPresenter인터페이스가 어떻게 Use Case를 웹·CLI·GraphQL로부터 격리하는지 코드로 보여줄 수 있는가?- 같은
GetOrderUseCase가 서로 다른 프레젠터 구현체와 함께 동작하는 이유를 설명할 수 있는가?
판단 기준
새 코드가 비즈니스 규칙인지 웹 세부사항인지 판단할 때 다음을 확인한다.
- 이 코드가 HTTP, REST, GraphQL, 특정 프론트엔드 프레임워크의 API를 직접 언급하는가? 그렇다면 웹 세부사항이다.
- 이 코드를 실제 브라우저나 HTTP 서버 없이 단위 테스트할 수 있는가? 그렇다면 비즈니스 규칙에 가깝다.
- 이 로직을 CLI나 배치 작업으로 옮겨도 그대로 재사용할 수 있는가? 재사용할 수 있다면 이미 웹으로부터 잘 분리된 것이다.
참고 자료
- Robert C. Martin, 『Clean Architecture』(2017), 31장 — “The web is a detail” 원칙의 원출처.
- Robert C. Martin, “The Clean Architecture”, The Clean Code Blog (2012) — “The Web is a detail” 문장의 출처.
핵심 요약
| 원칙 | 설명 |
|---|---|
| 웹은 세부사항 | 입출력 장치일 뿐 |
| 진자 운동 | 기술은 계속 변함 |
| 비즈니스 분리 | 웹 변화에 영향 없음 |
| 교체 가능성 | REST → GraphQL도 가능 |
“The GUI is a detail and the web is a GUI. … The web is nothing more than an IO device and hence we should try to isolate it from our business logic.” — Robert C. Martin, 『Clean Architecture』(2017), 31장
![Featured image of post [Clean Architecture] 42. 웹은 세부사항이다](/post/clean-architecture/web-is-detail-gui-history/wordcloud_hu_19d90ff18364af7a.webp)
![[Clean Architecture] 40. 세부사항 서론](/post/clean-architecture/details-introduction-interchangeable-parts/wordcloud_hu_4dc06db68530417.webp)
![[Clean Architecture] 41. 데이터베이스는 세부사항이다](/post/clean-architecture/database-is-detail-persistence/wordcloud_hu_e357f18944822a2c.webp)
![[Clean Architecture] 42. 웹은 세부사항이다](/post/clean-architecture/web-is-detail-gui-history/wordcloud_hu_765ffc49799b88b0.webp)
![[Clean Architecture] 43. 프레임워크는 세부사항이다](/post/clean-architecture/framework-is-detail-coupling-risk/wordcloud_hu_4e83d3140f688b96.webp)
![[Clean Architecture] 44. 사례 연구: 비디오 판매 시스템](/post/clean-architecture/case-study-video-sales-system/wordcloud_hu_3623790368a90bd5.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)
![[Tutorial] Learn Prompting - 프롬프트 엔지니어링 무료 가이드 정리](/post/2022-12-30-learn-prompting/wordcloud_hu_6a9d105de4834753.webp)
![[Clean Architecture] 32. 클린 아키텍처: 동심원과 의존성 규칙](/post/clean-architecture/clean-architecture-concentric-circles-dependency/wordcloud_hu_4f556820609c325c.webp)