아날로그 리팩터 철도 노선도: 유산 코드를 지하철 시스템처럼 안전하게 개편하는 방법
레거시 리팩터링을 지하철 노선망 계획처럼 다루는 법: DDD, 클린 아키텍처, 시각적 맵을 활용해, 시스템 전체를 무작정 갈아엎지 않고도 깨지기 쉬운 시스템을 안전하게 점진적으로 바꾸는 루트를 설계하는 방법을 소개합니다.
아날로그 리팩터 철도 노선도: 유산 코드를 지하철 시스템처럼 안전하게 개편하는 방법
레거시 시스템은 보통 한 줄의 나쁜 코드 때문에 무너지는 것이 아니다. 사람들이 지도를 잃어버렸기 때문에 무너진다.
오래된 핵심 업무 시스템을 리팩터링하는 일은, 스크립트를 한 번에 갈아쓰는 일이라기보다 운행 중인 지하철 네트워크를 다시 설계하는 일에 가깝다. 전체를 멈출 수도 없고, “그냥 새로 쓰자”라고 할 수도 없으며, 한 번의 잘못된 변경으로도 실제 운영이 탈선할 수 있다.
여기서 아날로그 리팩터 철도 노선도(Analog Refactor Rail Map) 라는 아이디어가 등장한다. 레거시 아키텍처를 지하철 시스템처럼 다루고, 리팩터링을 교통망 계획 작업으로 보는 것이다. 우리의 역할은 점진적 변경을 위한 가장 안전한 경로가 어디인지 보여주는 지도를 그리고 다듬는 일이다.
이 글에서는 다음과 같은 내용을 다룬다:
- 리팩터링을 단순한 코딩 작업이 아닌 조직·운영 과제로 다루는 방법
- 클린 아키텍처(Clean Architecture), SOLID, 도메인 주도 설계(DDD) 로 변경을 구조화하는 방법
- 화이트보드, 포스트잇, 전용 도구를 활용해 도메인을 공동 모델링하는 방법
- 바운디드 컨텍스트(bounded context) 와 컨텍스트 맵(context map) 으로 안전한 경계 정의하기
- Context Mapper 같은 도구로 아키텍처를 형식화하고 검증하는 방법
- 기존 산출물(JSON 스키마, API, Java 클래스 등)에서 모델을 역공학하는 방법
- 아키텍처를 지하철 노선도처럼 시각화해, 팀이 안전하게 탐색할 수 있도록 돕는 방법
1. 레거시 리팩터링은 ‘코딩 연습’이 아니다
리팩터링이 실패하는 프로젝트에는 공통점이 하나 있다. 누군가 이 일을 “기술 부채 청소” 정도로만 보고, 비즈니스 운영으로 보지 않았다는 점이다.
레거시 시스템은 다음과 같은 존재다:
- 보기엔 엉망일지라도 매출을 만들어내는 엔진
- 수년간 축적된 비즈니스 규칙이 담긴 지식 베이스
- 팀, 관행, 정치적 이해관계가 엮여 있는 사회·기술 시스템
이런 시스템을 계획 없이 리팩터링하는 것은, 대체 버스도 없이 도시 지하철 노선의 절반을 갑자기 폐쇄하는 것과 같다.
우리를 현실에 붙들어 놓는 두 가지 원칙이 있다.
-
명확한 비즈니스·기능적 필요가 있을 때만 리팩터링하라.
리팩터링은 항상 어떤 목표를 위해 수행돼야 한다. 새로운 기능을 가능하게 만들거나, 중요한 SLA를 개선하거나, 특정 운영 리스크를 줄이거나, 통합을 쉽게 만드는 등이다. 비즈니스 효과를 말로 설명할 수 없다면 그건 리팩터링이 아니라, 개발자의 취미 활동에 가깝다. -
리팩터링을 조직 변화로 다뤄라.
이 시스템을 누가 쓰는가? 누가 예산을 대는가? 무엇이 망가졌을 때 누가 영향을 받는가? 이들을 초기부터 관여시켜라. 리팩터링을 운영 프로젝트처럼 계획하라: 단계적 롤아웃, 커뮤니케이션 계획, 파일럿, 롤백 전략까지 포함하여.
지하철로 비유하면, 모든 리팩터링에 따른 노선 폐쇄에는 운행 공지와 대체 경로가 필요하다.
2. 클린 아키텍처와 SOLID: 선로를 다시 까는 작업
리팩터링이 조직 차원의 과제라는 사실을 받아들였다고 해서, 코드를 개선하지 못하는 건 아니다. 다만 이제는 분명한 목적을 갖고 개선하는 것이다.
클린 아키텍처(Clean Architecture) 와 SOLID 원칙은 마법의 해법은 아니지만, 다음을 가능하게 해준다.
- 비즈니스 규칙과 기술 구현 세부사항(UI, DB, 프레임워크 등) 을 분리할 수 있는 구조
- 코어를 건드리지 않고도 바깥 레이어를 더 쉽게 교체할 수 있는 계층화
- 점진적인 현대화를 위한 가드레일(안전 난간)
레거시 코드를 건드릴 때는 다음과 같이 시작해보자.
- 먼저 봉제선(seams) 을 찾는다: 어색하거나 지저분한 부분을 어댑터나 파사드로 감쌀 수 있는 지점들.
- 로직을 점차 더 명확한 경계로 이동시킨다: 엔티티, 유스케이스, 인터페이스 등으로 구조화.
- SOLID를 전역에 강요하기보다는 국소적으로 적용한다:
- 단일 책임 원칙(SRP): 파싱 + 비즈니스 규칙 + 영속성까지 한 클래스에 몰려 있다면, 책임별로 쪼갠다.
- 의존성 역전 원칙(DIP): 프레임워크 구현체가 아니라, 도메인에서 정의한 인터페이스에 의존하도록 바꾼다.
이 작업은 곧, 기존 낡은 선로 옆에 새롭고 정돈된 선로를 하나씩 까는 것이다. 그리고 트래픽을 조금씩 그쪽으로 우회시키는 것이다.
3. 도메인 주도 설계(DDD): 선로를 옮기기 전에 먼저 지도를 그려라
DDD의 핵심은 코드가 아니다. 언어와 이해다. 모듈을 재배치하기 전에, 먼저 비즈니스가 세상을 어떻게 바라보는지를 이해해야 한다.
여기서 공동 모델링(collaborative modelling) 이 중요해진다.
- 화이트보드, 포스트잇, 혹은 온라인 보드(Miro, Mural 등)를 사용한다.
- 개발자뿐 아니라 도메인 전문가(업무 담당자) 를 반드시 초대한다.
- 프로세스, 엔티티, 이벤트, 규칙을 함께 스케치한다.
- 진행하면서 의사결정, 특히 의견 충돌과 트레이드오프를 기록해둔다.
목표는 공유된 도메인 모델을 만드는 것이다.
- 핵심 개념이 무엇인가? (Order, Shipment, Policy, Account…)
- 이들이 어떻게 연결되는가? 어떤 팀이 어떤 데이터의 소유자인가?
- 책임은 어디서 시작되고 어디서 끝나는가?
포스트잇 한 장, 화살표 하나가 지하철 노선도의 역과 노선을 그려 넣는 행위와 같다. 우리는 단순히 객체 간 참조를 그리는 것이 아니라, 사람과 데이터가 시스템을 어떻게 ‘통과’하는지를 설계하고 있다.
4. 바운디드 컨텍스트: 한 노선이 끝나고 다른 노선이 시작되는 지점
레거시 시스템이 흔히 겪는 큰 문제 하나는, 모든 것이 모든 것과 대화한다는 점이다. 이는 마치 모든 열차가 구분 없이 같은 선로를 공유하는 것과 같다. 곧 혼란이다.
DDD의 바운디드 컨텍스트(bounded context) 는 이를 해결하는 도구다.
- 바운디드 컨텍스트는 의미가 일관된 경계이다. 그 안에서는 용어와 모델이 일관되게 쓰인다.
- Billing에서 말하는 “Customer”와 CRM에서 말하는 “Customer”는 다를 수 있다.
- 각 바운디드 컨텍스트는 자체 모델, 규칙, 스키마를 가진다.
이것은 안전한 리팩터링에 필수적이다.
- 시스템 전체를 갈아엎지 않고도 한 컨텍스트씩 개선할 수 있다.
- 컨텍스트 간에는 명확한 인터페이스와 통합 패턴을 정의할 수 있다.
이때 컨텍스트 맵(context map) 을 사용해 컨텍스트들이 어떻게 상호작용하는지 표현한다.
- 상류/하류(upstream/downstream) 관계
- 공통으로 공유하는 커널(shared kernel)
- 다른 모델의 침투를 막는 Anti-Corruption Layer(ACL)
아날로그 리팩터 철도 노선도에서, 각 바운디드 컨텍스트는 하나의 지하철 노선이고, 컨텍스트 맵은 노선 간 환승 지점과 교차점을 보여준다. 이 교차점은 리팩터링 리스크가 가장 높은 지점이기도 하다. 따라서 가장 신중해야 하는 부분이다.
5. Context Mapper로 지도를 형식화하기
포스트잇과 화이트보드는 강력하지만, 버전 관리가 어렵다.
Context Mapper 같은 도구를 사용하면, 개념적 지하철 노선도를 다음과 같은 아티팩트로 만들 수 있다.
- Git에 버전 관리할 수 있는 파일
- 컨텍스트 맵, UML 유사 뷰 등의 다이어그램 자동 생성물
- 특정 아키텍처 룰에 대한 검증 결과
- 반자동 변환의 기반 자료
Context Mapper를 사용하면 다음과 같은 작업을 할 수 있다.
- 전용 DSL로 바운디드 컨텍스트와 그 관계를 기술한다.
- 아키텍처 리뷰에 활용할 수 있는 시각적 다이어그램을 생성한다.
- 컨텍스트 분리/병합, 관계 변경, 새로운 ACL 도입 등 리팩터링 패턴을 실험한다.
이는 화이트보드 스케치와 코드 변경 사이의 간극을 메워준다. 지도가 코드베이스의 일부가 되어, 코드와 함께 살아 움직이게 된다.
6. 기존 산출물에서 노선도를 역공학하기
브라운필드(brownfield) 환경에서는, 깨끗한 백지 상태로 시작하는 일이 드물다. 이미 다음과 같은 것들이 존재한다.
- JSON 스키마
- API 명세서
- 데이터베이스 스키마
- Java(혹은 다른 언어) 클래스
- 이벤트 로그
이것들을 무시하기보다는, 도메인 맵을 역공학하는 자료로 활용하라.
- JSON 스키마나 API 엔드포인트를, 소유권과 사용 언어를 기준으로 묶어 후보 바운디드 컨텍스트를 만든다.
- Java 패키지 구조와 의존성을 분석해, 암묵적인 모듈 경계를 찾아낸다.
- Kafka 토픽, 메시지 큐 등 이벤트 흐름을 따라가며, 데이터가 실제로 어떻게 움직이는지 본다.
이벤트는 특히 많은 것을 알려준다.
OrderPlaced,PaymentCaptured,ShipmentDispatched같은 이벤트는 비즈니스 프로세스를 그대로 드러낸다.- 이벤트의 순서와 결합 관계는 상류/하류 관계를 드러낼 수 있다.
이 작업은 곧, 도시가 “그랬으면 좋겠다”고 생각하는 이상적인 노선도가 아니라, 현재 실제로 열차가 어떻게 다니는지를 읽어내는 일이다.
이것이 안전한 변경을 위한 기초 작업이다.
- 새로운 설계를 제안하기 전에, 현재의 운행 경로를 이해한다.
- God Class, 공유 DB 같은 병목 지점을 과밀 환승역으로 인식할 수 있다.
7. 지하철 노선도처럼 생각하기: 안전한 점진적 경로 시각화
궁극적으로 우리가 원하는 것은 다음을 한눈에 보여주는 하나의 일관된 시각화다.
- 도메인과 바운디드 컨텍스트(각각의 노선)
- 통합 지점과 의존 관계(교차역, 환승역, 선로)
- 비즈니스적으로 중요한 플로우(출퇴근 시간 주요 통근 노선)
이 지도를 활용해 다음을 계획할 수 있다.
- 점진적 리팩터 단계:
“먼저 Billing 컨텍스트를 공유 Customer 테이블에서 ACL을 통해 분리하고, 그 다음에Customer.billingInfo를 독자 모델로 옮긴다.” - 리스크 완화 전략:
“이 환승역(공유 DB)은 리스크가 크다. API 계층을 도입하고, 직접 DB 접근을 단계적으로 제거하자.” - 커뮤니케이션:
“프로덕트팀, 지금 당장 전체 리라이트가 불가능한 이유가 이 지도에 나와 있습니다. 대신 새 요금제를 지원하기 위한 안전한 경로가 세 가지 있습니다.”
아날로그 리팩터 철도 노선도는 단순한 다이어그램이 아니다. 엔지니어링, 프로덕트, 운영이 함께 보는 커뮤니케이션 도구다.
결론: 선로를 뜯어내지 말고, 네트워크를 다시 설계하라
레거시 코드는 적이 아니다. 지도 없는 레거시 코드가 문제일 뿐이다.
리팩터링을 단순한 코딩 작업이 아닌 도시 계획 작업으로 바라보면, 다음을 이룰 수 있다.
- 기술적 변경을 실제 비즈니스 목표와 정렬시키기
- 코드 레벨에서 클린 아키텍처와 SOLID를 적용해 구조를 개선하기
- DDD로 의미 있는 경계를 그리고 결합도를 낮추기
- 도메인을 공동 모델링해, 모두가 같은 멘탈 맵을 공유하도록 만들기
- Context Mapper 같은 도구로 진화하는 설계를 형식화하고 검증하기
- 기존 산출물에서 역공학해, 실제로 존재하는 시스템을 정확히 이해하기
- 이 모든 것을 지하철 노선도처럼 시각화해, 안전한 점진적 경로를 선택할 수 있게 하기
그린필드 리라이트가 없어도 시스템을 현대화할 수 있다. 필요한 것은 새로운 지도, 명확한 목적지, 그리고 열차가 계속 운행되는 동안에도 네트워크를 한 노선, 한 역, 한 환승 지점씩 바꿔 나가려는 규율이다.