
소프트웨어 아키텍처 스타일을 처음 보면 이름부터 많다.
Shared Repository
Blackboard
Event Broker
Event Mediator
Publish/Subscribe
...
처음에는 전부 다른 구조처럼 보였는데, 수업을 들으면서 한 가지 질문을 던지니 조금씩 구분되기 시작했다.
"이 시스템에서는 누가 다음 작업을 결정하는가?"
데이터 중심 스타일에서는 여러 컴포넌트가 하나의 데이터를 어떻게 공유하는가가 중요하다.
이벤트 드리븐 스타일에서는 컴포넌트들이 서로 직접 호출하지 않고 이벤트가 발생했을 때 누가 어떻게 반응하는가가 중요하다.
그리고 같은 데이터 중심 스타일 안에서도 Shared Repository와 Blackboard는 흐름을 주도하는 주체가 다르다.
이번 글에서는 이 차이를 중심으로 정리해본다.
데이터 중심 스타일(Data-Centered Style)의 가장 큰 특징은 이름 그대로 데이터가 시스템의 중심에 있다는 것이다.
기본적인 구조를 단순하게 그리면 다음과 같다.
Component A
│
▼
Component B → Data Store ← Component C
▲
│
Component D
여러 소프트웨어 컴포넌트가 중앙의 데이터 저장소를 공유한다.
중요한 것은 컴포넌트 A와 B가 직접 데이터를 주고받는 것이 아니라는 것이다.
A ──직접 통신──> B
보다는
A ──> Data Store <── B
의 형태가 된다.
즉 데이터 저장소가 컴포넌트 사이의 공유 공간 역할을 한다.
구조적으로는 크게 두 부분으로 나눌 수 있다.
Data Store
+
Data Accessor
Data Store는 데이터를 보관하는 중앙 저장소이고,
Data Accessor는 그 저장소에 접근하는 독립적인 소프트웨어 컴포넌트 또는 에이전트다.
따라서 데이터 중심 스타일에서는
데이터가 Data Accessor 사이의 의사소통 수단이 된다.
대표적인 데이터 중심 스타일은 다음 두 가지다.
Data-Centered Style
│
├── Shared Repository
│
└── Blackboard
둘 다 중앙에 데이터 저장소가 있다는 점은 같다.
그런데 결정적인 차이가 있다.
Shared Repository
→ 저장소 Passive
→ Client Active
Blackboard
→ 저장소 Active
→ Client(KS) Passive
여기서 처음 든 의문은 이것이었다.
"데이터 저장소가 능동적이라는 게 무슨 말이지?"
여기서 Active와 Passive는 단순히 데이터베이스가 CPU 연산을 하느냐 마느냐의 이야기가 아니다.
핵심은 누가 시스템의 처리 흐름을 주도하느냐에 가깝다.
Shared Repository를 생각해보자.
Client
│
│ "이 데이터 저장해줘"
▼
Repository
클라이언트가 먼저 요청한다.
예를 들어
INSERT ...
UPDATE ...
DELETE ...
SELECT ...
등을 요청하는 것이다.
Repository가 혼자 판단해서
"새 데이터가 들어왔네?
그럼 다음 프로그램을 실행해야겠다."
라고 결정하는 것이 아니다.
따라서
Client → Active
Repository → Passive
라고 한다.
즉 클라이언트가 로직의 흐름을 제어한다.
Shared Repository의 구조를 조금 더 자세히 보면 다음과 같다.
Client A ──────┐
Client B ──────┤
Client C ──────┼──> Repository
Client D ──────┤
Client E ──────┘
하나의 Data Store
+
여러 Client
로 구성된다.
클라이언트와 저장소가 상호작용하는 수단이다.
예를 들어
직접적인 데이터 접근
Database Query
Procedure Call
API
등이 될 수 있다.
쉽게 말하면
"클라이언트가 데이터베이스에 접근하기 위해 사용하는 수단"
이라고 생각하면 된다.
가장 중요한 제약은
Data Store = Passive
Client = Active
라는 것이다.
클라이언트가 요청을 보내고 로직의 흐름을 제어한다.
대표적으로 대량의 정보를 오랫동안 저장해야 하는 시스템에 적합하다.
가장 이해하기 쉬운 예가 관계형 데이터베이스 관리 시스템이다.
GUI Client
│
▼
Repository
▲ ▲ ▲
│ │ │
insert update delete
여러 클라이언트가 GUI나 Command Line 등을 통해
Insert
Update
Delete
Retrieve
작업을 수행한다.
데이터는 중앙 저장소에 모여 있고 클라이언트들이 필요할 때 접근한다.
조금 의외였던 예제가 Compiler와 IDE다.
컴파일러를 생각하면 보통 다음 과정부터 떠올린다.
Source Code
↓
Lexical Analysis
↓
Syntax Analysis
↓
Semantic Analysis
↓
Code Generation
Syntax Analysis 과정에서는 대표적으로 AST(Abstract Syntax Tree) 같은 구조가 만들어질 수 있다.
Repository 기반으로 구성하면 이러한 중간 정보를 중앙 저장소에 두고 여러 처리기가 공유할 수 있다.
Lexical Analyzer ──────┐
Syntax Analyzer ──────┤
Semantic Analyzer ─────┤
▼
Repository
┌───────────┐
│ AST │
│ Symbol │
│ Grammar │
│ Output │
└───────────┘
▲
Optimizer ─────────────┤
Code Generator ────────┘
즉 각 처리기가 서로 직접 데이터를 넘기는 대신 Repository의 공통 데이터를 읽고 수정할 수 있다.
여기서 이전에 배운 Batch Sequential이나 Pipe-and-Filter와의 차이도 생각해볼 수 있다.
Pipe-and-Filter
A → B → C → D
Repository
┌→ A
├→ B
DB ───┼→ C
└→ D
Pipe-and-Filter에서는 처리 결과가 다음 처리 단계로 흐르는 구조가 중요했다.
Repository에서는 여러 처리기가 동일한 중앙 데이터를 공유한다는 것이 핵심이다.
첫 번째는 확장성(Scalability)이다.
특히 읽기 중심 구조에서는 수평 확장이 비교적 용이하다.
두 번째는 변경 용이성(Modifiability)이다.
새로운 클라이언트를 추가하거나 기존 클라이언트의 기능을 수정하기 쉽다.
기존
A ─┐
B ─┼── Repository
C ─┘
새 기능 추가
A ─┐
B ─┤
C ─┼── Repository
D ─┘
기존 A, B, C를 모두 수정하지 않고 D를 추가할 수 있다.
또한 모든 컴포넌트가 같은 공간의 데이터를 공유한다.
Component A가 데이터 변경
↓
Repository
↓
다른 Component도 변경된 데이터 사용
컴포넌트끼리 계속 데이터를 복제하거나 전달할 필요가 줄어든다는 장점이 있다.
Shared Repository의 가장 직관적인 문제는 Single Point of Failure(SPOF)다.
A ─┐
B ─┤
C ─┼── Repository 💥
D ─┤
E ─┘
모든 컴포넌트가 Repository에 의존하고 있는데 Repository가 죽으면 어떻게 될까?
전체 시스템이 영향을 받을 수 있다.
따라서 신뢰성(Reliability), 그리고 결과적으로 가용성 측면에서 문제가 될 수 있다.
또 다른 문제는 데이터 구조와 클라이언트 사이의 높은 의존성이다.
Repository Schema 변경
↓
Client A 수정
Client B 수정
Client C 수정
Client D 수정
중앙 데이터 구조를 변경했는데 모든 클라이언트가 그 구조를 알고 있다면 변경의 영향 범위가 매우 커질 수 있다.
Blackboard 역시 중앙에 데이터를 공유한다.
그래서 겉으로 보면 Repository와 매우 비슷하다.
KS1 ─┐
KS2 ─┤
KS3 ─┼── Blackboard
KS4 ─┤
KS5 ─┘
하지만 가장 큰 차이는
Shared Repository
Client가 먼저 요청
Blackboard
Blackboard의 상태 변화가 다음 처리를 유발
한다는 점이다.
Blackboard에서는 클라이언트를 특별히
Knowledge Source
KS
지식 소스
라고 부른다.
KS를 처음 이해할 때는 특정 문제 하나를 해결할 줄 아는 독립적인 Agent처럼 생각하면 편하다.
예를 들어 하나의 복잡한 문제가 있다고 하자.
Raw Data
↓
정보 추출
↓
Tree 생성
↓
계층 구조 생성
↓
최종 결과
이 모든 것을 하나의 거대한 프로그램이 처리하도록 만들 수도 있다.
하지만 Blackboard에서는 문제를 독립적인 해결 단위로 분해한다.
KS1 = Raw Data에서 정보 추출
KS2 = 추출된 정보로 Tree 생성
KS3 = 여러 Tree를 계층 구조로 구성
각 KS는 자신이 해결할 수 있는 문제만 알고 있다.
그래서 중요한 것은
KS들을 얼마나 잘 정의하느냐
이다.
즉 복잡한 문제를 독립적인 해결 단위로 얼마나 잘 분해할 수 있는가가 Blackboard 설계의 핵심이 된다.
처음 Blackboard에 Raw Data가 들어왔다고 생각해보자.
Blackboard
[ Raw Data ]
Control이 Blackboard의 상태 변화를 관찰한다.
"Raw Data가 들어왔네?"
현재 상태를 처리할 수 있는 KS를 찾는다.
KS1 : Raw → 정보 추출
KS1이 실행된다.
Blackboard
[ Raw Data ]
↓
[ 추출된 정보 ]
Blackboard의 상태가 다시 바뀌었다.
그러면 또 조건을 확인한다.
"추출된 정보가 존재하네?"
→ KS2 실행
KS2가 Tree를 만든다.
Blackboard
Raw Data
↓
추출된 정보
↓
Tree
상태가 또 변경된다.
"Tree가 생겼네?"
→ KS3 실행
결국
Raw Data
↓
[KS1]
↓
Extracted Data
↓
[KS2]
↓
Tree
↓
[KS3]
↓
Hierarchy
처럼 문제가 단계적으로 해결된다.
여기서 중요한 질문이 생긴다.
"Blackboard가 직접 KS를 호출하는 건가?"
조금 더 정확하게는 Control이 존재한다.
┌───────────┐
│ Control │
└─────┬─────┘
│ 상태 관찰
▼
┌───────────┐
│ Blackboard│
└───────────┘
▲ ▲ ▲
│ │ │
KS1 KS2 KS3
Control은 Blackboard의 변화를 관찰하고
다음에 어떤 Action을 실행할 것인가?
를 결정한다.
그리고 조건에 맞는 KS를 활성화한다.
따라서 Blackboard에서 중요한 것은 단순히 데이터를 저장하는 것이 아니다.
현재 Blackboard 상태
↓
실행 가능한 KS 판단
↓
KS 실행
↓
Blackboard 상태 변경
↓
다시 실행 가능한 KS 판단
↓
...
이 순환 구조가 핵심이다.
[Shared Repository]
Client
│
│ 요청
▼
Repository
"Client가 뭘 할지 결정한다."
[Blackboard]
Blackboard 상태 변경
↓
Control
↓
실행할 KS 결정
↓
KS
↓
Blackboard 변경
"현재 문제 상태에 따라 다음 처리가 결정된다."
그래서 수업에서 말한
Repository
저장소 Passive
Client Active
Blackboard
저장소 Active
Client Passive
라는 차이를 이해할 수 있다.
Blackboard가 잘 어울리는 대표적인 문제가 음성 인식이다.
음성 인식은 한 번의 처리로 답이 나오는 단순한 문제가 아니다.
예를 들어 개념적으로
음성
↓
Segmentation
↓
Syllable Creation
↓
Word Creation
↓
결과
처럼 여러 전문적인 처리 과정이 필요할 수 있다.
각 처리기를 KS로 구성한다.
KS1 → Segmentation
KS2 → Syllable Creation
KS3 → Word Creation
그리고 Blackboard의 현재 상태를 보고 어떤 KS가 현재 문제 해결에 기여할 수 있는지 판단한다.
새로운 입력 발생
↓
Blackboard 상태 확인
↓
실행 가능한 KS 선택
↓
KS 동작
↓
Blackboard Update
↓
다시 상태 확인
이 과정이 반복되면서 답에 가까워진다.
가장 큰 장점은 기능 확장성(Extensibility)이다.
KS가 독립적으로 구성되어 있다면
KS1
KS2
KS3
에 새로운 기능이 필요할 때
KS1 KS2 KS3 KS4 ← 추가처럼 새로운 Knowledge Source를 추가할 수 있다.
따라서 변경 용이성과 재사용성도 좋아질 수 있다.
또 KS들이 충분히 독립적이라면 여러 KS를 동시에 실행할 수 있다.
┌→ KS1 BB ────┼→ KS2 └→ KS3즉 Concurrency를 활용할 수 있고 이는 성능과 확장성으로 이어질 수 있다.
16. Blackboard의 단점
하지만 모든 KS가 Blackboard 구조를 알고 있다.
따라서 Blackboard의 데이터 구조 자체가 변경되면
Blackboard 구조 변경 ↓ KS1 영향 KS2 영향 KS3 영향 KS4 영향처럼 전체 KS가 영향을 받을 수 있다.
또 하나의 어려움은
"언제 문제 해결이 끝났다고 판단할 것인가?"
이다.
KS가 Blackboard를 변경하고,
그 변경이 다른 KS를 실행시키고,
다시 Blackboard가 변경되는 구조이기 때문이다.
여러 KS가 동시에 실행될 수도 있기 때문에 시스템 동작이 항상 똑같은 순서로 나타난다고 보장하기도 어렵다.
따라서 시스템 설계와 테스트가 어려워지고 시험 용이성(Testability)이 떨어질 수 있다.
17. 이제 완전히 다른 관점: Event-Driven Architecture
앞에서는 중앙 데이터가 중심이었다.
이번에는 Event가 중심이다.
기존 프로그램에서는 보통 특정 대상을 직접 호출한다.
A │ ├── call B() │ └── call C()이것을 명시적 호출(Explicit Invocation)이라고 볼 수 있다.
A가 B를 정확히 알고 있다.
반면 이벤트 기반에서는
A │ └── "OrderCreated 발생!" ↓ Event System ↙ ↘ B CA가 B나 C를 직접 호출하지 않는다.
A는 단지
OrderCreated라는 이벤트를 발행한다.
그리고 그 이벤트에 관심 있는 컴포넌트가 반응한다.
이것이 묵시적 호출(Implicit Invocation)이다.
18. Message와 Event는 같은 것일까?
둘은 완전히 같은 말은 아니다.
Message는 컴포넌트 사이에서 전달되는 데이터 단위다.Message 안에는 여러 의미가 들어갈 수 있다.
Message ├── Event ├── Command └── Information예를 들어
CreateOrder는 특정 작업을 수행하라는 Command가 될 수 있다.
반면
OrderCreated는
"주문이 이미 생성되었다."
라는 상태 변화를 알리는 Event다.
따라서 Event는 보통 직접적인 응답을 요구하기보다
"이런 일이 발생했다."라고 알리는 데 초점이 있다.
19. Event Broker Style
첫 번째 이벤트 기반 스타일은 Event Broker다.
구조는 다음처럼 생각할 수 있다.
Publisher │ │ Event ▼ ┌──────────┐ │ Broker │ └──────────┘ │ │ │ ▼ ▼ ▼ S1 S2 S3Broker는 발행자가 보낸 이벤트를 받아 적절한 구독자에게 전달한다.
주요 컴포넌트는
Event Publisher Event Subscriber Event Broker [Event Stream Processor]다.
Publisher는 이벤트를 생성한다.
Subscriber는 자신이 관심 있는 이벤트를 비동기적으로 받아 처리한다.
Broker는 이벤트를 수집하고 라우팅한다.
20. Broker가 Workflow까지 결정하는 것은 아니다
여기서 굉장히 중요한 차이가 있다.
Broker의 핵심 역할은
"이 이벤트를 누구에게 전달할까?"이다.
즉 메시지를 전달하고 라우팅하는 역할에 가깝다.
전체 업무 흐름을 중앙에서 완전히 제어하는 것은 아니다.
예를 들어 주문 시스템을 생각해보자.
OrderCreated ↓ Payment ↓ Inventory ↓ ShippingBroker 스타일에서는 각각의 컴포넌트가 이벤트를 받아 처리하고 다시 다음 이벤트를 발생시킬 수 있다.
OrderCreated ↓ Payment Service ↓ PaymentCompleted ↓ Inventory Service ↓ InventoryUpdated ↓ Shipping Service각 컴포넌트는 다른 컴포넌트를 직접 알 필요가 없다.
그래서 결합도가 낮아진다.
하지만 반대로 생각하면
"지금 주문이 전체 과정 중 어디까지 왔지?"
를 중앙에서 파악하기 어려울 수 있다.
21. 그래서 Event Mediator가 등장한다
복잡한 Workflow를 관리해야 한다면 중간에 Event Mediator를 둘 수 있다.
Event │ ▼ ┌─────────────┐ │ Mediator │ └─────────────┘ │ │ │ ▼ ▼ ▼ Payment Stock ShippingMediator는 단순히 이벤트를 전달하는 것을 넘어
현재 주문이 어디까지 진행되었는가? 다음에는 어떤 작업을 해야 하는가? 실패했다면 재시도해야 하는가? 보상 처리가 필요한가?등을 판단하면서 Workflow를 관리한다.
22. 배송 시스템으로 Broker와 Mediator 비교하기
Broker 방식이라면
OrderCreated ↓ Payment ↓ PaymentCompleted ↓ Inventory ↓ InventoryUpdated ↓ Shipping처럼 이벤트가 이어진다.
각 서비스는 자신에게 필요한 이벤트에만 반응한다.
반면 Mediator 방식은
┌───────────────┐ │ Order Mediator│ └───────────────┘ │ │ │ ┌─────┘ │ └─────┐ ▼ ▼ ▼ Payment Inventory ShippingMediator가 전체 진행 상황을 알고 있다.
예를 들어
1. 주문 생성 2. 결제 요청 3. 결제 완료 확인 4. 재고 확인 5. 배송 요청순서를 직접 관리할 수 있다.
따라서 네 필기의
"배송 중 상태에서 다시 이전 단계로 못 넘어가게 한다?"
라는 생각은 워크플로우 제어라는 방향에서 이해하면 된다.
정확히는 단순히 이전 단계로 못 가게 하는 것만이 목적은 아니다.
Mediator가
현재 상태 다음 상태 실패 처리 재시도 보상 처리등 전체 Workflow를 관리할 수 있다는 것이 핵심이다.
23. Broker vs Mediator
두 구조의 차이는 다음 질문으로 기억하면 편하다.
Broker "누구에게 전달할까?"Mediator "다음에는 무엇을 해야 하지?"Broker는 서비스들을 강하게 분리할 수 있다.
반면 Mediator는 전체 흐름을 더 쉽게 통제할 수 있다.
그 대신 중앙의 Mediator에 대한 의존성이 생긴다.
24. Event Broker의 장점과 단점
Broker 방식의 가장 큰 장점은 Decoupling이다.
이벤트 구독자들이 서로를 직접 알 필요가 없다.
A → Broker → B → C → D따라서 각각 독립적으로 확장하기 쉽고 높은 확장성과 응답성, 성능을 기대할 수 있다.
또 하나 중요한 장점은 내고장성이다.
각 컴포넌트가 분리되어 있기 때문에 하나의 서비스 장애가 반드시 전체 시스템 장애로 이어지는 구조는 아니다.
하지만 전체 Workflow를 제어하기 어렵다.
이 이벤트가 마지막인가? 모든 처리가 끝났나? 중간에서 실패했나?를 파악하기 어려울 수 있다.
또 비동기 처리 때문에 복구와 재시작이 복잡해질 수 있으며 데이터 비일관성 문제도 발생할 수 있다.
25. Event Mediator의 장점과 단점
Mediator의 장점은 Broker의 약점을 반대로 생각하면 된다.
Workflow Control Error Handling Recoverability Restartability Data Consistency같은 부분을 관리하기 쉬워진다.
Mediator가 전체 Workflow를 알고 있기 때문이다.
하지만 그 대가가 있다.
Mediator에 대한 의존성이 커지고 Broker보다 확장성과 성능이 다소 떨어질 수 있다.
또 Mediator 자체가 중요한 역할을 담당하기 때문에 내고장성 측면에서도 불리해질 수 있다.
즉
Broker → 자유롭게 분산 → 흐름 통제 어려움 Mediator → 흐름 통제 쉬움 → 중앙 의존 증가라는 Trade-off가 생긴다.
26. Queue와 Topic은 어떻게 다를까?
이벤트 시스템을 공부하면 Queue와 Topic도 등장한다.
가장 단순하게 구분하면 다음과 같다.
Queue
Producer │ ▼ Queue │ ▼ Consumer특정 메시지를 소비자가 가져가 처리하는 P2P 구조로 생각할 수 있다.
Topic
┌→ Subscriber A Publisher → Topic └→ Subscriber BPublisher가 Topic에 메시지를 발행하면 해당 Topic을 구독한 Subscriber들이 메시지를 받을 수 있다.
그래서
Queue → 특정 소비자를 향한 메시지 전달 Topic → 하나의 이벤트를 여러 구독자가 관심에 따라 수신이라는 관점으로 시작하면 이해하기 쉽다.
27. 이벤트 기반 시스템의 공통적인 장점
첫 번째는 익명성(Anonymity)이다.
메시지 소비자는 반드시 다음을 알 필요가 없다.
누가 메시지를 만들었는가? 어디에 있는가? 언제 만들었는가?이 때문에 컴포넌트 사이의 결합을 줄일 수 있다.
두 번째는 Concurrency다.
생산자와 소비자, 여러 소비자가 독립적으로 동작할 수 있다.
세 번째는 메시지 전달의 신뢰성을 조절할 수 있다는 점이다.
예를 들어
메시지 승인 메시지 우선순위 메시지 만료등을 설정할 수 있다.
28. 이벤트 기반 시스템의 단점
하지만 비동기 시스템에서는 순서를 예측하기 어려워진다.
Event 발생 ↓ Listener A Listener B Listener C이때
A → B → C순서로 반드시 끝난다고 보장하기 어려울 수 있다.
따라서
응답 순서 예측 종료 시점 판단 디버깅 검증이 어려워진다.
메시지 큐 자체의 용량 문제나 메시지 처리에 따른 오버헤드도 고려해야 한다.
즉
"자동으로 반응해서 편하다"
는 장점의 반대편에는
"누가 언제 어떤 순서로 반응할지 추적하기 어렵다"
라는 문제가 존재한다.
29. Blackboard와 Event-Driven은 비슷해 보인다
여기까지 보면 한 가지 의문이 생긴다.
Blackboard도
상태 변경 → KS 실행이고 Event-Driven도
Event 발생 → Subscriber 실행이다.
둘이 굉장히 비슷해 보인다.
실제로 상태 변화가 다른 컴포넌트의 동작을 유발한다는 점에서는 비슷한 모습을 가질 수 있다.
하지만 아키텍처의 중심이 다르다.
Blackboard → 공유된 문제 상태(Data)를 중심으로 협력 → 여러 KS가 부분적인 해법을 적용 → 문제를 점진적으로 해결 Event-Driven → Event를 중심으로 통신 → Publisher와 Subscriber의 결합을 줄임 → 사건 발생에 따라 독립적인 Component가 반응즉 Blackboard에서는
"현재 문제의 상태가 무엇인가?"
가 중요하고,
Event-Driven에서는
"무슨 사건이 발생했는가?"
가 중요하다.
30. 결국 아키텍처에는 무조건 좋은 구조가 없다
마지막으로 가장 중요한 내용이다.
데이터베이스 구조를 다음 세 가지로 생각해보자.
1. Monolithic DB Service A ─┐ Service B ─┼── One DB Service C ─┘2. Domain별 DB Domain A ── DB A Domain B ── DB B3. Service별 DB Service A ── DB A Service B ── DB B Service C ── DB C처음 보면
"서비스별 DB가 가장 분리되어 있으니까 3번이 제일 좋은 것 아닌가?"라는 생각이 들 수 있다.
하지만 아키텍처에서는 그렇게 단순하게 판단할 수 없다.
31. 무엇을 중요하게 생각하느냐에 따라 답이 달라진다
평가해야 하는 품질 속성이 있기 때문이다.
성능 구축 용이성 변경 용이성 내고장성 확장성예를 들어 하나의 DB를 공유하면
구조 단순 관리 편리 데이터 일관성 관리 상대적으로 쉬움이라는 장점이 있을 수 있다.
하지만 DB가 하나이므로 장애가 전체 시스템에 영향을 줄 가능성이 커지고 서비스별 독립 확장에도 제약이 생길 수 있다.
반대로 서비스마다 DB를 분리하면
서비스 독립성 ↑ 변경 독립성 ↑ 확장성 ↑ 장애 격리 ↑같은 장점을 얻을 수 있다.
하지만
운영 복잡성 ↑ 데이터 일관성 관리 난이도 ↑ 분산 트랜잭션 문제 ↑같은 비용을 지불해야 한다.
Domain별 DB는 그 사이에서 하나의 절충안이 될 수 있다.
32. 그래서 "몇 번이 제일 좋아요?"라는 질문에는 답이 없다
이게 이번 내용에서 가장 중요한 결론이라고 생각한다.
1번이 나쁘고 3번이 좋다가 아니다.
만약
"나는 확장성과 서비스 독립성이 굉장히 중요해."라면 서비스별 분리를 적극적으로 고려할 수 있다.
반대로
"시스템이 작고 구축과 운영의 단순성이 훨씬 중요해."라면 굳이 모든 것을 서비스별로 쪼갤 필요가 없다.
또
"어느 정도 독립성은 필요하지만 서비스마다 DB를 운영하는 복잡성까지 감당하고 싶지는 않아."라면 Domain 단위의 분리를 선택할 수도 있다.
결국 아키텍처 설계는
"어떤 구조가 가장 좋은가?"
를 찾는 문제가 아니라
"내 시스템에서 가장 중요한 품질 속성은 무엇이고, 그 속성을 얻기 위해 어떤 비용을 감수할 것인가?"
를 결정하는 문제에 가깝다.
마무리
처음에는 Shared Repository, Blackboard, Event Broker, Event Mediator가 전부 비슷하게 느껴졌다.
하지만 누가 흐름을 주도하는가?를 기준으로 보면 차이가 보인다.
Shared Repository ──────────────────── Client가 주도 Repository는 수동적 Client → RepositoryBlackboard ──────────────────── 문제 상태가 중심 Control이 상태를 보고 KS 선택 Blackboard 상태 ↓ Control ↓ KS ↓ Blackboard 갱신Event Broker ──────────────────── Event가 발생하면 관심 있는 Component가 반응 Publisher ↓ Broker ↙ ↓ ↘ A B CEvent Mediator ──────────────────── Mediator가 전체 Workflow를 관리 Mediator ↙ ↓ ↘ A B C그리고 이 모든 구조에는 장점과 단점이 존재한다.
따라서 소프트웨어 아키텍처를 공부할 때 단순히 구조를 암기하는 것보다
왜 이런 구조가 등장했는가? 누가 제어 흐름을 가지고 있는가? 컴포넌트는 어떻게 통신하는가? 무엇을 얻는 대신 무엇을 포기하는가? 어떤 시스템에 적합한가?를 질문하는 것이 훨씬 중요하다.
결국 아키텍처 스타일은 정답을 고르는 문제가 아니다.
시스템이 중요하게 생각하는 품질 속성에 따라 적절한 Trade-off를 선택하는 문제다.