본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 25. 21:59

하드코딩이라는 악몽 없이 100개 이상의 국가에 대한 법정 급여(Statutory Payroll) 모델링하기

요약

다국가 급여 계산 시스템을 구축할 때 발생하는 하드코딩 문제를 해결하기 위해, 규칙을 코드가 아닌 선언적 데이터 스키마로 모델링하는 방법을 제시합니다. 이를 통해 코드 변경과 재배포 없이도 새로운 국가 추가 및 세율 변경에 유연하게 대응할 수 있습니다.

핵심 포인트

  • 하드코딩된 분기문은 확장성을 저해하고 유지보수를 어렵게 만듦
  • 규칙을 선언적 스키마(Rules as a declarative schema)로 설계하여 데이터로 관리
  • 유효 날짜(Effective dating)를 통해 재배포 없이 세율 변경 대응 가능
  • 계산 기준(Base)을 조합 가능한 개념으로 설계하여 복잡한 예외 처리 해결

만약 당신이 한 국가 이상의 국가에서 실수령액(Net pay)을 계산하려고 시도해 본 적이 있다면, 그 함정을 알고 있을 것입니다. 첫 번째 국가는 문제없이 진행됩니다. 함수를 작성하고, 작동하면 배포합니다. 두 번째 국가는 당신의 가설 중 절반을 깨뜨립니다. 다섯 번째 국가에 이르면 if country == "X"와 같은 분기문이 얽히고설켜 아무도 건드리고 싶어 하지 않는 급여 엔진이 되어버립니다.

이 글은 새로운 지역(Corridor)을 추가할 때 배포(Deploy)가 아닌 설정(Config) 변경만으로 가능하도록, 방대한 국가 세트에 대한 법정 급여(Statutory payroll) 규칙을 코드가 아닌 데이터로서 어떻게 모델링하는지에 대한 기록입니다.

핵심적인 실수: 코드로 작성된 규칙 (Rules as code)
초보적인 버전은 다음과 같으며, 문제가 발생하기 전까지는 괜찮아 보입니다.

def net_pay(gross, country):
    if country == "IN":
        pf = min(gross * 0.12, 1800)
...

새로운 국가가 추가될 때마다 코드 변경, 리뷰, 배포가 필요하며, 이는 이미 지원하고 있는 국가들을 망가뜨릴 새로운 기회가 됩니다. 또한 법정 세율(Statutory rates)은 연도 중간에 변경되기도 하므로, 단순한 데이터 업데이트여야 할 작업 때문에 재배포를 해야 합니다. 이는 몇몇 지역을 넘어 확장(Scale)할 수 없습니다.

변화: 선언적 스키마로서의 규칙 (Rules as a declarative schema)

더 나은 모델은 모든 법정 공제(Statutory deduction) 또는 기여금(Contribution)을 타입이 지정된 규칙 레코드(Typed rule record)로 취급합니다. 규칙은 국가, 유효 날짜 범위(Effective date range), 적용 대상 베이스(Base), 계산 방법(Calculation method), 그리고 경계값(Bounds)을 가집니다. 엔진은 범용적(Generic)인 상태를 유지하며 단지 규칙을 해석할 뿐입니다.

