독립성
좋은 아키텍처는 다음을 지원해야 한다.
- 시스템의 유스케이스
- 시스템의 운영
- 시스템의 개발
- 시스템의 배포
유스케이스
시스템 아키텍처는 시스템의 의도를 지원해야 한다. 아키텍트의 최우선 관심사는 유스케이스이며, 아키텍쳐에서도 유스케이스가 최우선이다.
유스케이스는 사용자가 어떠한 목적을 달성하기 위한 행동 시나리오다.
좋은 아키텍처가 행위를 지원하기 위해 할 수 있는 일 중에서 가장 중요한 사항은 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다.
좋은 아키텍처를 갖춘다면, 해당 시스템의 유스케이스는 시스템 구조 자체에서 한눈에 드러날 것이다. 이들 행위는 일급 요소(first-class element)이며 시스템의 최상위 수준에서 알아볼 수 있으므로, 개발자가 일일이 찾아 헤매지 않아도 된다.
프로그래밍에서 일급이란 시스템 내에서 특별하지 않고 기본 단위로 다뤄지는 것을 말한다. 쉽게 말해, 특별 대우를 받지 않고 다른 것들과 동일한 권리를 누린다면 그것을 일급 요소로 볼 수 있다.
보통 아래의 세 가지가 가능하다면 일급으로 간주한다.
- 변수나 데이터 구조에 담을 수 있다. (할당 가능성)
- 함수의 인자(Parameter)로 전달할 수 있다. (전달 가능성)
- 함수의 결과값(Return value)으로 반환할 수 있다. (반환 가능성)
대표적 사례로 일급 함수, 일급 객체 같은 것들이 있다.
아키텍처에서 일급 요소란 무엇일까? 시스템의 정체성을 드러내는 가장 중요한 핵심 개념으로 유스케이스가 시스템 구조의 중심에 드러나 있어야 한다는 뜻으로 볼 수 있다.
src/
components/
hooks/
api/
utils/
store/
위와 같은 구조에서는 “사용자가 주문을 생성하는 로직”이나 로그인 흐름이 어디서 완성되는지가 한 눈에 보이지 않는다. 사용자의 행위는 components, hooks, api, store에 흩어져 숨겨져 있고 이것이 주문 시스템인지 도서 예약 시스템인지 구분할 수 없다.
src/
usecases/
createOrder/
createOrder.ts
createOrder.validator.ts
login/
login.ts
login.policy.ts
여기서는 프로젝트를 열면 기능 목록이 보이고 사용자의 행동이 최상위 개념이 되어 시스템이 무엇을 하는지 명확히 알 수 있다.
운영
시스템의 운영 지원 관점에서 본다면 아키텍처는 더 실질적이며 덜 피상적인 역할을 맡는다. 시스템이 초당 100,000명의 고객을 처리해야 한다면, 아키텍처는 이 요구와 관련된 유스케이스에 걸맞은 처리량과 응답시간을 보장해야 한다.
이러한 형태를 지원한다는 말은 시스템에 따라 다양한 의미를 지닌다. 시스템에 따라 처리 요소를 작은 서비스들로 배열하여 서로 다른 서버에서 병렬로 실행할 수 있게 만들어야 할 수도 있다. 또 다른 곳에서는 경량의 수많은 스레드가 단일 프로세서에서 같은 주소 공간을 공유하도록 만든다는 뜻일 수도 있다.
이러한 결정은 뛰어난 아키텍트라면 열어 두어야 하는 선택사항 중 하나이다.
개발
아키텍처는 개발환경을 지원하는 데 있어 핵심적인 역할을 수행한다. 여기서 콘웨이(Conway)의 법칙이 작용한다.
시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것이다.
조직의 의사소통 구조는 어디서 어떤 요청을 주고받느냐를 의미하고 그와 동일한 구조의 설계는 어떤 팀들이 소프트웨어 정책에 관여하느냐를 말하는 것으로 보인다.
많은 팀으로 구성되며 관심사가 다양한 조직은 각 팀이 독립적으로 행동하기 편한 아키텍처를 확보해야 하며, 개발하는 동안 팀들이 서로를 방해하지 않도록 해야한다.
이런 아키텍처를 만들기 위해서는 잘 격리되어 독립적으로 개발 가능한 컴포넌트 단위로 시스템을 분할해야 한다.
배포
아키텍처는 배포 용이성을 결정하는 데 중요한 역할을 한다. 이때 목표는 즉각적인 배포(immediate deployment)다. 좋은 아키텍처는 꼭 필요한 디렉토리나 파일을 수작업으로 생성하게 두지 않는다.
좋은 아키텍처라면 시스템이 빌드된 후 즉각 배포할 수 있도록 지원해야 한다. 이런 아키텍처를 만들려면 시스템을 컴포넌트 단위로 적절하게 분할하고 격리해야 한다.
선택사항 열어놓기
좋은 아키텍처는 컴포넌트 구조와 관련된 이 관심사들 사이에서 균형을 맞추고, 각 관심사 모두를 만족시킨다.
하지만 이러한 균형을 잡는 것은 매우 어렵다. 우리는 모든 유스케이스를 알 수는 없으며, 운영하는 데 따르는 제약사항, 팀 구조, 배포 요구사항도 알지 못하기 때문이다. 더 문제가 되는 것은 이러한 것들을 알고 있더라도 시스템의 생명주기를 거쳐감에 따라 이 사항들도 반드시 변해간다는 사실이다.
그러나 이 변화 속에서도 사라지지 않는 것이 있다. 몇 아키텍처 원칙은 구현하는 비용이 비교적 크지 않으며, 관심사들 사이에서 균형을 잡는 데 도움이 된다. 이 원칙들은 시스템을 격리된 컴포넌트 단위로 분할할 때 도움이 되며, 이를 통해 선택사항을 최대한 많이, 오랫동안 열어둘 수 있게 해준다.
좋은 아키텍처는 선택사항을 열어 둠으로써, 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.
계층 결합 분리(Decoupling Layers)
아키텍트는 필요한 모든 유스케이스를 지원할 수 있는 구조를 원하지만 모든 유스케이스를 다 알지 못한다. 그러나 시스템의 기본적인 의도는 분명히 알고 있다. (장바구니 시스템인지, 주문 처리 시스템인지)
따라서 단일 책임 원칙(SRP)과 공통 폐쇄 원칙(CCP)을 적용하여, 의도의 맥락에 따라 다른 이유로 변경되는 것들을 분리하고, 동일한 이유로 변경되는 것들을 묶는다.
업무 규칙은 그 자체가 애플리케이션과 밀접한 관련이 있거나 더 범용적일 수도 있다. 예를 들어 입력 필드 유효성 검사는 애플리케이션 자체와 밀접한 업무 규칙이다. 반대로 계좌의 이자 계산이나 재고품 집계는 업무 도메인에 밀접한 업무 규칙이다.
서로 다른 두 유형의 규칙은 각자 다른 속도와 이유로 변경될 것이다. 따라서 이들 규칙은 서로 분리하고, 독립적으로 변경할 수 있도록 만들어야 한다. DB, 쿼리 언어, 스키마도 기술적인 세부사항이며, 업무 규칙이나 UI와는 아무런 관련이 없다. 아키텍트는 이들을 시스템의 나머지 부분으로부터 분리하여 독립적으로 변경할 수 있도록 해야한다.
이처럼 시스템은 서로 결합하지 않은 수평적인 계층으로 분리가 필요하다.
유스케이스 결합 분리(Decoupling Usecases)
서로 다른 이유로 변경되는 것에는 무엇이 또 있을까? 바로 유스케이스 그 자체이다. 주문 입력 시스템에서 주문을 추가하는 유스케이스는 주문을 삭제하는 유스케이스와는 다른 속도로, 다른 이유로 변경된다. 유스케이스는 시스템을 분할하는 매우 자연스러운 방법이다.
이와 동시에 유스케이스는 시스템의 수평적인 계층을 가로지르도록 자른 수직으로 좁다란 조각이기도 하다. 각 유스케이스는 UI의 일부, 애플리케이션 특화 업무 규칙의 일부, 애플리케이션 독립적 업무 규칙의 일부, DB 기능의 일부를 사용한다.
따라서 우리는 시스템을 수평적으로 분할하면서 해당 계층을 가로지르는 얇은 수직적인 유스케이스로 시스템을 분할할 수 있다.

