ERP급 도메인이 많은 시스템 개발 전략과 레거시 DB 리버스 엔지니어링 방법론.
A-JADM v1.3의 실전 확장 문서입니다.
본 문서는 A-JADM v1.3의 확장 문서로, 실전에서 가장 빈번한 두 가지 시나리오를 다룹니다.
두 주제는 별개이지만, 현실의 ERP 현대화 프로젝트는 거의 항상 두 문제를 동시에 직면하므로 하나의 확장 문서로 묶었습니다.
ERP를 하나의 모노리식 프로젝트로 열어서 Claude Code로 개발하려 하면 실패합니다.
컨텍스트 윈도우, 추론 비결정성, 세션 간 일관성 문제가 규모에 비례해 누적되기 때문입니다.
erp-system/ ├── erp-shared-kernel/ # 공통 VO만 ├── erp-master-data/ # 기준정보 BC ├── erp-sales/ # 영업·수주 BC ├── erp-purchase/ # 구매 BC ├── erp-inventory/ # 재고 BC ├── erp-accounting/ # 회계 BC └── erp-hr/ # 인사 BC
각 BC가 독립된 Claude Code 세션을 갖습니다.
루트 디렉토리·CLAUDE.md·계약 파일·커맨드 세트를 별도로 유지합니다.
ERP에서 BC 간 의존성은 명확합니다. 의존성 방향대로 Wave(물결)로 묶어 순차 개발합니다.
같은 Wave 내의 BC는 병렬 개발이 가능합니다.
ERP에서 가장 위험한 실수는 BC 간 직접 참조입니다.
다음 두 패턴만 허용하고, 어느 것을 쓸지는 arch-decisions.md에 명시합니다.
수주 시점에 재고 확인 같은 경우.
포트 인터페이스는 호출 BC의 application 레이어, 구현체는 구현 BC의 adapter 레이어에 둡니다.
pom.xml에서 양방향 직접 참조 금지.
// sales/application/port/output/
public interface InventoryPort {
boolean checkStock(ProductId id, int qty);
}
// inventory/adapter/outbound/
@Component
public class InventoryPortAdapter
implements InventoryPort { ... }
수주 완료 후 회계 전표 자동 생성 같은 경우.
Domain Event를 Kafka/RabbitMQ/Spring Events로 발행.
수신 BC는 ACL Translator로 자신의 도메인 모델로 변환.
// sales → OrderConfirmed → Kafka
// accounting이 order.confirmed 구독
@KafkaListener(topics = "order.confirmed")
public void onOrderConfirmed(
OrderConfirmedMessage msg) { ... }
communications:
- from: sales
to: inventory
mode: sync-acl
port: InventoryPort
- from: sales
to: accounting
mode: async-event
event: OrderConfirmed
topic: order.confirmed
/implement-bc가 추론 없이 포트 인터페이스와 어댑터 위치를 그대로 생성합니다.
Claude Code로 ERP를 개발할 때 가장 중요한 결정은 어디서 세션을 끊을 것인가입니다.
세션 누적이 품질을 결정합니다.
"영업 모듈 만들고 재고 모듈 만들고 회계 모듈도 만들어줘"
→ 컨텍스트가 누적되며 후반 BC의 품질이 급격히 떨어짐
→ Claude가 앞서 생성한 코드를 혼동하기 시작
→ 일관성 없는 패키지 구조 · 중복 인터페이스 발생
설계 세션 (1회) → 계약 파일만 생성 후 폐기
BC별 구현 세션 (N회) → BC 하나만 담당
테스트 세션 (구현과 분리) → 순환논리 방지
세션 간 상태 전달은 오직 계약 파일로만 수행
| 세션 유형 | 실행 횟수 | 입력 | 산출물 |
|---|---|---|---|
| 설계 세션 | 1회 | 도메인 설명 | domain-context.md, arch-decisions.md, bc-plan.md |
| BC별 구현 세션 | N회 | 계약 파일 3종 + 해당 BC의 usecases/ |
BC의 domain / application / adapter 코드 |
| 테스트 세션 | N회 (구현과 분리) | 계약 파일 + 구현 코드 | 테스트 코드 |
ERP 프로젝트 시작 전 반드시 확인할 사항입니다.
| 확인 항목 | 통과 조건 |
|---|---|
| BC 개수 | 각 BC가 Aggregate 3~7개 내외. 10개 넘으면 분할 검토 |
| 공유 커널 크기 | Money, Address, UserId 등 정말 불변인 VO만. 10개 이하 권장 |
| 순환 의존 | bc-plan.md의 depends-on을 DFS로 검사해 사이클 0 |
| Wave 크기 | 한 Wave에 BC 3개 이하 (병렬 개발 피로도 고려) |
| 세션 분리 규칙 | 설계 / BC별 구현 / 테스트 세션이 물리적으로 분리됨 |
| 계약 파일 존재 | domain-context.md, arch-decisions.md가 없으면 /implement-bc 실행 금지 |
legacy-db-context.md로 고정한 뒤, 이 위에서 DDD 모델링을 수행합니다.
리버스 엔지니어링을 시작하기 전에 레거시 DB의 유형을 먼저 판정해야 합니다.
유형에 따라 전략이 완전히 달라지기 때문입니다.
| 진단 항목 | 확인 방법 | 판정 기준 |
|---|---|---|
| FK 존재율 | information_schema.referential_constraints |
70% 미만이면 B/D형 |
| Stored Procedure 수 | information_schema.routines |
테이블 수의 30% 넘으면 C/D형 |
| Trigger 개수 | information_schema.triggers |
테이블당 평균 1개 초과면 C/D형 |
| 네이밍 일관성 | 테이블 prefix, 컬럼 suffix 패턴 분석 | 3개 이상의 네이밍 규칙 혼재 시 B/D형 |
| NULL 허용 비율 | 컬럼 nullable 비율 | 80% 넘으면 제약 부재 → B/D형 |
| 1:N 역참조 | 애플리케이션 코드 grep | 문자열 기반 참조가 많으면 B/D형 |
pg_dump --schema-only / mysqldump -d 결과로 먼저 수행합니다. legacy-db-context.md의 classification 필드에 기록됩니다.
A-JADM의 표준 파이프라인 앞단에 레거시 처리 단계를 추가합니다.
/reverse-schema가 /ddd-modeling보다 먼저 실행된다는 것이 핵심입니다.
기존 테이블의 물리적 경계를 무시하고 DDD부터 시작하면 나중에 매핑이 불가능해집니다.
[ Phase 0 ] 레거시 진단 — 세션 밖에서 사람이 수행 → classification: A/B/C/D 판정 [ Phase 1 ] 스키마 추출 /reverse-schema --source postgres --conn "jdbc:..." → legacy-db-context.md (계약 파일) [ Phase 2 ] UC 추출 (기존 화면/배치 기반) /usecase --from-legacy → usecases/UCxxx.md [ Phase 3 ] DDD 모델링 (레거시 참조) /ddd-modeling --with-legacy → domain-context.md (legacy-db-context.md 참조) [ Phase 4 ] 매핑 결정 /arch-design --legacy-strategy strangler → arch-decisions.md (매핑 전략 포함) [ Phase 5 ] 구현 (기존 A-JADM과 동일) /uc-to-skeleton → /implement-bc → /gen-test
--with-legacy, --legacy-strategy 플래그로 레거시 참조. 이것이 전략의 핵심입니다.
레거시 DB의 사실(fact)을 Claude Code가 추론 없이 참조할 수 있도록 YAML로 고정합니다.
## Meta
schema-version: 1.0
content-version: 1.0.0
extracted-at: 2026-04-16
source-db: postgres 12.4
classification: B # A/B/C/D
## Summary
total-tables: 347
total-views: 42
total-procedures: 18
total-triggers: 9
fk-coverage: 43% # 낮음 → B형 근거
## Tables
tables:
- name: TB_ORDER_MST
purpose: "수주 마스터"
primary-key: [ORD_NO]
candidate-aggregate-root: Order # ddd-modeling에 힌트
row-count: 4200000
columns:
- name: ORD_NO
type: VARCHAR(20)
nullable: false
semantic: "YYYYMMDD + 일련번호 5자리"
- name: CUST_CD
type: VARCHAR(10)
nullable: false
implicit-fk: TB_CUSTOMER.CUST_CD # 물리 FK 없음
cross-bc-reference: master-data # BC 간 참조로 변환 대상
## Implicit References
# 앱 코드에서만 존재하는 참조 관계 (grep으로 발견)
implicit-references:
- from: TB_ORDER_DTL.ITEM_CD
to: TB_ITEM_MST.ITEM_CD
evidence: "src/order/OrderService.java:142"
## Stored Procedures (C/D형일 때 필수)
stored-procedures:
- name: SP_CALCULATE_ORDER_AMOUNT
purpose: "수주 금액 계산 (할인/세금 포함)"
migration-strategy: keep-as-adapter # keep-as-adapter / rewrite / delete
risk: high # 회계 영향
## Mapping Decisions
# 테이블 → Aggregate 매핑 초안 (사람이 확정)
mapping:
- aggregate: Order
root-table: TB_ORDER_MST
child-tables: [TB_ORDER_DTL, TB_ORDER_DISC]
bc: sales
notes: "TB_ORDER_DLVR은 별도 Shipment Aggregate로 분리"
- aggregate: Customer
root-table: TB_CUSTOMER
bc: master-data
notes: "TB_CUST_CREDIT은 sales BC의 CreditLimit VO로 인입"
/ddd-modeling은 "수주 도메인을 설계해줘"라는 막연한 요청에 추론으로 대응하지 않고, TB_ORDER_MST의 실제 구조를 읽어 Aggregate를 제안합니다.
진단 결과 확정된 유형에 따라 완전히 다른 접근을 적용합니다.
FK가 잘 잡혀 있고 로직이 앱 계층에 있는 드문 경우입니다.
테이블을 그대로 Aggregate로 매핑해도 대부분 맞습니다. 다만 기존 DB를 그대로 쓸 것인지, 새 스키마로 갈 것인지 결정합니다.
persistence: jpa + @Table(name="TB_ORDER_MST")로 매핑. /gen-schema 실행하지 않고 legacy DDL을 그대로 schema.sql로 사용/gen-schema로 새 ERD 생성. ETL 스크립트 별도 작성. 데이터 이관 기간 확보FK 없고 네이밍 혼란.
테이블을 직접 Aggregate로 매핑하면 도메인 모델이 오염됩니다. 해법은 뷰(VIEW)를 중간 계층으로 삽입하는 것입니다.
-- 도메인 관점의 정제된 뷰
CREATE VIEW V_ORDER AS
SELECT
ORD_NO AS order_id,
CUST_CD AS customer_id,
ORD_DT AS order_date,
ORD_AMT AS total_amount,
CASE ORD_STS
WHEN '1' THEN 'PENDING'
WHEN '2' THEN 'CONFIRMED'
WHEN '9' THEN 'CANCELLED'
END AS status
FROM TB_ORDER_MST
WHERE DEL_YN = 'N';
persistence: jpa+jdbc로 가면 Command(JPA)는 실제 테이블에, Query(JdbcTemplate)는 뷰에 매핑하는 식으로 분리할 수 있습니다.
legacy-db-context.md에는 이 뷰 매핑을 명시적으로 기록합니다.
SP가 20개 넘게 있고 회계/재고 계산 로직이 들어 있는 경우.
이것을 Java로 옮기겠다고 덤비면 반드시 망합니다. 전략은 SP를 Outbound Port로 래핑하는 것입니다.
// application/port/output/
public interface LegacyOrderCalculator {
OrderAmount calculate(
OrderCalcRequest req);
}
// adapter/outbound/
@Repository
public class LegacyOrderCalculatorAdapter
implements LegacyOrderCalculator {
@Override
public OrderAmount calculate(
OrderCalcRequest req) {
// JdbcTemplate으로 SP 호출
// 결과 → OrderAmount VO
}
}
legacy-db-context.md의 stored-procedures[].migration-strategy에 keep-as-adapter로 표시해두면 /uc-to-skeleton이 이 구조를 자동으로 생성합니다.
이 경우 전면 재개발을 시도하면 3년 뒤에도 끝나지 않습니다.
현실적 전략은 기존을 유지하고 신규만 새 BC로 분리하는 것입니다.
# legacy-db-context.md
cdc-mapping:
- source-table: TB_ORDER_MST
event: LegacyOrderChanged
topic: legacy.order.changed
consumers: [new-sales-bc, analytics-bc]
레거시 리버스는 일반 개발보다 세션 분리가 더 중요합니다.
컨텍스트 오염이 도메인 모델의 신뢰성을 직접 해치기 때문입니다.
| 세션 | 입력 | 산출 | 비고 |
|---|---|---|---|
| 진단 세션 | 스키마 덤프 (DDL) | legacy-db-context.md의 Summary + Classification |
사람 확인 필수 |
| 테이블 분석 세션 | DDL + sample data + grep 결과 | tables[], implicit-references[] |
대용량 → 배치로 분할 |
| 매핑 결정 세션 | 위 산출 + usecases/ |
mapping[] 섹션 |
반드시 사람이 검토·확정 |
| DDD 모델링 세션 | legacy-db-context.md + usecases/ |
domain-context.md |
기존 A-JADM 그대로 |
| 구현 세션 | 모든 계약 파일 | 코드 | BC별 분리 |
실전에서 자주 놓치는 세 가지 포인트.
ORD_STS = '9'가 "취소"인지 "보류"인지 코드만 봐서는 모릅니다.
Claude Code가 절대 혼자 못 합니다. 도메인 전문가 인터뷰 결과를 legacy-db-context.md의 columns[].semantic 필드에 사람이 직접 채워 넣어야 합니다.
야간 정산 배치, 월마감 프로시저 같은 것들이 실제로는 도메인 규칙의 절반을 차지합니다.
UC 추출 시 화면뿐 아니라 크론/스케줄러 목록도 반드시 입력에 포함시킵니다.
새 BC가 동일 입력에 대해 레거시와 같은 결과를 내는지 확인하려면, 한동안 둘 다 실행하고 결과를 비교하는 shadow write 기간이 필요합니다.
최소 1개 월마감 주기는 돌려봐야 합니다.
본 확장 문서는 진단 프레임과 계약 파일 스키마 초안까지 정립한 수준입니다.
아래는 실전 적용을 통해 완성해 나갈 항목입니다.
reverse-schemalegacy-db-context.mdlegacy-uc-extract (예정)usecases/UCxxx.mdlegacy-db-context.mdreverse-schemaddd-modeling, arch-design, uc-to-skeleton
/reverse-schema --source postgres --conn "jdbc:postgresql://..." /reverse-schema --classify-only # 진단만 수행 /ddd-modeling --with-legacy # legacy-db-context.md 참조 /arch-design --legacy-strategy strangler # 전략 명시
reverse-schema 스킬의 SKILL.md 작성legacy-db-context.md YAML 스키마 완전 정의