Architecture - 전략적 모놀리스: 잘못된 시스템의 리팩토링


MSA 가 모든 문제의 해답은 아니다.
여기서는 ‘전략적으로 구축된 모놀리스’가 여전히 탁월한 선택일 수 있음을 이야기하며, 이를 어떻게 실현할 수 있을지 살펴본다.

한동안 소프트웨어 업계는 MSA 열풍에 휩싸였다.
하지만 모든 시스템이 복잡한 분산 시스템 구조를 필요로 하는 것은 아니다.
잘 설계된 모놀리스는 단순성과 유지 관리의 장점 덕분에 여전히 유효한 선택이며, 특정 상황에서는 오히려 더 뛰어난 결과를 낳는다.

이 포스트에서는 아래와 같은 주제를 다룬다.

  • 모놀리스를 선택해야 하는 이유와 성공 전략
  • 모놀리스에서 출발해도 충부한 시스템이 많다는 점
  • 레거시를 운영하면서 아키텍처를 리팩토링하는 방법
  • 소프트웨어가 진흙탕이 되는 걸 피하는 법


모놀리스를 다룰 때는 아래 3가지 주요 관심사를 구분해서 접근해야 한다.

  • 처음부터 모놀리스를 올바르게 설계하고 유지하기
    • 모듈성과 경계를 명확히 하는 것이 핵심
    • 기술 부채를 사전에 방지하고, 유지보수성을 최우선으로 고려
    • 배포 및 테스트 전략을 단순하게 유지하여 개발 속도 확보
  • 이미 잘못된 모놀리스를 바로잡기
    • 스파게티 코드, 얽힌 의존성, 무분별한 서비스 확장을 구조적으로 분해
    • 비즈니스 역량 기반의 리팩토링 전략 필요
    • 목표는 ‘MSA 전환’이 아니라 ‘건강한 모놀리스 회복’
  • 모놀리스에서 MSA 로의 전환 고려
    • 반드시 필요한 경우에만 선택
    • 조직의 구모, 도메인의 복잡성, 독립적 배포 필요성 등 신중한 검토 필요
    • 전환 그 자체가 목적이 되어서는 안됨

세 번째 항목은 ‘가능성’이지 ‘필연’이 아니다.
오히려 불필요한 분산화는 복잡성만 높이고 생산성을 떨어뜨릴 수 있다.

모놀리스에서 MSA 로의 전환 고려 에 대한 내용은 추후 다룰 예정입니다. (p. 330)

많은 프로젝트는 시간이 지날수록 진흙 구덩이처럼 빠져나오기 힘든 구조적 혼란을 겪는다.
그 이유는 다음과 같다.

  • 초기 설계의 부실
  • 비즈니스 변화에 따른 무계획한 확장
  • 비일관적 아키텍처 결정
  • 의도없는 기술 도입과 남용


이러한 함정을 피하려면 아래와 같은 노력이 필요하다.

  • 비즈니스 역량 중심의 모듈화
  • 명확한 경계와 의존성 관리
  • 현실적인 기술 선택과 적절한 단순성 유지


전략적 모놀리스는 단순히 ‘MSA 가 필요없는 경우’에 대한 대안이 아니다.
그 자체로도 강력하고 유지보수 가능하며, 변화에 유연하게 대응할 수 있는 아키텍처이다.
이제 이를 어떻게 설계하고 관리할 수 있을지 구체적인 방법에 대해 알아본다.

Architecture - 디지털 트랜스포메이션Architecture - 전략적 학습을 위한 도구 을 먼저 보면 도움이 됩니다.

브로슈어웨어(Brochureware)

회사의 광고용 브로슈어처럼 겉모습만 그럴듯하고 실제 기능은 거의 없거나 미완성인 웹사이트

개발 초기 단계에 투자자나 고객 유치용으로 임시 제작됨 기능보다는 UI 에 집ㅂ중되어 있음 실제로는 작동하지 않지만 ‘우리에게 이런 제품이 있다’라는 인상을 주기 위해 만들어짐

예) AI 솔루션을 만들었다고 주장하지만, 실제로는 사람이 수동으로 만들어서 보여주는 경우, 버튼과 화면만 존재하고 백엔드는 전혀 없는 앱 데모

부정적, 냉소적 뉘앙스를 담고 있으며, 제품이 허술하거나 과장되었다는 비판을 할 때 사용됨

