스트랭글러 피그와 AI 에이전트: 레거시 현대화를 안전하게 자동화할 수 있을까?
요약
AI 에이전트는 레거시 코드의 읽기 및 초기 구현을 가속화할 수 있지만, 시스템 전반의 복잡한 상호작용이나 '정확성' 검증에는 한계가 있습니다. 본 글은 이러한 위험성을 지적하며, 점진적인 마이그레이션 패턴인 스트랭글러 피그(Strangler Fig pattern)를 활용하여 안전하게 레거시 시스템을 현대화하는 방법을 제시합니다.
핵심 포인트
- AI 에이전트는 코드 생성에 능숙하나, 배치 작업 등 기존의 복잡한 규칙을 놓칠 수 있다.
- 레거시 현대화는 한 번에 모든 것을 바꾸는 '빅뱅 리라이트'보다 점진적이어야 한다.
- 마이그레이션 성공의 핵심은 모델의 자신감이 아닌 객관적인 검증(verification) 과정이다.
- 파사드(Facade)를 구축하여 요청을 오래된 코드와 새로운 서비스로 분기하는 것이 안전한 방법이다.
티켓에 '배송 견적을 모놀리스(monolith)에서 분리하라'고 적혀 있다고 상상해 보세요. 당신이 15년 된 코드베이스를 AI 에이전트에게 지목합니다. 4분 후, 이 에이전트는 깔끔하게 정리된 요약본을 건네줍니다: 엔드포인트 하나, 계산기 클래스 하나, 데이터베이스 테이블 두 개, 쓰기 작업 없음. 마치 시니어 엔지니어가 작성한 것처럼 보입니다.
하지만 무언가가 빠져 있습니다. 야간 배치(batch) 작업이 같은 계산기를 에이전트가 만든 엔드포인트를 거치지 않고 직접 호출합니다. 만약 이 엔드포인트를 새 서비스로 라우팅한다면, 그 배치 작업은 여전히 이전 규칙으로 실행됩니다. 아무도 알아차리지 못하다가 숫자가 맞지 않기 시작할 때까지입니다.
이것이 오늘날 레거시 현대화에 있어 AI의 솔직한 모습입니다. 에이전트는 몇 주가 걸리던 무거운 읽기 작업에는 매우 능숙합니다. 하지만 그들은 '맞다'는 것과는 전혀 상관없는 방식으로 자신감 넘칩니다. **스트랭글러 피그 패턴(Strangler Fig pattern)**은 이 첫 번째 속성을 활용하는 방법을 제공하면서도, 두 번째 속성에 노출되지 않도록 합니다. 단지 하나의 규칙을 중심으로 마이그레이션을 구축한다면 말입니다:
AI 에이전트는 레거시 현대화를 가속화할 수 있습니다. 하지만 프로덕션으로 가는 관문은 모델의 자신감이 아니라 검증(verification)이어야 합니다.
본 게시물에서는 다음 내용을 다룹니다:
- 왜 '빅뱅 리라이트(big-bang rewrite)'가 실패하는지, 그리고 스트랭글러 피그 패턴이 무엇을 바꾸는지.
- 입력, 출력, 권한을 가진 전문 에이전트들의 파이프라인.
- 작업 예시 (가상의 시나리오): 하나의 기능을 추출하고, 파사드(facade)를 구축하며, 트래픽을 전환하고, 오래된 경로를 폐기하는 과정과 마이그레이션을 수락하거나 거부할 객관적인 기준들.
- 절대 완전히 자율적일 수 없는 것들과 에이전트가 얼마나 도움이 되고 있는지 측정하는 방법.
리라이트 함정 (The rewrite trap)
모든 레거시 시스템은 결국 같은 대화를 촉발합니다: '다시 작성(rewrite)하는 것이 더 빠를 것이다.' 하지만 거의 그렇지 않습니다. Martin Fowler의 Strangler Fig Application에서는 그 이유들을 나열하고 있습니다: 대체 작업에는 오랜 시간이 걸리고, 비즈니스는 기다리는 동안 새로운 기능을 동결할 수 없으며, 기존 동작을 파악하기 어렵다는 점입니다. 이는 상당 부분이 더 이상 필요하지 않기 때문이기도 합니다.
세 번째 이유는 제가 밑줄을 긋고 싶은 부분입니다. 은행 및 제품 회사에서 현대화 작업을 수행해 본 경험에 비추어 볼 때, 비용이 많이 드는 부분은 새로운 코드를 작성하는 경우가 거의 아닙니다. 오히려 아무도 문서로 남기지 않은 것들을 포함하여 기존 코드가 실제로 무엇을 하는지 아는 것입니다.
스트랭글러 피그 패턴(Strangler Fig pattern)은 한 번에 모든 것을 교체하지 않음으로써 이 문제를 해결합니다. 파사드(Facade)가 레거시 시스템 앞에 자리 잡고 각 요청을 오래된 코드 또는 새로운 서비스 중 하나로 라우팅합니다. 기능을 한 번에 하나씩 옮기면서, 레거시 시스템은 나머지 모든 것을 계속 제공합니다.
┌──────────────────────┐
clients ──▶ FACADE ──▶│ legacy monolith │ (아직 마이그레이션되지 않은 모든 것)
│ └──────────────────────┘
...
Azure Architecture Center에서는 최종 상태를 설명합니다. 레거시 시스템에 기능이나 의존성이 남아있지 않으면, 해당 시스템을 폐기하고 결국 파사드까지 제거하게 됩니다. 그때까지는 과도기적 아키텍처(transitional architecture), 즉 오래된 것과 새로운 것이 공존하기 위해서만 존재하는 코드에 대한 비용을 지불해야 합니다. Fowler의 주장은 낮은 위험성과 더 빠른 가치가 그 비용을 상쇄할 만큼 충분하다는 것입니다.
| 빅뱅 리라이트 (Big-bang rewrite) | 스트랭글러 피그 (Strangler Fig) | |
|---|---|---|
| 가치를 얻는 시점 | 마지막에, 만약 가능하다면 | 첫 번째 기능 이후부터 |
| ... |
마지막 행에 주목하십시오. 이 패턴은 레거시 시스템을 이해해야 할 필요성을 제거하지 않습니다. 대신 그 작업을 마이그레이션 전반에 걸쳐 분산합니다. 이것이야말로 에이전트가 현재 속도를 높이는 데 능숙한 바로 그 작업입니다.
AI 에이전트의 역할
전략 자체는 변하지 않습니다. 점진적 마이그레이션, 공존(coexistence), 그리고 통제된 위험은 이 패턴의 기반이며, 에이전트는 이를 변화시키지 않습니다. 에이전트가 변화시키는 것은 각 마이그레이션을 둘러싼 단계들—즉, 발견(discovery), 문서화(documentation), 테스트 생성(test generation), 그리고 잘 정의된 슬라이스(slice) 구현—에 드는 비용입니다.
이에 대한 속도 향상과 그 한계 모두에 대해 공개적인 증거가 있습니다:
- Thoughtworks는 CodeConcise를 구축했는데, 이는 레거시 코드를 추상 구문 트리(AST)로 파싱하고, 이를 지식 그래프에 저장한 다음 LLM이 설명하고 쿼리할 수 있게 합니다. COBOL/IDMS 시스템의 개념 증명(proof of concept) 사례에서 그들은 10,000줄 분량의 모듈을 리버스 엔지니어링하는 시간이 6주에서 2주로 단축될 수 있다고 추정했습니다. 같은 기사는 출력물에 대해서는 "인간이 전적으로 통제권을 갖는다"고 주장하며, 도메인 전문가가 이를 검증해야 한다고 강조합니다.
- Google은 LLM 기반 코드 마이그레이션 경험 보고서를 발표했습니다. 한 대규모 마이그레이션의 경우, 적용된 변경 목록(landed changelists) 중 80%가 완전히 AI에 의해 작성되었으며, 엔지니어들은 총 시간이 약 50% 감소했다고 추정했습니다. 또한 코드 검토와 배포는 "여전히 인간 작업자(human operator)"가 필요하며, 또 다른 마이그레이션에서는 검토 속도가 병목 지점이었다고 언급했습니다.
두 보고서 모두 같은 분기점을 가리킵니다. 에이전트는 아티팩트(artifact)를 생성하는 데 강합니다. 하지만 이 아티팩트를 신뢰할 수 있게 만드는 것은 여전히 인간과 결정론적 검사입니다. 따라서 설계 질문은 "에이전트가 얼마나 자율적일 수 있는가?"가 아닙니다. 그것은 "각 에이전트가 무엇을 생성하며, 그 결과물에 의존하기 전에 어떻게 검사할 것인가?"입니다.
에이전트 기반 현대화 파이프라인 설계
'모듈을 마이그레이션하는' 단일 에이전트를 사용하는 대신, 저는 좁은(narrow) 에이전트들의 체인(chain)을 사용할 것입니다. 각 에이전트는 하나의 임무만을 가지고 있으며, 제한된 권한과 검증 가능한 아티팩트를 출력합니다. 만약 아티팩트가 검사에 실패하면, 실수가 코드, 테스트 및 라우팅으로 확산되기 전에 체인이 멈춥니다.
| 에이전트 | 입력 | 출력 아티팩트 | 쓰기 가능 범위 | 검증 주체 |
|---|---|---|---|---|
| 1. Discovery | 레거시 저장소, DB 스키마, 정제된 트래픽 샘플 | capability-map.yaml, 모든 주장과 증거 | 없음 (읽기 전용) | 정적 호출 그래프 교차 검증 + 엔지니어의 주장에 대한 샘플 검토 |
| ... |
이 두 가지 설계 선택지가 대부분의 안전장치를 담당합니다.
코드를 작성하는 에이전트는 자신을 판단하는 검사(checks)에 접근할 수 없어야 한다. 특성 검증 스위트(characterization suite)는 먼저 생성되어 레거시 시스템을 통해 검증된 후, 구현 에이전트를 위해 읽기 전용으로 마운트된다. 나는 Dark Factory vs. Light Factory에서도 같은 주장을 했다: 이러한 경계가 없다면, 녹색 스위트(green suite)는 거의 아무것도 증명하지 못한다.
모든 주장에는 증거가 따른다. 산문 형태의 발견 요약은 대규모로 검사하는 것이 불가능하다. 파일과 라인 번호, 또는 트래픽 샘플이 포함된 주장은 스크립트나 사람이 몇 초 만에 확인할 수 있다.
한 가지 기능을 단계적으로 마이그레이션하기: 실제 예시
아래의 예시는 가설적이지만 형태는 현실적이다. ShippingCalculator를 가지고 있고 배송 견적을 반환하는 엔드포인트가 있는 Java 모놀리스(monolith)를 가정한다. 이 예시가 좋은 첫 번째 슬라이스인 이유는 다음과 같은 가치가 있기 때문이다: 데이터베이스에 아무것도 쓰지 않고 값을 계산하기만 한다. 따라서 구 버전과 신 버전이 데이터를 두고 경쟁하지 않으면서 나란히 실행될 수 있다. 하지만 단계 1에서 보여주듯이, 이것만으로는 두 번 실행해도 안전하다고 할 수는 없다.
단계 1: 증거를 통한 발견(discovery with evidence)
발견 에이전트(discovery agent)는 저장소(repository), 스키마, 그리고 정제된 요청 샘플에 대한 읽기 접근 권한을 얻는다. 그 결과물은 에세이가 아니다. 그것은 지도이다:
# capability-map.yaml (발견 에이전트가 생성)
capability: shipping-quote
entry_points:
...
여기서 개시 장면이 해결된다. 두 번째 진입점인 배치 작업(batch job)은 산문 요약이 놓치기 쉬운 의존성이다. 결정론적 호출 그래프 도구(deterministic call graph tool)는 두 호출자 모두가 존재함을 확인할 수 있으며, 엔지니어는 코드에 대해 몇 가지 규칙을 샘플링할 수 있다. 각 규칙은 증거를 제시하는 누군가가 달리 말하지 않는 한 미확인(unconfirmed) 상태로 유지된다.
open_questions 목록은 규칙만큼이나 중요하다. 좋은 발견 에이전트는 단순히 무엇을 의미하는지 조용히 결정하기보다는, 특정 우편번호 범위에 대한 12월 할증과 같은 이상한 것들을 드러내기 때문에 유용하다.
두 번째 질문은 비즈니스 규칙이 아니라 안전성(safety)에 관한 것입니다. 데이터베이스 쓰기 작업이 없다는 것이 부작용(side effects)이 없다는 것과 같지 않습니다. 만약 캐리어가 호출당 요금을 청구하거나, 속도 제한을 걸거나, 모든 견적을 기록한다면, 레거시 시스템과 새로운 시스템을 병렬로 실행하는 것은 그 효과를 두 배로 늘립니다. 섀도잉(shadowing)은 경로상의 모든 외부 호출이 확인된 경우에만 안전하며, 이 확인 작업에는 코드뿐 아니라 캐리어 계약을 아는 사람이 필요합니다.
단계 2: 레거시 시스템에 대한 캐릭터라이제이션 테스트
다음으로, 캐릭터라이제이션 에이전트(characterization agent)는 맵을 골든 케이스(golden cases)로 변환합니다. 이는 각 규칙을 실행하는 입력 값과, 해당 에이전트가 코드를 읽고 예측한 출력 값을 포함합니다. 트래픽 샘플에서 가져온 사례들은 실제 입력을 추가하며, 이때의 출력값은 레거시 시스템이 실제로 반환했던 값입니다.
{
"id": "free-shipping-threshold-is-inclusive-after-tax",
"request": { "weightKg": 2.4, "destination": "PT", "orderTotalCents": 5000, "customerTier": "standard" },
...
}
동일한 테스트 스위트(suite)는 두 시스템 모두에 대해 실행되며, 대상 URL만 변경됩니다:
import { describe, it, expect } from "vitest";
import cases from "./golden/shipping-quote.cases.json";
...
이 스위트를 수용하기 위한 규칙은 엄격합니다: 변경되지 않은 레거시 시스템에서 100% 통과해야 합니다. 예측된 케이스가 실패한다는 것은 에이전트가 이전 동작을 잘못 읽었다는 의미이며, 이는 모놀리스(monolith)의 버그가 아니라 이해도의 결함입니다. 따라서 사례와 기능 맵(capability map)을 함께 수정하여 오독한 내용이 문서에 남지 않도록 해야 합니다. 레거시 계산기에 뮤테이션 테스트 도구 (Java의 PIT, JavaScript 또는 .NET의 Stryker 등)를 실행하면, 이 스위트가 실제로 깨진 규칙을 포착하는지 아니면 단순히 통과만 하는지를 알려줍니다.
이 지점은 사람이 첫 번째 실제 결정을 내리는 곳입니다. 12월 할증료는 비즈니스 규칙인지 아니면 잊힌 임시방편(hack)인지에 따라 다릅니다. 제 추천은 먼저 마이그레이션하고 나중에 동작을 변경하는 것입니다. 즉, 마이그레이션 기간 동안 새 서비스가 버그와 호환되도록 유지하고, 그 규칙 수정은 별도의 눈에 띄는 변경 사항으로 나중에 처리해야 합니다. 이 두 가지를 혼합하면, 이전과 새로운 방식 간의 모든 차이가 모호해집니다.
단계 3: 파사드(Facade)와 라우팅 모드(Routing Modes)
계약(contract)이 승인되고 새 서비스가 CI에서 테스트 스위트(suite)를 통과하면, 파사드는 경로당 네 가지 모드를 갖게 됩니다. 이것은 TypeScript로 작성된 예시적인 개요이며; 실제 운영 환경에서는 동일한 로직이 API 게이트웨이나 서비스 메시(Service Mesh)에 존재하는 경우가 많습니다.
type RouteMode = "legacy" | "shadow" | "canary" | "new";
interface RouteConfig { mode: RouteMode; canaryPercent: number }
interface ShadowJob { correlationId: string; body: string; legacyStatus: number; legacyBody: string }
...
워커(worker)는 요청 경로 외부에서 큐를 소비하고, 새 서비스에 호출하며, 동일한 상관관계 ID(correlation id) 아래 비교 기록을 남깁니다:
export async function handleShadowJob(job: ShadowJob): Promise<void> {
const candidate = await forward(NEW_URL, job.body, job.correlationId);
const expected = { status: job.legacyStatus, body: JSON.parse(job.legacyBody) };
...
여기서 중요한 것은 다음과 같습니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기