{
  "country": "IN",
  "component": "provident_fund_employee",
...

엔진은 특정 국가와 급여 지급일(Pay date)에 적용 가능한 규칙을 읽고, 의존성(Dependency)에 따라 정렬한 뒤 이를 적용합니다. 국가를 추가하는 것은 "해당 국가의 규칙 레코드를 작성하는 것"이 됩니다. 연도 중간에 세율을 변경하는 것은 "effective_to를 사용하여 기존 레코드를 종료하고, 새 레코드를 여는 것"이 됩니다. 배포는 필요 없습니다.

def compute(gross, components, country, pay_date):
    rules = rule_store.applicable(country, pay_date)
    ctx = {"gross": gross, **components}
...

실제로 당신을 괴롭히는 세 가지 요소

스키마를 설계하는 것은 쉬운 부분입니다. 진짜 실무 급여 계산의 어려움은 여기서부터 시작됩니다.

  1. "기본 금액 (base)"은 결코 단순히 총급여 (gross)가 아닙니다. 인도의 퇴직연금 (Provident fund)은 총급여가 아니라 기본급 (basic)에 물가상승분 (dearness allowance)을 더한 금액에 적용됩니다. 어떤 기여금은 상한선이 있는 기준 금액 (capped base)에 적용되고, 어떤 것은 상한선이 없는 기준 금액 (uncapped)에 적용됩니다. 만약 당신의 규칙이 "이 퍼센트가 어떤 금액에 적용되는가"를 표현할 수 없다면, 일주일 안에 예외 사항들을 하드코딩 (hardcode)하게 될 것입니다. base 필드는 사후에 고려되는 것이 아니라, 일급 시민 (first-class)이자 조합 가능한 (composable) 개념이어야 합니다.

  2. 유효 날짜 (Effective dating)는 선택 사항이 아닙니다. 법정 세율 (Statutory rates)은 변하며, 이는 당신의 일정이 아닌 정부의 일정에 따라 변합니다. 당신은 같은 주에 5월의 세율로 5월 급여를 실행하고, 3월의 세율로 3월에 대한 수정 사항을 다시 실행해야 할 것입니다. 모든 규칙에는 유효 기간 (validity window)이 필요하며, 엔진은 반드시 지급일 (pay date)을 기준으로 규칙을 선택해야 하며, 절대로 "최신 (latest)" 기준을 사용해서는 안 됩니다.

  3. 순서와 의존성 (Ordering and dependency). 어떤 공제 항목은 다른 공제 항목이 적용된 후의 금액을 기준으로 계산됩니다. 세금 (Tax)은 종종 이미 납부된 기여금에 따라 달라집니다. 규칙을 의존성 그래프 (dependency graph)로 취급하고 위상 정렬 순서 (topological order)로 해결하십시오. 규칙들을 단순히 평면적인 루프 (flat loop)로 돌린다면, 정작 가장 중요한 국가들에서 잘못된 기준 금액을 바탕으로 세금을 계산하는 실수를 조용히 저지르게 될 것입니다.

우리가 인간용 버전도 함께 발행하는 이유

엔진은 내부용이지만, 그 뒤에 숨겨진 법정 로직 (statutory logic)은 새로운 국가에서 채용을 시작하려는 기업이 확정하기 전에 반드시 이해해야 하는 바로 그 내용입니다. 따라서 계산을 구동하는 것과 동일한 규칙 데이터가 우리가 발행하는 국가별 가이드 (country guides)를 구동합니다. 퇴직연금 (provident fund) 상한선이 변경되면 엔진과 공개 페이지가 동일한 소스에서 업데이트되므로, 문서가 시스템과 동떨어지는 일이 발생할 수 없습니다.

이것의 인간이 읽기 쉬운 측면을 보고 싶다면, global employer of record 허브의 국가별 페이지에서 국가별 법정 구성 요소를 확인할 수 있으며, 특히 인도 코리더 (India corridor) 섹션에서는 기여 메커니즘 (contribution mechanics)에 대해 심도 있게 다루고 있습니다.

이를 구축하고 있다면 얻어야 할 교훈

  • 법정 규칙 (statutory rules)을 코드 내의 분기(branch)가 아닌, 유효 날짜가 포함된 데이터 레코드 (effective-dated data records)로 모델링하십시오.
  • "계산 기준 (base of calculation)"을 일급 필드 (first-class field)로 만드십시오. 이것이 가장 먼저 오류를 일으키는 요소입니다.
  • 규칙을 선택할 때는 반드시 지급일 (pay date)을 기준으로 하십시오. 절대 "가장 최근 (most recent)"을 기준으로 삼지 마십시오. 그렇지 않으면 수정 사항이 잘못될 것입니다.
  • 종속성 (dependencies)을 위상 정렬 순서 (topological order)로 해결하여, 하위 공제 항목 (downstream deductions)이 올바른 기준값을 읽을 수 있도록 하십시오.

그 결과로 얻는 보상은 새로운 국가를 온보딩하는 과정이 더 이상 엔지니어링 프로젝트가 아니라, 컴플라이언스 분석가 (compliance analyst)가 직접 수행할 수 있는 데이터 작업이 된다는 점입니다. 이것이 5개국을 지원하는 것과 100개국을 지원하는 것의 차이입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
1

댓글

0