데이터 스크러빙(Data Scrubbing)

오류가 있는 데이터 또는 불완전한 데이터를 자동으로 탐지하고 수정/제거하는 과정

예) 중복된 정보 병합


목차


1. 시작부터 바로

건강한 모놀리스를 구축하려면 처음부터 올바른 방향으로 설정해야 한다.
아키텍처 결정, 경계 구분, DB 접근 방식까지, 이 모든 선택이 이후의 유지보수성과 확장성에 영향을 미친다.


1.1. 아키텍처 결정

아키텍처를 정의하는 순간들을 프로젝트 초기에만 존재하지 않는다.
소프트웨어가 진화하면서 수많은 결정의 순간들이 등장하고, 이를 의식적이고 전략적으로 기록하는 것이 중요하다.

ADR(Architecture Decision Records) 은 아키텍처 결정 과정을 구조화된 형식으로 남기는 도구이다.

  • 어떤 선택을 했는지
  • 왜 그렇게 결정했는지
  • 어떤 대안이 있었는지
  • 어떤 영향을 미치는지

이러한 기록은 팀의 의사 결정 과정에 일관성과 투명성을 제공하고, 나중에 아키텍처를 되짚을 수 있는 강력한 회고 도구가 된다.

어떤 아키텍처 결정이 필요한지에 대해서는 ADR 도구를 활용하면 좋다.

룩앤필(Look and Feel)

소프트웨어의 겉모습과 사용자 경험(UX) 를 포괄적으로 묘사하는 용어
즉 UI(User Interface) 와 UX(User Experience) 를 함께 설명할 때 쓰는 말

UI: 시각적 구성 요소, 배치, 스타일 등
UX: 흐름, 반응 속도, 직관적 조작 등

웹/앱의 겉모양과 사용자 체감을 모두 아우르는 개념으로, 시각적인 것 뿐 아니라 느낌,흐름,조작의 직관성까지 포함

바운디드 컨텍스트는 자체 DB 를 소유해야 한다.

하나의 DB 를 여러 바운디드 컨텍스트가 함께 사용하거나, 다른 컨텍스트에서 직접 DB 를 조회해서는 안된다.
각 컨텍스트는 자신만의 DB 또는 스키마를 소유해야 하고, 다른 컨텍스트와 통신할 때는 공개 API 를 통해 접근해야 한다.

단일 DB 기반의 다중 컨텍스트 구조는 확장성과 성능(많은 연결과 동시 작업으로 인한), 양쪽 모두에서 곧 한계에 부딪히게 된다.


2. 잘못된 것에서 올바른 것으로

빅볼 오브 머드를 MSA 로 바꾸는 것보다, 건강한 모놀리스로 바로잡는 것이 먼저다.

모놀리스를 잘못 설계하면 빅볼 오브 머드라는 혼돈의 상태에 빠지게 된다.
하지만 모놀리스 → MSA 가 유일한 답은 아니다.
많은 경우 모놀리스 → 전략적 모놀리스로의 리팩토링이 더 현실적이고 효과적인 해결책이 될 수 있다.


모놀리스에서 모놀리스로, 올바르게 리팩토링하는 법

대부분의 레거시 시스템은 라이브 상태에서 유지되며 비즈니스 기능이 계속 추가된다.
따라서 “모든 걸 멈추고 리팩토링에 집중하자”는 방식은 실현 가능성이 낮다.

현실적인 리팩토링 접근

  • 전체 리빌드보다는 점진적인 구조화
  • 비즈니스 흐름을 이해하고 이에 맞춘 코드 경계부터 재정비
  • 기존 기능 유지와 새로운 구조 설계를 동시에 고려한 이중 관점 리팩토링

즉, 리팩토링은 시스템을 켜둔 채 엔진을 교체하는 일과 같다.


리팩토링이 어려운 이유: 조직 내 역학 관계

역학 관계란 조직이나 팀 내에서 권력, 감정, 이해관계가 얽혀 쉽게 결정하고 실행할 수 없는 복잡한 상황을 의미한다.

역학 관계

서로 영향을 주고받는 관계로 주로 권력, 이해 관계, 감정적 긴장이 얽힌 관계를 표현할 때 사용함

