Skip to content

Commit 58bfb61

Browse files
authored
소프트웨어 아키텍처 The Hard Parts 1주차 - 이근주 (#579)
* 20260107. 1~3장
1 parent 074a1ec commit 58bfb61

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# 트레이드오프 위에서의 아키텍처 선택
2+
3+
## 1 ~ 3장
4+
---
5+
6+
## chapter 1 - '베스트 프랙티스'가 없다면?
7+
8+
> 정답은 없다. 다만 트레이드오프만 있을 뿐이다.
9+
10+
우리는 늘 정답을 찾는다. 나 또한 그렇다.
11+
하지만 인생이 그렇듯, 소프트웨어 설계에도 절대적인 정답은 없다.
12+
존재하는 것은 언제나 **장점과 단점**, 그리고 그 사이에서의 **선택**뿐이다.
13+
14+
아키텍처 설계 역시 마찬가지다.
15+
"어떤 구조가 가장 좋은가?"가 아니라,
16+
**"지금 이 시점의 제약과 상황에서 무엇이 가장 합리적인 선택인가?"** 를 고민하는 문제다.
17+
18+
그래서 이 책은 정답을 주기보다는,
19+
그 선택을 하기 위해 필요한 **사고의 도구들**을 설명한다.
20+
21+
### 이 책에서 반복적으로 등장하는 키워드들
22+
23+
- coupling / cohesion
24+
- component 경계
25+
- sync / async
26+
- orchestration / choreography
27+
- atomicity
28+
- contract
29+
30+
이 단어들은 각각 따로 보면 어려울 수 있지만,
31+
결국은 **"무엇을 함께 묶고, 무엇을 분리할 것인가"** 라는 질문으로 수렴한다.
32+
33+
책에서 다루는 *한빛가이버* 사례를 읽으면서,
34+
나는 지난 5년간 내가 겪어온 MSA 전환 과정과 굉장히 유사하다는 느낌을 받았다.
35+
정답을 찾아 적용하기보다는,
36+
트래픽·조직·장애·운영이라는 현실 속에서
37+
계속해서 **트레이드오프를 선택해 온 과정**에 더 가까웠기 때문이다.
38+
39+
이 챕터는
40+
"베스트 프랙티스를 알려주는 장"이 아니라,
41+
**이후 챕터들을 어떤 관점으로 읽어야 하는지를 정리해 주는 장**이라고 느꼈다.
42+
43+
## chapter 2 - 아키텍처 퀀텀
44+
45+
> MSA는 '유행'이 아니라, 현재 시스템의 제약과 이해를 바탕으로 선택해야 하는 결과다.
46+
47+
나의 경험을 예로 들면, 처음에는 다들 그렇듯이 레이어드 모놀리식으로 시작했다.
48+
그 이후 MSA를 목표로 점진적인 전환을 시도하고 있다.
49+
50+
아직은 DB를 완전히 분리하지 못해, 엄밀한 의미의 MSA를 적용했다고 보기는 어렵다.
51+
다만 향후 DB 분리와 서비스 분리를 가능하게 만들기 위해, 미리 헥사고날 아키텍처와 이벤트 기반 아키텍처를 적용하는 방향으로 설계를 진행 중이다.
52+
(이벤트 기반 아키텍처의 경우, 늘어나는 트래픽 대응이 주된 목적이다.)
53+
54+
또한 비동기 통신을 위한 보상 처리로 Saga 패턴도 함께 적용하고 있다.
55+
56+
이처럼 각자의 상황과 제약에 맞게 아키텍처를 선택하는 것이 중요하다고 생각한다.
57+
MSA가 무조건 좋은 것도 아니고, 모놀리식이 무조건 나쁜 것도 아니다.
58+
59+
## chapter 3 - 아키텍처 모듈성
60+
61+
> 아키텍처 별 성격 비교
62+
63+
유지 보수성, 시험성, 확장성, 가용성/내고장성 관점에서 보면
64+
모놀리식 < 서비스 기반 < MSA 순으로 점수가 매겨지는 흐름이 나온다.
65+
66+
### 정리
67+
68+
아키텍처 모듈성을
69+
유지 보수성, 시험성, 확장성, 가용성/내고장성 관점에서 비교하면
70+
일반적으로 **모놀리식 < 서비스 기반 < MSA** 순으로 평가가 높아진다.
71+
72+
이 결과는 "아키텍처의 우열"이라기보다는,
73+
**결합을 얼마나 잘 분리했는가**에 대한 평가에 가깝다고 느꼈다.
74+
75+
- 모놀리식은 변경·장애·확장의 범위가 하나로 묶여 있어
76+
규모가 커질수록 리스크가 증폭되기 쉽다.
77+
- 서비스 기반 아키텍처는 일정 수준의 분리를 제공하지만,
78+
공통 로직과 동기 호출 체인이 늘어나면 다시 결합도가 높아진다.
79+
- MSA는 서비스별 DB 분리와 비동기 통신을 전제로 할 때,
80+
변경 영향 최소화, 독립 확장, 장애 격리 측면에서 가장 큰 이점을 가진다.
81+
82+
다만 이 점수표는
83+
**MSA를 '제대로 운영할 수 있다는 전제'가 붙은 결과**라는 점이 중요하다.
84+
운영 성숙도, 관측성, 재처리, 계약 관리가 뒷받침되지 않으면
85+
높은 점수는 오히려 높은 복잡도로 돌아올 수 있다.
86+
87+
결국 이 비교는
88+
"어떤 아키텍처가 더 낫다"를 말하기보다는,
89+
**어떤 트레이드오프를 선택할 준비가 되어 있는가**를 묻는 기준이라고 생각한다.
90+
91+
### 논의사항
92+
93+
1. **서비스 기반 아키텍처는 ‘과도기’인가, ‘최종 형태’인가?**
94+
서비스 기반은 MSA로 가기 위한 단계인가?
95+
아니면 **조직·트래픽·운영 역량이 맞으면 충분히 '최종 형태'** 가 될 수 있는가?
96+
97+
2. **MSA의 장점을 얻기 위해 반드시 필요한 전제는 무엇인가?**
98+
DB 분리 없이도 MSA를 했다고 말할 수 있는가?
99+
100+
101+
> 아래 내용은 정리나 결론을 위한 글은 아니다.
102+
> 혹시 비슷한 규모의 시스템을 운영하면서
103+
> 다른 팀은 어떻게 하고 있는지 궁금한 사람이 있다면,
104+
> 참고가 될 수 있을 것 같아 남겨둔 개인적인 경험 기록이다.
105+
106+
### 경험 사례 – 내가 겪은 아키텍처 선택의 흐름
107+
108+
#### 여러 아키텍처를 거치며 느낀 점
109+
110+
**레이어드 모놀리스**는 전통적이고 빠르게 시작하기에 좋았다.
111+
하지만 규모가 커질수록 도메인 경계가 흐려지고,
112+
작은 변경이 예상보다 넓은 영역에 영향을 주는 경험을 자주 하게 됐다.
113+
114+
**모듈러 모놀리스**는 도메인 단위로 모듈을 나누면서
115+
변경 영향 범위를 줄이는 데는 분명 도움이 됐다.
116+
다만 시간이 지나면서
117+
"이건 어느 모듈 책임이지?" 같은 애매한 지점이 생기기 시작했고,
118+
그 순간부터 경계를 지키는 일이 생각보다 어렵다는 걸 체감했다.
119+
120+
**마이크로커널 아키텍처**
121+
확장 축(기능/전략)이 반복적으로 추가될 때는 꽤 매력적이다.
122+
예를 들어 PG 연동처럼
123+
결제/취소/중단(Abort) 흐름이 유사한 기능이 계속 늘어나는 경우에는
124+
플러그인이나 전략 패턴으로 분리할 가치가 있다.
125+
다만 실제로 운영해보니,
126+
생각보다 이렇게 반복적으로 확장되는 영역은 많지 않았고
127+
그 외의 영역에서는 과설계가 되기 쉽다고 느꼈다.
128+
129+
---
130+
131+
#### 결국 선택한 방향: 서비스 기반 + 헥사고날
132+
133+
서비스 기반 아키텍처는
134+
팀을 나누고 서비스를 분리하는 데 분명한 장점이 있다.
135+
하지만 실제로 운영해보면
136+
공통 로직이 생기는 순간부터 선택이 어려워진다.
137+
138+
- 그냥 복붙할지
139+
- 공용 라이브러리로 묶을지
140+
- 아니면 공통 서비스를 하나 더 만들지
141+
142+
이 선택을 매번 고민하게 된다.
143+
144+
특히 서비스 간 동기 호출이 늘어날수록,
145+
장애가 전파되고 배포가 서로 엮이는 경험을 여러 번 했다.
146+
이 지점이 서비스 기반 아키텍처에서
147+
가장 크게 체감한 한계였다.
148+
149+
그럼에도 불구하고,
150+
현재의 조직 규모와 트래픽을 고려하면
151+
서비스 단위로 나누는 선택 자체는 피하기 어렵다고 판단했다.
152+
그래서 내 기준에서의 질문은
153+
**"나눌 것인가?"가 아니라 "어디까지 나눌 것인가?"** 였다.
154+
155+
그 결과 나는
156+
서비스 기반 구조를 유지하되,
157+
헥사고날 아키텍처를 적용해
158+
서비스 내부의 도메인 경계를 최대한 명확히 하려고 했다.
159+
그리고 공통 로직은 정말 불가피한 경우에만
160+
내부 모듈이나 라이브러리로 제한하고,
161+
도메인 로직이 공통으로 새어나가지 않도록 의식적으로 막고 있다.
162+
163+
---
164+
165+
### 그리고 EDA
166+
167+
EDA를 도입한 지점은 **결제 이후**다.
168+
결제 자체는 짧고 확실하게 끝내고,
169+
결제 이후에 따라오는 후속 작업들을
170+
동기 호출로 묶지 않기 위해 Kafka 기반 이벤트로 분리했다.
171+
172+
#### 이벤트 발행 (결제 서비스)
173+
174+
결제가 완료되면 Kafka로 이벤트를 발행한다.
175+
이 이벤트에는 대략 다음 정보가 담긴다.
176+
177+
- paymentSeq (결제 식별자)
178+
- amount (금액)
179+
- item 정보 (상품 / 구성 / 수량 등)
180+
181+
중요한 점은
182+
"무엇을 해라"가 아니라
183+
**"결제가 완료되었다"라는 사실을 발행**한다는 것이다.
184+
185+
#### 팀1 컨슘: Timeline 적재
186+
187+
팀1은 이 이벤트를 컨슘해서
188+
결제 건을 Timeline에 적재한다.
189+
이 작업은 결제의 성공/실패와 운명을 같이할 필요가 없고,
190+
지연되더라도 나중에 따라잡을 수 있기 때문에
191+
비동기 방식이 잘 맞았다.
192+
193+
#### 팀2 컨슘: 적립 계산 및 반영
194+
195+
팀2는 동일 이벤트를 컨슘한 뒤,
196+
자기 정책에 따라 적립 금액을 계산하고 적립을 반영한다.
197+
198+
적립은 정책·이벤트·프로모션에 따라 변수가 많고,
199+
결제 서비스가 직접 알고 있어야 할 이유가 없다고 판단했다.
200+
오히려 결제 플로우에 섞이면
201+
변경 영향 범위만 커진다.
202+
그래서 계산과 반영의 책임을 아예 분리했다.
203+
204+
---
205+
206+
#### EDA를 통해 얻은 효과
207+
208+
- 결제 서비스는 결제 자체에만 집중할 수 있었다
209+
- 동기 호출 체인을 끊으면서 더 빠른 결제 및 더 많은 처리량을 달성할 수 있었다
210+
- 후속 작업은 컨슈머 확장으로 처리량을 맞추고,
211+
피크 트래픽은 큐가 흡수하도록 만들 수 있었다
212+
213+
#### 미래 수정할 점
214+
215+
현재는 **결제 완료 이벤트**를 타 팀에서 직접 컨슘해
216+
내역 적재나 포인트 작업까지 처리하고 있다.
217+
218+
하지만 앞으로는 다음 형태로 바꿀 예정이다.
219+
220+
- 결제 서비스는 **결제 완료(Fact)** 이벤트만 발행한다.
221+
- 해당 토픽을 컨슘하는 서비스가
222+
- **내역 발행 Command**
223+
- **포인트 적립 Command**
224+
를 각각 발행한다.
225+
226+
이렇게 하면 내역은 쌓였는데 포인트 적립이 누락된 경우에도
227+
Admin을 통해 **포인트 적립 Command만 재발행**하는 형태로 보상이 가능해진다.
228+
229+
#### 운영하면서 중요했던 포인트
230+
231+
- 이벤트 중복/재처리는 전제로 두고 멱등하게 설계한다
232+
- 재처리를 항상 가능하게 만든다
233+
- 추적을 위해 requestUuid 같은 식별자를 이벤트에 포함한다
234+
- 사내에 FDC(financial Data Center)가 존재하고, 매일 bulk로 사내 전체 DB를 조회하여 적재한다. 그리고 매일 배치를 돌려서 누락된 적립이 있는지 확인하고, 존재한다면 노티 이메일을 보내준다. 따라서
235+
재처리가 가능하다. 이것이 운영에서 가장 중요한 포인트였다.

0 commit comments

Comments
 (0)