diff --git a/2026/Fundamentals_of_Software_Architecture_2nd_Edition/geunju-lee/Chapter_1_to_3.md b/2026/Fundamentals_of_Software_Architecture_2nd_Edition/geunju-lee/Chapter_1_to_3.md new file mode 100644 index 00000000..ae1fd94a --- /dev/null +++ b/2026/Fundamentals_of_Software_Architecture_2nd_Edition/geunju-lee/Chapter_1_to_3.md @@ -0,0 +1,235 @@ +# 트레이드오프 위에서의 아키텍처 선택 + +## 1 ~ 3장 +--- + +## chapter 1 - '베스트 프랙티스'가 없다면? + +> 정답은 없다. 다만 트레이드오프만 있을 뿐이다. + +우리는 늘 정답을 찾는다. 나 또한 그렇다. +하지만 인생이 그렇듯, 소프트웨어 설계에도 절대적인 정답은 없다. +존재하는 것은 언제나 **장점과 단점**, 그리고 그 사이에서의 **선택**뿐이다. + +아키텍처 설계 역시 마찬가지다. +"어떤 구조가 가장 좋은가?"가 아니라, +**"지금 이 시점의 제약과 상황에서 무엇이 가장 합리적인 선택인가?"** 를 고민하는 문제다. + +그래서 이 책은 정답을 주기보다는, +그 선택을 하기 위해 필요한 **사고의 도구들**을 설명한다. + +### 이 책에서 반복적으로 등장하는 키워드들 + +- coupling / cohesion +- component 경계 +- sync / async +- orchestration / choreography +- atomicity +- contract + +이 단어들은 각각 따로 보면 어려울 수 있지만, +결국은 **"무엇을 함께 묶고, 무엇을 분리할 것인가"** 라는 질문으로 수렴한다. + +책에서 다루는 *한빛가이버* 사례를 읽으면서, +나는 지난 5년간 내가 겪어온 MSA 전환 과정과 굉장히 유사하다는 느낌을 받았다. +정답을 찾아 적용하기보다는, +트래픽·조직·장애·운영이라는 현실 속에서 +계속해서 **트레이드오프를 선택해 온 과정**에 더 가까웠기 때문이다. + +이 챕터는 +"베스트 프랙티스를 알려주는 장"이 아니라, +**이후 챕터들을 어떤 관점으로 읽어야 하는지를 정리해 주는 장**이라고 느꼈다. + +## chapter 2 - 아키텍처 퀀텀 + +> MSA는 '유행'이 아니라, 현재 시스템의 제약과 이해를 바탕으로 선택해야 하는 결과다. + +나의 경험을 예로 들면, 처음에는 다들 그렇듯이 레이어드 모놀리식으로 시작했다. +그 이후 MSA를 목표로 점진적인 전환을 시도하고 있다. + +아직은 DB를 완전히 분리하지 못해, 엄밀한 의미의 MSA를 적용했다고 보기는 어렵다. +다만 향후 DB 분리와 서비스 분리를 가능하게 만들기 위해, 미리 헥사고날 아키텍처와 이벤트 기반 아키텍처를 적용하는 방향으로 설계를 진행 중이다. +(이벤트 기반 아키텍처의 경우, 늘어나는 트래픽 대응이 주된 목적이다.) + +또한 비동기 통신을 위한 보상 처리로 Saga 패턴도 함께 적용하고 있다. + +이처럼 각자의 상황과 제약에 맞게 아키텍처를 선택하는 것이 중요하다고 생각한다. +MSA가 무조건 좋은 것도 아니고, 모놀리식이 무조건 나쁜 것도 아니다. + +## chapter 3 - 아키텍처 모듈성 + +> 아키텍처 별 성격 비교 + +유지 보수성, 시험성, 확장성, 가용성/내고장성 관점에서 보면 +모놀리식 < 서비스 기반 < MSA 순으로 점수가 매겨지는 흐름이 나온다. + +### 정리 + +아키텍처 모듈성을 +유지 보수성, 시험성, 확장성, 가용성/내고장성 관점에서 비교하면 +일반적으로 **모놀리식 < 서비스 기반 < MSA** 순으로 평가가 높아진다. + +이 결과는 "아키텍처의 우열"이라기보다는, +**결합을 얼마나 잘 분리했는가**에 대한 평가에 가깝다고 느꼈다. + +- 모놀리식은 변경·장애·확장의 범위가 하나로 묶여 있어 + 규모가 커질수록 리스크가 증폭되기 쉽다. +- 서비스 기반 아키텍처는 일정 수준의 분리를 제공하지만, + 공통 로직과 동기 호출 체인이 늘어나면 다시 결합도가 높아진다. +- MSA는 서비스별 DB 분리와 비동기 통신을 전제로 할 때, + 변경 영향 최소화, 독립 확장, 장애 격리 측면에서 가장 큰 이점을 가진다. + +다만 이 점수표는 +**MSA를 '제대로 운영할 수 있다는 전제'가 붙은 결과**라는 점이 중요하다. +운영 성숙도, 관측성, 재처리, 계약 관리가 뒷받침되지 않으면 +높은 점수는 오히려 높은 복잡도로 돌아올 수 있다. + +결국 이 비교는 +"어떤 아키텍처가 더 낫다"를 말하기보다는, +**어떤 트레이드오프를 선택할 준비가 되어 있는가**를 묻는 기준이라고 생각한다. + +### 논의사항 + +1. **서비스 기반 아키텍처는 ‘과도기’인가, ‘최종 형태’인가?** + 서비스 기반은 MSA로 가기 위한 단계인가? + 아니면 **조직·트래픽·운영 역량이 맞으면 충분히 '최종 형태'** 가 될 수 있는가? + +2. **MSA의 장점을 얻기 위해 반드시 필요한 전제는 무엇인가?** + DB 분리 없이도 MSA를 했다고 말할 수 있는가? + + +> 아래 내용은 정리나 결론을 위한 글은 아니다. +> 혹시 비슷한 규모의 시스템을 운영하면서 +> 다른 팀은 어떻게 하고 있는지 궁금한 사람이 있다면, +> 참고가 될 수 있을 것 같아 남겨둔 개인적인 경험 기록이다. + +### 경험 사례 – 내가 겪은 아키텍처 선택의 흐름 + +#### 여러 아키텍처를 거치며 느낀 점 + +**레이어드 모놀리스**는 전통적이고 빠르게 시작하기에 좋았다. +하지만 규모가 커질수록 도메인 경계가 흐려지고, +작은 변경이 예상보다 넓은 영역에 영향을 주는 경험을 자주 하게 됐다. + +**모듈러 모놀리스**는 도메인 단위로 모듈을 나누면서 +변경 영향 범위를 줄이는 데는 분명 도움이 됐다. +다만 시간이 지나면서 +"이건 어느 모듈 책임이지?" 같은 애매한 지점이 생기기 시작했고, +그 순간부터 경계를 지키는 일이 생각보다 어렵다는 걸 체감했다. + +**마이크로커널 아키텍처**는 +확장 축(기능/전략)이 반복적으로 추가될 때는 꽤 매력적이다. +예를 들어 PG 연동처럼 +결제/취소/중단(Abort) 흐름이 유사한 기능이 계속 늘어나는 경우에는 +플러그인이나 전략 패턴으로 분리할 가치가 있다. +다만 실제로 운영해보니, +생각보다 이렇게 반복적으로 확장되는 영역은 많지 않았고 +그 외의 영역에서는 과설계가 되기 쉽다고 느꼈다. + +--- + +#### 결국 선택한 방향: 서비스 기반 + 헥사고날 + +서비스 기반 아키텍처는 +팀을 나누고 서비스를 분리하는 데 분명한 장점이 있다. +하지만 실제로 운영해보면 +공통 로직이 생기는 순간부터 선택이 어려워진다. + +- 그냥 복붙할지 +- 공용 라이브러리로 묶을지 +- 아니면 공통 서비스를 하나 더 만들지 + +이 선택을 매번 고민하게 된다. + +특히 서비스 간 동기 호출이 늘어날수록, +장애가 전파되고 배포가 서로 엮이는 경험을 여러 번 했다. +이 지점이 서비스 기반 아키텍처에서 +가장 크게 체감한 한계였다. + +그럼에도 불구하고, +현재의 조직 규모와 트래픽을 고려하면 +서비스 단위로 나누는 선택 자체는 피하기 어렵다고 판단했다. +그래서 내 기준에서의 질문은 +**"나눌 것인가?"가 아니라 "어디까지 나눌 것인가?"** 였다. + +그 결과 나는 +서비스 기반 구조를 유지하되, +헥사고날 아키텍처를 적용해 +서비스 내부의 도메인 경계를 최대한 명확히 하려고 했다. +그리고 공통 로직은 정말 불가피한 경우에만 +내부 모듈이나 라이브러리로 제한하고, +도메인 로직이 공통으로 새어나가지 않도록 의식적으로 막고 있다. + +--- + +### 그리고 EDA + +EDA를 도입한 지점은 **결제 이후**다. +결제 자체는 짧고 확실하게 끝내고, +결제 이후에 따라오는 후속 작업들을 +동기 호출로 묶지 않기 위해 Kafka 기반 이벤트로 분리했다. + +#### 이벤트 발행 (결제 서비스) + +결제가 완료되면 Kafka로 이벤트를 발행한다. +이 이벤트에는 대략 다음 정보가 담긴다. + +- paymentSeq (결제 식별자) +- amount (금액) +- item 정보 (상품 / 구성 / 수량 등) + +중요한 점은 +"무엇을 해라"가 아니라 +**"결제가 완료되었다"라는 사실을 발행**한다는 것이다. + +#### 팀1 컨슘: Timeline 적재 + +팀1은 이 이벤트를 컨슘해서 +결제 건을 Timeline에 적재한다. +이 작업은 결제의 성공/실패와 운명을 같이할 필요가 없고, +지연되더라도 나중에 따라잡을 수 있기 때문에 +비동기 방식이 잘 맞았다. + +#### 팀2 컨슘: 적립 계산 및 반영 + +팀2는 동일 이벤트를 컨슘한 뒤, +자기 정책에 따라 적립 금액을 계산하고 적립을 반영한다. + +적립은 정책·이벤트·프로모션에 따라 변수가 많고, +결제 서비스가 직접 알고 있어야 할 이유가 없다고 판단했다. +오히려 결제 플로우에 섞이면 +변경 영향 범위만 커진다. +그래서 계산과 반영의 책임을 아예 분리했다. + +--- + +#### EDA를 통해 얻은 효과 + +- 결제 서비스는 결제 자체에만 집중할 수 있었다 +- 동기 호출 체인을 끊으면서 더 빠른 결제 및 더 많은 처리량을 달성할 수 있었다 +- 후속 작업은 컨슈머 확장으로 처리량을 맞추고, + 피크 트래픽은 큐가 흡수하도록 만들 수 있었다 + +#### 미래 수정할 점 + +현재는 **결제 완료 이벤트**를 타 팀에서 직접 컨슘해 +내역 적재나 포인트 작업까지 처리하고 있다. + +하지만 앞으로는 다음 형태로 바꿀 예정이다. + +- 결제 서비스는 **결제 완료(Fact)** 이벤트만 발행한다. +- 해당 토픽을 컨슘하는 서비스가 + - **내역 발행 Command** + - **포인트 적립 Command** + 를 각각 발행한다. + +이렇게 하면 내역은 쌓였는데 포인트 적립이 누락된 경우에도 +Admin을 통해 **포인트 적립 Command만 재발행**하는 형태로 보상이 가능해진다. + +#### 운영하면서 중요했던 포인트 + +- 이벤트 중복/재처리는 전제로 두고 멱등하게 설계한다 +- 재처리를 항상 가능하게 만든다 +- 추적을 위해 requestUuid 같은 식별자를 이벤트에 포함한다 +- 사내에 FDC(financial Data Center)가 존재하고, 매일 bulk로 사내 전체 DB를 조회하여 적재한다. 그리고 매일 배치를 돌려서 누락된 적립이 있는지 확인하고, 존재한다면 노티 이메일을 보내준다. 따라서 + 재처리가 가능하다. 이것이 운영에서 가장 중요한 포인트였다.