예) “그 둘 사이엔 복잡한 역학 관계가 얽혀 있어서 쉽게 끊을 수 없어.”, “조직 내의 역학 관계를 잘 파악해야 일을 매끄럽게 할 수 있다.”

이 때 ‘역학’은 물리학의 ‘힘의 관계’에서 유래해 사람들 간의 ‘힘의 균형’이나 ‘긴장 구조’를 비유적으로 표현하는 것임

시스템이 망가져있는 것을 모두가 알지만 누가, 언제, 어떻게 고쳐야 할 지 말하기 어렵다.
기술적 문제 뿐 아니라 조직 문화와 감정적 저항이 방해 요소가 되기도 한다.


소스코드가 혼돈스러워지는 일반적인 패턴들

건강하지 않은 모놀리스는 아래와 같은 특성을 보인다.

  • 기술 중심 설계
    • 비즈니스가 아닌 기술적 관점으로 구성됨
  • 비아키텍처 구조
    • 일관성없이 우연히 만들어진 구조
  • 모듈화 실패
    • 비즈니스 기준의 경계가 없거나 약함
  • 테스트 없음
    • 단위 테스트 부재로 회귀 버그가 많음
  • 빈혈 모델
    • 도메인 객체에 로직이 없고 CRUD 에만 집중
  • 로직 손실
    • 비즈니스 로직이 UI 나 컨트롤러에 산재
  • 깊은 커플링
    • 컴포넌트 간 상호 의존성이 높음
  • 양방향 의존
    • 모듈 간 서로 호출하는 순환 참조 구조

회귀 버그(Regression bug)

기존에 정상 작동하던 기능이 새로운 콛 변경 이후 다시 고장나는 현상


helper 없이 돌아가는 설계가 진짜 비즈니스 중심 구조

많은 레거시 코드에서는 helper, util, common 과 같은 모듈이 비즈니스 로직을 대체하곤 한다.
하지만 제대로 설계된 도메인 모델에서는 이런 구성요소가 거의 필요없다.

도메인 모델이 자체적으로 책임을 갖고 행동하는 구조를 만들면 helper 는 자연스럽게 사라진다.


따라서 리팩토링 시엔 아래 전략을 항상 고려하고 있어야 한다.

  • 비즈니스 중심 경계 식별
    • 기능 단위가 아닌 업무 흐름 중심으로 코드 재정렬
  • 점진적 구조화
    • 기존 콛를 유지하며 새 구조를 도입하고 이관
  • 리팩토링 로그 남기기
    • ADR 을 사용해 구조 변경 이유와 맥락 기록
  • 문화와 심리 고려
    • 기술적 설득 이전에 조직적 공감과 지지 확보

2.1. 실전 리팩토링 전략

리팩토링은 별도의 프로젝트가 아니라, 매일의 코드 변경 속에 녹아들어야 한다.

혼돈의 모놀리스를 구조화된 아키텍처로 탈바꿈하는 가장 현실적인 방법은 작은 변경의 순간을 리팩토링의 기회로 삼는 것이다.
이는 실제로 실행 가능한 점진적 개선의 실전 가이드이다.


1단계: 패치가 곧 기회다.

패치가 버그를 수정할 때, 단순히 수정만 하지 말자.
코드에 테스트를 추가하고, 리팩토링의 실마리를 찾아 작은 단위로 구조를 개선할 수 있다.

1: 테스트 먼저

  • 패치가 필요한 코드에 테스트 케이스를 추가
  • 버그를 유도하는 조건을 테스트로 명시하여 실패 확인
  • 버그를 수정하고 테스트를 통과시킨 후 커밋

2: 코드 정리와 모듈화

  • 수정한 코드 근처에서 모듈화할 수 있는 비즈니스 로직을 식별
  • UI, 컨트롤러, 서비스 계층에 흩어진 로직을 도메인 모델 내부로 이동
  • 리팩토링한 코드를 테스트 후 커밋

2단계: 변경된 파일 주변을 리팩토링 한다.

수정이 발생한 파일의 주변부도 주목한다.
기존에 개선하고 싶었지만 건드릴 수 없었던 코드들을 이번 기회를 통해 함께 리팩토링할 수 있다.

  • 이번에 변경되었지만 이전 모듈에 얽혀 있는 코드 분리
  • 서비스 계층에 남은 비즈니스 로직을 도메인 모델로 이전
  • 모든 변경 후 테스트가 통과되면 커밋