이와 같이 결합을 분리하려면 주문 추가 유스케이스의 UI와 주문 삭제 유스케이스의 UI를 분리해야 한다. 업무 규칙과 DB도 마찬가지다. 시스템의 맨 아래 계층까지 수직으로 내려가며 유스케이스들이 각 계층에서 서로 겹치지 않게 한다.
시스템에서 서로 다른 이유로 변경되는 요소들의 결합을 분리하면 기존 요소에 지장을 주지 않고 새로운 유스케이스를 계속 추가할 수 있게 된다.
클린 아키텍처에서는 수평 분리(Layering)는 책임의 종류에 따라 나눈다. 변경의 이유가 다른 것들끼리 구분한다는 의미로 볼 수 있다.
정책(domain), 비즈니스 로직(application), UI(presentation), 기타 외부 환경(infrastructure)와 같이 나눌 수 있다.
수직 분리는 유스케이스를 단위로 나눈다. 이는 application 계층에서 가장 강하게 나타나는 편이다. 이 또한 변경을 최소화하기 위함이다.
src/
domain/
Order.ts
DiscountPolicy.ts
application/
submitOrder.ts
ports/
PaymentGateway.ts
OrderRepository.ts
infrastructure/
paymentGateway.http.ts
orderRepository.graphql.ts
presentation/
CheckoutPage.tsx
FSD 아키텍처는 수평 분리는 두 가지가 있는데 첫 번째는 전역적인 수평 구조 분할이다. app, pages, features, entities, shared 등으로 나누고 이는 역할과 책임(추상화 레벨) 기준이다.
그리고 수직 분리는 비즈니스 도메인(order, cart, checkout...)에 따라 나눈다.
슬라이스 내부에서 두 번째 수평 분리가 이뤄지는데 이것은 관심사(ui, model, api...)에 따른 분리이다.
src/
app/
pages/
checkout/
page.tsx
features/
checkout/
ui/SubmitOrderButton.tsx
model/useSubmitOrder.ts
api/submitOrder.api.ts
entities/
order/
model/order.ts
shared/
ui/
lib/
클린 아키텍처와 FSD 아키텍처를 비교해보도록 하자. 이 아키텍처 모두 수평 + 수직 분할을 하는 공통점이 있다.
클린 아키텍처
- 목적: 정책을 세부사항으로부터 보호하여 변경의 전파를 줄이고 독립적 변경을 지원하는 것.
- 핵심: 의존성 방향 제어(DIP) + SRP/CCP 기반의 경계(boundary)
FSD
- 목적: 제품/기능 관점에서 코드 탐색성을 높이고 확장성을 높이는 구조를 만드는 것.
- 핵심: 전역 레이어(추상화/재활용 레벨) + 슬라이스(기능 단위) 구조화
수평 분할의 공통점과 차이점
공통점
- 둘 다 수평 분할을 통해 서로 다른 성격의 코드를 분리한다.
- 목표는 결합도를 낮추고 변경 영향을 국소화하는 것.
차이점
- 클린 아키텍처의 수평 분할 기준 = 변경 트리거의 종류(책임의 종류)
- 도메인 규칙(
domain), 유스케이스 규칙(application), 세부사항(infrastructure), 표현(presentation) - '누가 누구를 몰라야 하는가?'가 핵심
- 도메인 규칙(
- FSD 수평 분할 기준 = 추상화 레벨, 재사용 범위
shared/entities/features/pages/app- '어디에 두면 탐색/재사용/조립이 쉬운가'가 핵심
수직 분할의 공통점과 차이점
공통점
- 둘 다 기능 단위로 잘라서 변경을 국소화하려 함.
- 그래서
usecase와feature가 많이 닮아 보임.
차이점
- 클린 아키텍처의 수직 분할 기준 = 유스케이스(사용자 목적/행동 시나리오)
application레이어에서 유스케이스가 1급 단위로 드러남.- domain은 엔티티/정책/VO 같은 개념 단위가 더 자연스러울 수 있고, infra는 기술 구현 단위로 갈라지는 경향이 있음.
- FSD 수직 분할 기준 =
feature(제품 기능 단위 슬라이스)feature내부에ui/model/api등 관심사가 분리됨.- 도메인 개념은
entities로 올려서 재사용을 유도.
현실에서 feature는 '사용자 목적'을 담고 유스케이스 또한 사용자 목적이어서 feature ≈ 유스케이스 묶음처럼 보인다.
둘 다 분리의 목표가 변경의 최소화이기 때문이다.
결정적인 차이가 있다면 클린 아키텍처는 정책을 세부사항으로부터 보호하기 위해 의존성 규율을 강하게 요구한다. 안쪽이 바깥을 알 수 없는 것이 본질이다. FSD는 제품 구조를 읽기 쉽게 배치하는 규율이 강하고, 의존성 통제는 프로젝트 규칙에 따라 달라진다. 그러므로 구조가 잘 보여도 정책과 세부사항이 섞일 수도 있다.
결합 분리 모드
이렇게 결합을 분리하면 두 번째 항목인 운영 관점에서 어떤 의미가 있는지 살펴보자.
유스케이스에서 서로 다른 관점(aspect)이 분리되었다면, 높은 처리량을 보장해야 하는 유스케이스와 낮은 처리량으로도 충분한 유스케이스는 이미 분리되어 있을 가능성이 높다. UI와 DB가 업무 규칙과 분리되어 있다면, UI와 DB는 업무 규칙과는 다른 서버에서 실행될 수 있다. 높은 대역폭을 가진 유스케이스는 여러 서버로 복제하여 실행 가능하다.
간단히 말해 유스케이스를 위해 수행하는 결합 분리 작업은 운영에도 도움이 된다. 하지만 운영 측면에서 이점을 살리기 위해서는 결합을 분리할 때 적절한 모드를 선택해야 한다. 분리된 컴포넌트를 서로 다른 서버에서 실행해야 한다면 단일 프로세서의 동일한 주소 공간에 함께 상주하는 형태로 만들어져서는 안된다.
개발 독립성
컴포넌트가 완전히 분리되면 팀 사이의 간섭은 줄어든다. 업무 규칙이 UI를 알지 못하면 UI에 중점을 둔 팀은 업무 규칙에 중점을 둔 팀에 영향을 줄 수 없다. 유스케이스 자체도 결합이 분리되면 주문 추가와 주문 삭제 유스케이스는 서로 개입할 가능성이 거의 없다.
계층과 유스케이스의 결합이 분리되는 한 시스템 아키텍처는 그 팀 구조를 뒷받침해줄 것이다.
배포 독립성
유스케이스와 계층의 결합이 분리되면 배포 측면에서도 고도의 유연성이 생긴다. 결합을 제대로 분리했다면 운영 중인 시스템에서도 계층과 유스케이스를 교체할 수 있다.
중복
소프트웨어에서 중복은 일반적으로 나쁜 것이다. 하지만 중복에도 여러 종류가 있다.
진짜 중복은 인스턴스가 변경되면, 동일한 변경을 그 인스턴스의 모든 복사본에 반드시 적용해야 한다.
또 다른 중복은 거짓된 또는 우발적 중복이다. 중복으로 보이는 두 코드 영역이 각자의 경로로 발전한다면, 즉 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는 진짜 중복이 아니다.
예를 들어 두 유스케이스의 화면 구조가 매우 비슷하다고 가정해보자. 이 구조에 사용할 코드를 통합하고 싶은 유혹을 강하게 느낄 것이다. 하지만 이는 우발적 중복일 가능성이 높다. 시간이 지나며 두 화면은 서로 다른 방향으로 분기하며, 결국 매우 다른 모습을 가질 가능성이 높다.
유스케이스를 수직으로 분리할 때 이런 문제와 마주칠 것이고, 이들 유스케이스를 통합하고 싶다는 유혹을 받게 될 것이다. 서로 비슷한 화면 구조, 알고리즘, DB 쿼리와 스키마를 갖기 때문이다. 자동반사적으로 중복을 제거해버리는 잘못을 저지르는 유혹을 떨쳐내고 중복이 진짜 중복인지 확인해야 한다.
마찬가지로 계층을 수평으로 분리하는 경우, 특정 DB 레코드 데이터 구조가 특정 화면의 데이터 구조와 상당히 비슷하다는 점을 발견할 수도 있다. 이 DB 레코드를 그대로 UI까지 전달하고 싶다는 유혹을 받을 수도 있다. 이러한 중복은 거의 확실히 우발적이다.
결합 분리 모드(다시)
결합 분리 모드로 돌아가자. 계층과 유스케이스의 결합을 분리하는 방법은 다양하다. 소스 코드 수준에서도 가능하며, 바이너리 코드(배포) 수준에서도, 실행 단위(서비스) 수준에서도 가능하다.
프로젝트 초기 단계에서는 어떤 것이 최선인지 알기 어렵다. 프로젝트가 성숙해갈수록 적절한 모드가 달라질 수도 있다.
클린 아키텍처는 보통 백엔드나 네이티브 앱에서 많이 쓰이는 구조이고 웹에서는 찾아보기 쉽지 않다.
그럼 왜 백엔드에서 많이 쓰이는 걸까?
- 백엔드는 "정책이 중심"인 시스템이다.
이런 정책들은 갑자기 DB 마이그레이션을 해도 살아남아야 하고, API를 REST에서 GraphQL로 바꿔도 유지되어야 하며, 레거시 프레임워크를 걷어내어도 독립적으로 잘 동작해야 한다. - 백엔드는 "외부 의존성이 많다"
DB, 메시지 브로커, 캐시, 외부 API, 인증 서버 등 여러 의존성이 있고 이러한 것들은 전부 "세부사항"이다. 클린 아키텍처는 이를 정책과 분리하고자 태어난 구조이다. - 백엔드는 "시스템 수명이 길다"
백엔드 시스템은 보통 5~10년 중장기적으로 가는 경우가 많다. 그러므로 기술 교체 비용을 줄이는 것이 매우 중요하다. 이 점은 클린 아키텍처의 목적과 정확히 일치한다.
웹에서는 왜 덜 쓰였을까?
고전적인 웹(JSP, ASP, PHP)은 서버 렌더링 중심이었고 브라우저는 단순히 표현을 위한 수단이었다. 그래서 굳이 클린 아키텍처를 도입할 이유가 없었다.
그렇다면 네이티브 앱에서는 왜 많이 쓰이는걸까?
모바일 앱은 오프라인 모드, 로컬 DB, 캐싱, 동기화, 백그라운드 작업, OS 생명주기 관리, 디바이스 네이티브 API 관리, 푸시 등 클라이언트지만 서버만큼 복잡한 시스템이다. 그래서 MVVM과 같은 패턴으로 해결할 문제가 아니고 플랫폼 코드에서 도메인을 분리해야 한다는 관점에서 클린 아키텍처가 중용된 것이다.
현대의 웹은?
현대의 웹은 이야기가 좀 달라진다. 고전적인 웹에서 벗어나 프론트엔드 쪽에서 많은 것들이 올라오게 되었다. 애플리케이션 정책, 클라이언트 상태 관리, 캐싱/동기화 전략, 낙관적 업데이트, 네트워크 에러 핸들링, UI 관리 등 이전보다 신경써야할 것들이 많아져서 복잡도가 높은 시스템이 되었다.
아키텍처는 프로젝트에 따라 적절하게 유동적으로 선택되어야하지만 복잡도가 높은 시스템이라면 웹에서도 클린 아키텍처를 써볼 수도 있지 않을까? FSD와 섞어서 적절한 절충안을 도입해도 괜찮을 것 같다.
결론
시스템의 결합 분리 모드는 시간이 지나면서 바뀌기 쉬우며, 뛰어난 아키텍트라면 이러한 변경을 예측하여 큰 무리 없이 반영할 수 있도록 만들어야 한다.