하드코딩 vs 룰 엔진: 전환 시점은 언제인가
if-else로 잘 돌아가던 코드가 어느 순간 짐이 되는 순간이 있습니다. 하드코딩한 비즈니스 룰이 부채가 되는 시점과 마이그레이션 방법을 정리했습니다.
아무도 건드리고 싶지 않은 코드
모든 코드베이스에 하나씩 있습니다. PricingService, DiscountCalculator, EligibilityChecker 같은 이름을 가진 파일. 처음엔 깔끔한 20줄이었습니다. 지금은 800줄짜리 중첩 조건문 덩어리가 됐고, 전체를 이해하는 사람이 없습니다.
if (customer.getTier().equals("VIP") && cart.getTotal() > 100_000) {
if (!promotion.isBlackoutPeriod() && !cart.hasExcludedCategory("ELECTRONICS")) {
if (customer.getLoyaltyYears() >= 3) {
discount = 0.20;
} else {
discount = 0.15;
}
} else if (cart.getTotal() > 200_000) {
discount = 0.10; // 블랙아웃 기간 대형 주문 예외
}
} else if (customer.getTier().equals("GOLD")) {
// ... 40줄 더
}
이 코드는 돌아갑니다. 2년째 프로덕션에서 수백만 건을 처리하고 있습니다. 문제는 누군가 임계값을 바꾸거나 조건을 추가해야 할 때마다 같은 순서가 반복된다는 것입니다. Jira 티켓 생성, 개발자가 마지못해 파일을 열고, 딱 한 시나리오만 커버하는 테스트를 작성하고, // TODO: 리팩토링 주석과 함께 배포합니다.
리팩토링은 일어나지 않습니다. 깨뜨리는 비용이 너무 높으니까요.
하드코딩의 한계가 드러나는 5가지 신호
모든 애플리케이션에 룰 엔진이 필요한 것은 아닙니다. 할인 로직이 price * 0.9이고 1년에 한 번 바뀐다면, 하드코딩이 맞습니다. 하지만 한계에 도달했다는 명확한 신호가 있습니다.
1. 비즈니스 변경에 코드 배포가 필요합니다
기획자가 무료 배송 기준을 10만원에서 7만5천원으로 바꿔달라고 합니다. 5분이면 끝날 일인데 실제로는 3일이 걸립니다. PR → 코드 리뷰 → QA → 스테이징 → 프로덕션 배포 → 모니터링. 비즈니스 로직이 소프트웨어 개발 사이클에 인질로 잡혀 있는 것입니다.
2. 전체 규칙을 아는 사람이 없습니다
팀에 물어보세요. "지금 프로덕션에 적용 중인 할인 규칙 전부 말해줄 수 있어?" 답이 여러 파일의 소스 코드를 읽어야 나온다면, 지식 관리에 문제가 있는 것입니다. 비즈니스 로직의 원천 정보(source of truth)를 확인하는 데 IDE가 필요해서는 안 됩니다.
3. 규칙이 충돌하는데 아무도 모릅니다
어떤 고객이 VIP 할인 20% + 충성도 할인 15% + 프로모션 쿠폰을 동시에 적용받아서 45% 할인을 받고 있습니다. 의도한 건가요? 아무도 모릅니다. 하드코딩된 룰이 여러 서비스에 흩어져 있으면, 충돌 탐지는 수동 검토에 의존하거나 아예 불가능합니다.
4. 테스트로 못 잡는 케이스가 프로덕션에서 터집니다
규칙이 3개일 때는 테스트로 충분합니다. 하지만 규칙이 20개가 되면 서로 겹치는 조합이 수백 가지입니다. 이걸 전부 테스트에 넣는 건 현실적으로 불가능합니다. 결국 프로덕션에서 예상치 못한 조합이 터지고, 장애 리포트에는 매번 같은 결론이 적힙니다 — "테스트를 더 촘촘하게 짜야 합니다." 하지만 더 촘촘하게 짜도 다음에 또 빠진 케이스가 나옵니다.
5. 비개발자가 줄을 서서 기다립니다
기획자, 운영팀, 비즈니스 분석가는 원하는 규칙을 정확히 알고 있습니다. 다만 직접 구현할 수 없을 뿐입니다. 모든 규칙 변경이 스프린트 백로그에서 기능 개발과 경쟁합니다. 결과: "이 임계값 바꿔주세요" Jira 티켓이 쌓이고, 우선순위에서 계속 밀립니다.
이 중 3개 이상에 해당한다면, 규칙을 외부화할지 말지가 아니라 언제 할지가 문제입니다.
룰 엔진이 바꾸는 것
룰 엔진은 비즈니스 규칙을 없애는 것이 아닙니다. 바꾸기 어려운 곳(애플리케이션 코드)에서 바꾸기 쉬운 곳(전용 시스템)으로 옮기는 것입니다.
아키텍처 비교
하드코딩 방식:
비즈니스 요청 → Jira 티켓 → 개발자 PR → 코드 리뷰
→ QA → 스테이징 → 프로덕션 배포 → 모니터링
소요: 2~5일
룰 엔진 방식:
비즈니스 요청 → 콘솔/API → 시뮬레이션 → 배포
소요: 5~30분
핵심 차이는 속도가 아니라 관심사의 분리입니다. 애플리케이션 코드는 어떻게 동작하는지를 담당합니다(결제 처리, 주문 이행, API 오케스트레이션). 규칙은 무엇을 결정할지를 담당합니다(누구에게 할인을 줄지, 어떤 주문에 승인이 필요한지, 언제 거래를 차단할지).
이 두 관심사가 뒤섞이면, 하나를 바꿀 때 다른 하나가 깨질 위험이 있습니다. 분리되면, 각각 독립적으로 진화할 수 있습니다.
코드에 남는 것
룰 엔진이 애플리케이션을 대체하는 것은 아닙니다. 애플리케이션 안의 의사결정 로직을 대체하는 것입니다. 코드가 여전히 하는 일은 다음과 같습니다:
- 관련 팩트를 담아 룰 엔진 API를 호출합니다
- 어떤 룰이 매칭됐고 어떤 액션을 취할지, 응답을 받습니다
- 액션을 실행합니다 (할인 적용, 알림 발송, 거래 차단)
비즈니스 로직은 빠져나갑니다. 애플리케이션 로직은 그대로 남습니다.
# 애플리케이션이 룰 엔진을 호출합니다
curl -X POST https://api.lexq.io/api/v1/execution/groups/{groupId} \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"facts": {"customer_tier": "VIP", "cart_total": 150000}}'
{
"result": "SUCCESS",
"data": {
"outputVariables": {
"discount_amount": 30000,
"discount_applied": true
},
"executionTraces": [
{
"ruleName": "vip-discount",
"conditionMatched": true,
"evaluatedExpression": "cart_total > 100000 AND customer_tier == VIP"
}
],
"decisionTraces": [
{
"ruleName": "vip-discount",
"selected": true,
"reason": "CONDITION_MATCHED"
}
]
}
}
애플리케이션은 이 응답을 받아서 20% 할인을 적용합니다. 내일 25%로 바꾸고 싶다면 코드 배포 없이 룰만 수정하면 됩니다.
마이그레이션 방법
한 번에 전부 옮기지 마세요. 그런 식의 마이그레이션은 실패합니다.
1단계: 규칙 하나를 고릅니다
가장 자주 바뀌고, 가장 많은 마찰을 일으키는 규칙을 선택합니다. 보통 가격/할인, 자격 검증, 승인 워크플로 중 하나입니다.
가장 복잡한 것을 고르지 마세요. 기획, 운영팀 쪽에서 "변경이 너무 느리다"고 가장 크게 불만을 제기하는 것을 고릅니다.
2단계: 현재 로직을 정리합니다
기존 코드의 모든 조건과 결과를 문서화합니다. 가장 고통스럽고 어려운 단계입니다. 아무도 기억 못 하는 규칙, 다른 규칙과 모순되는 엣지 케이스, 기술적으로 도달 불가능한 조건을 발견하게 될 것입니다.
이 문서화 작업 자체가 룰 엔진을 도입하지 않더라도 가치 있습니다.
3단계: 룰 엔진에 같은 규칙을 만듭니다
기존 코드에 있는 규칙을 룰 엔진 콘솔에서 똑같이 정의합니다. 그 다음, 실제 요청이 들어올 때마다 기존 코드와 룰 엔진 양쪽에 동시에 보내봅니다. 실제 할인 적용은 기존 코드가 하고, 룰 엔진은 "나라면 이 결과를 줬을 것이다"를 기록만 합니다.
두 결과가 다르면? 마이그레이션에서 빠진 조건이 있다는 뜻입니다. 실제로 해보면 문서화에서 놓친 엣지 케이스가 거의 반드시 나옵니다.
4단계: 실제 데이터로 시뮬레이션을 돌립니다
전환 전 마지막 확인입니다. 최근 10,000건의 실제 주문 데이터를 룰 엔진에 넣어봅니다. 기존 코드가 내린 결정과 룰 엔진이 내리는 결정이 같은지 비교합니다.
결과가 같으면 전환 준비가 된 것입니다. 다르면 아직 빠진 규칙이 있다는 뜻이니, 프로덕션에 영향을 주기 전에 잡은 셈입니다.
5단계: 전환합니다
애플리케이션 코드에서 if-else 블록 대신 룰 엔진 API를 호출하도록 바꿉니다. 기존 코드는 바로 삭제하지 마세요. 주석 처리해두고 30일간 운영합니다. 문제가 생기면 주석만 풀면 되니까요.
30일간 아무 문제 없으면, 그때 삭제합니다. 다시 찾을 일은 없을 겁니다.
전환하지 말아야 할 때
룰 엔진은 네트워크 홉 하나, 외부 의존성 하나, 운영 복잡성을 추가합니다. 이 트레이드오프가 항상 가치 있는 것은 아닙니다.
하드코딩을 유지해야 할 때:
- 규칙이 분기에 1회 미만으로 변경됩니다
- 전체 규칙이 5개 이하입니다
- 순수하게 기술적인 규칙입니다 (재시도 정책, 서킷 브레이커 임계값)
- 레이턴시 요구사항이 서브밀리초입니다 (고빈도 거래)
룰 엔진을 고려해야 할 때:
- 규칙이 주 단위 이상으로 자주 변경됩니다
- 여러 이해관계자가 규칙을 수정해야 합니다
- 규칙 충돌이 반복적인 프로덕션 장애 원인입니다
- 모든 규칙 변경과 실행에 대한 감사 추적이 필요합니다
- 배포 전에 실제 데이터로 규칙 변경을 테스트하고 싶습니다
작게 시작하세요
한방에 마이그레이션에 올인할 필요가 없습니다. 규칙 하나를 골라서 외부화하고, 팀의 속도가 개선되는지 확인하세요. 개선되면 확장합니다. 아니라면 하루치 셋업 비용을 잃은 것이지, 분기를 날린 것이 아닙니다.
LexQ 무료 플랜은 월 1,000건 실행을 포함합니다. 프로덕션 수준의 PoC를 돌리기에 충분합니다.
→ lexq.io에서 무료로 시작하기 → 문서 보기 → 룰 엔진 비교: GoRules vs Nected vs LexQ