7장까지는 계속 아키텍처 특성에 대한 이야기였다.
4장 — 아키텍처 특성이란 무엇인가
5장 — 어떤 특성이 중요한지 어떻게 찾는가
6장 — 그 특성을 어떻게 측정하고 지키는가
7장 — 그 특성을 어디까지 적용하는가7장 마지막에서 Architecture Quantum이라는 단위가 나왔는데, 결국 시스템을 어떤 덩어리로 나눌 것인가의 문제였다.
8장은 그 덩어리의 정체를 다룬다. 바로 컴포넌트다.
컴포넌트란 무엇인가
책에서 컴포넌트는 아주 단순하게 정의된다.
소프트웨어의 구성 블록(building block)
가장 단순한 형태는 패키지나 네임스페이스다. 하지만 컴포넌트는 물리적인 형태에 따라 여러 스케일을 가질 수 있다.
Package / Namespace
↓
Library — 같은 메모리 공간에서 동작
↓
Subsystem — 애플리케이션 내부의 큰 단위
↓
Service — 독립 배포, 원격 호출로 접근즉 컴포넌트는 하나의 정해진 크기가 아니다. 라이브러리도 컴포넌트고, 서비스도 컴포넌트다.
중요한 건 크기가 아니라, 그것이 아키텍처가 다루는 최소 단위라는 점이다.
아키텍트의 역할과 개발자의 역할
여기서 책이 명확하게 선을 긋는다.
아키텍트
→ 컴포넌트를 식별하고, 다듬고, 관리하고, 거버넌스한다
개발자
→ 컴포넌트 안의 클래스와 코드를 설계한다아키텍트가 클래스 하나하나까지 설계하지는 않는다. 대신 어떤 컴포넌트가 존재해야 하는가, 각 컴포넌트가 무엇을 책임지는가를 정한다.
그래서 컴포넌트를 나누는 일이 아키텍트가 하는 가장 대표적인 분할(partitioning) 작업이 된다.
시스템을 나누는 두 가지 방식
컴포넌트를 나눌 때 크게 두 가지 축이 있다.
Technical Partitioning — 기술적 관심사로 나누기
Domain Partitioning — 도메인/워크플로로 나누기Technical Partitioning
전통적인 레이어드 구조가 여기에 해당한다.
Presentation
Business Rules
Services
Persistence관심사의 분리라는 관점에서는 아주 깔끔하다. 프레젠테이션 코드는 프레젠테이션끼리, 영속성 코드는 영속성끼리 모인다.
문제는 하나의 도메인 변경이 발생했을 때다.
"주문 기능을 수정해주세요"
↓
Presentation의 Order 화면
Business의 Order 로직
Persistence의 Order 저장도메인 코드가 여러 레이어에 흩어져 있어서, 하나의 도메인을 고치려면 여러 레이어를 동시에 건드려야 한다. 데이터베이스도 대부분 하나로 공유되기 쉽다.
Domain Partitioning
반대로 도메인 단위로 나눈다.
Catalog Checkout
Shipping
Customer Profile
PaymentDDD의 영향을 받은 방식이고, 각 컴포넌트 안에 그 도메인에 필요한 프레젠테이션과 영속성이 함께 들어간다.
이 방식의 장점은 변경의 단위와 코드의 단위가 일치한다는 것이다. 배송 정책이 바뀌면 Shipping만 보면 된다.
또 이 구조는 이후 마이크로서비스로 넘어가기에도 자연스럽다. 반대로 단점은 공통 코드가 여러 도메인에 중복될 수 있다는 점이다.
여기서 콘웨이의 법칙이 등장한다
컴포넌트를 나누는 방식은 순수하게 기술적인 선택이 아니다.
조직은 자신의 커뮤니케이션 구조를 닮은 시스템을 설계한다.
프론트엔드 팀, 백엔드 팀, DBA 팀으로 나뉜 조직은 자연스럽게 레이어드 구조(기술적 분할)를 만들게 된다. 반대로 도메인 단위의 크로스펑셔널 팀이라면 도메인 분할이 자연스럽다.
그래서 컴포넌트 구조를 정할 때는 조직 구조도 같이 봐야 한다.
컴포넌트를 찾아가는 과정은 반복이다
책은 컴포넌트를 한 번에 완성하는 게 아니라 반복적으로 다듬는 흐름으로 설명한다.
1. 초기 컴포넌트 식별
↓
2. 요구사항을 컴포넌트에 배정
↓
3. 역할과 책임 분석
↓
4. 아키텍처 특성 분석
↓
5. 컴포넌트 재구성
↓
(다시 2번으로)특히 4번이 앞 장들과 연결된다. 컴포넌트를 나눈 뒤에 이 컴포넌트에 필요한 아키텍처 특성이 무엇인지를 다시 확인하고, 그 결과에 따라 경계를 다시 조정한다.
처음 그린 컴포넌트 다이어그램이 최종본이 아니라는 점이 중요하다.
컴포넌트 크기 문제
컴포넌트를 얼마나 잘게 나눌 것인가도 트레이드오프다.
너무 잘게 나누면
→ 컴포넌트 간 통신이 지나치게 많아진다
너무 크게 나누면
→ 결합도가 높아지고, 배포/테스트/확장이 어려워진다7장에서 Architecture Quantum을 작게 만들수록 좋은 것만은 아니라고 했던 이야기와 같은 구조다.
컴포넌트를 발견하는 방법들
그럼 실제로 어떻게 컴포넌트를 찾을까? 책은 몇 가지 접근법을 소개한다.
안티패턴: Entity Trap
가장 먼저 나오는 건 하지 말아야 할 방법이다.
데이터베이스 엔티티를 그대로 컴포넌트로 만드는 것이다.
User → UserManager
Order → OrderManager
Product → ProductManager
Payment → PaymentManager익숙한 그림이다. 그리고 겉보기에는 깔끔해 보인다.
문제는 이게 사실 아키텍처 설계가 아니라 ORM 매핑을 그대로 옮겨놓은 것에 가깝다는 점이다. 실제로 시스템이 하는 일, 사용자가 수행하는 작업이 이 구조에 드러나지 않는다.
그리고 대부분의 실제 작업은 여러 엔티티를 가로지른다. 그러다 보면 특정 매니저 컴포넌트가 지나치게 비대해진다.
Actor/Actions 접근
행위자와 그들이 수행하는 행동을 먼저 찾는다.
Bidder — 입찰한다, 경매를 본다
Auctioneer— 경매를 진행한다, 입찰을 받는다
System — 결제를 처리한다, 낙찰을 기록한다역할이 뚜렷하게 구분되는 시스템에서 잘 맞는다.
Event Storming
DDD에서 나온 방식으로, 시스템에서 발생하는 이벤트를 먼저 찾고 그 이벤트를 중심으로 컴포넌트를 만든다.
메시지 기반 시스템이나 마이크로서비스와 궁합이 좋다.
Workflow 접근
시스템의 워크플로를 따라가면서 컴포넌트를 도출한다. 역할을 식별하고, 어떤 워크플로가 있는지 파악한 뒤, 그 활동을 중심으로 컴포넌트를 만든다.
경매 시스템 예시
책에서는 Going, Going, Gone이라는 온라인 경매 시스템을 예시로 actor/actions 접근을 적용한다.
행위자를 먼저 뽑는다.
Bidder — 원격에서 입찰하는 사람
Auctioneer — 실제 경매를 진행하는 사람
System — 자동으로 수행되는 작업들그리고 각 행위자의 행동을 따라가면 컴포넌트가 나온다.
Video Streamer — 경매 영상 전송
Bid Streamer — 입찰 현황 전송
Bid Capture — 입찰 수집
Bid Tracker — 입찰 추적
Auction Session — 경매 세션 관리
Payment — 결제Entity Trap으로 접근했다면 Auction, Bid, User 같은 이름이 나왔을 것이다. 그런데 actor/actions로 접근하니 시스템이 실제로 하는 일이 컴포넌트 이름에 드러난다.
Bid Capture와 Bid Streamer가 나뉘어 있는 것도 재밌다. 둘 다 입찰을 다루지만 하나는 수집이고 하나는 전송이다. 필요한 아키텍처 특성이 다르다.
그리고 다시 Quantum으로
8장은 결국 7장과 이어진다.
컴포넌트를 나누고 각 컴포넌트에 필요한 아키텍처 특성을 분석했을 때,
모든 컴포넌트가 같은 특성을 공유한다
→ 하나의 Quantum
→ 모놀리식 아키텍처로 충분
컴포넌트마다 필요한 특성이 크게 다르다
→ 여러 개의 Quantum
→ 분산 아키텍처를 고려모놀리스냐 마이크로서비스냐를 취향이나 유행으로 정하는 게 아니라, 컴포넌트 분석의 결과로 결정한다는 이야기다.
프론트엔드에서 생각해보면
Entity Trap 이야기가 특히 익숙했다.
프론트엔드에서도 비슷한 구조를 자주 만든다.
UserStore
ProjectStore
DocumentStore
CommentStore서버 응답 스키마를 그대로 상태 구조로 옮기는 방식이다. 편하지만, 실제로 사용자가 수행하는 작업은 여러 스토어를 가로지른다.
기술적 분할과 도메인 분할의 구분도 그대로 대응된다.
components/
hooks/
utils/
api/기술 종류로 나눈 구조에서는 기능 하나를 고치려면 네 폴더를 다 열어야 한다.
editor/
dashboard/
billing/도메인으로 나누면 변경 범위가 한 폴더 안에 모인다. 7장에서 봤던 Editor / Admin / Billing의 번들 경계 이야기와도 이어진다.
결국 폴더 구조도 일종의 컴포넌트 분할이고, 같은 트레이드오프가 적용되는 것 같다.
정리
8장을 한 줄로 정리하면,
아키텍처 특성을 논하려면 그 특성을 담을 그릇, 즉 컴포넌트를 먼저 제대로 나눠야 한다.
그리고 컴포넌트를 나눌 때 스스로에게 물어봐야 할 질문들.
이 컴포넌트 이름이 시스템이 하는 일을 드러내는가?
DB 테이블 목록을 그대로 옮긴 것은 아닌가?
기술로 나눈 것인가, 도메인으로 나눈 것인가?
우리 조직 구조가 이 분할을 강요하고 있지는 않은가?
컴포넌트 간 통신이 지나치게 많지는 않은가?
각 컴포넌트에 필요한 아키텍처 특성이 실제로 다른가?마지막 질문의 답이 예스라면, 그때 분산 아키텍처를 이야기하면 된다.
레퍼런스
- Mark Richards, Neal Ford, Fundamentals of Software Architecture, 2nd Edition — Chapter 8, Component-Based Thinking