3단계: 서비스 계층의 매개변수를 밸류 타입으로 변경한다.

서비스 계층이 도메인 객체에 값을 설정할 때 단순 매개변수들로 넘긴다면 이제는 그 값들을 의미있는 Value Object 로 리팩토링한다.

// Before
user.changePassword(password, passwordConfirm)

// After
user.changePassword(PasswordChangeRequest(password, passwordConfirm))
  • 단순 타입에서 의미있는 타입으로 변환
  • 검증, 포맷, 불변성을 Value Object 내부로 위임
  • 도메인 언어의 명확성 강화

2.2. 커플링 끊기

강한 커플링은 시스템 진화를 방해하는 주범이다.

레거시 모놀리스에서 가장 흔한 문제 중 하나는 컨텍스트 간의 강한 커플링이다.
컨텍스트 모듈 간에 비즈니스 로직, 객체 참조, 트랜잭션이 얽혀 있다면 어떤 리팩토링도 근본적인 개선이 되기 어렵다.
여기서는 전략적으로 커플링을 분리하는 2단계 방식코드 재사용에 대한 오해와 진실을 함께 다룬다.

컨텍스트 모듈 간에는 여전히 커플링이 존재하므로 당분간은 소스코드 리비전 제어를 위해 모노 리포지토리를 사용하는 것이 좋다.


1단계: 컨텍스트 간 커플링 제거

가장 먼저 해결할 커플링은 다른 바운디드 컨텍스트 간의 직접적 결합이다.

  • 컨텍스트 간 트랜잭션 일관성 → 궁극적 일관성으로 전환
  • 객체 직접 참조 → ID 기반 참조로 전환
  • FK 제약 → DB 수준에서 제거
  • 구성 요소 간 호출 → API, 메시징으로 분리

서로 다른 컨텍스트 간에 동일한 ‘정책’ 모델이 있어도 이는 DRY(Don’t Repeat Yourself) 원칙 위반이 아니다.
각 컨텍스트는 자체 목적에 맞는 모델을 가져야 한다.


2단계: 컨텍스트 내부 커플링 제거

한 컨텍스트 안에도 복잡하게 얽힌 컴포넌트는 존재한다.
이 경우 우선순위를 정해 트랜잭션 경계를 분리하며 리팩토링한다.

<우선순위 분리 전략>

  • 엔티티 간 강한 결합을 애그리거트 규칙(엔티티 트랜잭션 경계)으로 분리
  • 트랜잭션 일관성이 필요하지 않은 컴포넌트 → 분리 대상
  • 공유 로직을 도메인 서비스로 이전하거나 제거
// 나쁜 예시: 여러 엔티티를 하나의 트랜잭션으로 처리
order.place(payment, delivery)

// 좋은 예시: 각 애그리거트가 독립된 책임을 가지며 순차 처리
payment.approve()
delivery.schedule()

코드 재사용보다 결합 회피가 우선이다.

많은 개발자가 재사용 가능한 코드를 만들기 위해 helper, util 클래스를 만든다.
그러나 재사용 중심의 설계는 오히려 코드 복잡성과 커플링을 증가시킬 수 있다.

미래의 사용을 예측해서 코드를 일반화하려는 시도는 대부분 실패한다. 실제로는 단순한 복제가 더 나은 선택일 수 있다.

유사 코드 복제는 코드 컨텍스트화로 간주되어야 한다.
복제를 허용하면 모듈 간 불필요한 연결을 줄이고, 시스템을 더 명확하게 만든다.


모든 helper, util 구성 요소는 레거시 시스템에서 제거할 수 있다.
아래 기준을 적용하여 불필요한 유틸은 제거하고, 필요한 경우 위치를 명확히 하자.

  • 도메인 로직이면
    • 도메인 모델(엔티티, 밸류 타입)로 이동
  • 여러 도메인에서 공통이면
    • 도메인 서비스로 이동
  • 진짜 유틸성 도구이면
    • 서비스 계층 또는 인프라 계층에서 한정 사용

참고 사이트 & 함께 보면 좋은 사이트

본 포스트는 반 버논, 토마스 야스쿨라 저자의 전략적 모놀리스와 마이크로서비스를 기반으로 스터디하며 정리한 내용들입니다.






© 2020.08. by assu10

Powered by assu10