#Backend

[코드가 환경을 모르는 구조 4/7] 타임머신 — 시간 축을 교체한다

[코드가 환경을 모르는 구조 4/7] 타임머신 — 시간 축을 교체한다
01

Summary

내일의 급여를 오늘 미리 계산한다면? flex의 '시간을 지배하는' 백엔드 설계법

헥사고날 아키텍처로 구현한 시간 격리와 비동기 전파 전략

이 아티클은 비즈니스 로직에서 가장 다루기 까다로운 요소인 '시간'을 어떻게 교체 가능한 컴포넌트로 만들었는지 다룹니다. 시스템 시계라는 절대적인 환경 변수를 Port와 Adapter 구조로 추상화하여, 운영 환경에서도 안전하게 시뮬레이션을 수행하는 구체적인 메커니즘을 소개합니다. 단순한 구현 기술을 넘어 아키텍처 규율이 팀의 개발 문화와 검증 속도에 어떤 영향을 미치는지 보여줍니다.

  • 01시간을 시스템 종속성이 아닌 주입 가능한 비즈니스 입력값으로 재정의
  • 02HTTP 헤더를 통해 특정 요청만 격리하여 시간을 이동시키는 타임머신 모드 구현
  • 03ThreadLocal에서 코루틴, Kafka로 이어지는 복잡한 비동기 구간의 컨텍스트 전파 기술
  • 04실제 운영 사고를 막기 위한 환경별 활성/비활성 제어 및 감사 로그 장치
  • 05외부 라이브러리 의존성 등 현실적인 제약 조건에 대한 대응 및 문서화 전략

RECOMMENDATION

시간 기반의 복잡한 정산 로직을 다루는 엔지니어나, 헥사고날 아키텍처의 실무 적용 사례를 찾고 있는 시니어 개발자에게 강력히 추천합니다.

The Problem

HR SaaS에서 시간은 단순한 기록용 타임스탬프가 아니라 급여 및 연차 계산 등의 결과를 결정짓는 핵심적인 비즈니스 로직의 입력값입니다. 시스템 시계에 의존하는 구조에서는 미래 시점의 퇴직금이나 급여 시뮬레이션을 위해 실제 그 날짜가 될 때까지 기다려야 하는 테스트의 한계가 존재합니다.

The Solution

모든 비즈니스 로직이 시스템 시계를 직접 호출하는 대신 'Clock' 인터페이스를 주입받아 사용하도록 규율화하고, 요청 헤더를 통해 시간 컨텍스트를 주입하는 '타임머신' 기능을 구현했습니다. ThreadLocal을 통해 요청별 시간을 격리하며, 코루틴 컨텍스트와 Kafka 메시지 헤더를 활용해 비동기 경계 너머까지 시간 정보를 일관되게 전파하는 구조를 채택했습니다.

The Result

별도의 코드 수정 없이 HTTP 헤더 하나만으로 특정 요청의 시간 축을 자유롭게 조작하여 미래나 과거 시점의 로직을 안전하게 검증할 수 있게 되었습니다. 이는 2020년부터 실무에서 신뢰할 수 있는 QA 도구 및 버그 재현 장치로 기능하며 개발 생산성을 크게 향상시켰습니다.

Trade-off

시스템 시계를 직접 호출하는 서드파티 라이브러리나 ORM의 자동 타임스탬프 기능까지는 완벽히 제어할 수 없어 시뮬레이션 시점과 실제 시점이 섞이는 미묘한 틈이 발생할 수 있습니다. 또한 Kafka 재처리 시 과거 헤더의 시간 정보가 의도치 않게 적용될 수 있어 별도의 예외 처리가 필요합니다.

03

Key Concepts

Concept · 01

Clock Interface

시스템 시계에 직접 접근하는 대신 현재 시간을 추상화하여 제공하는 인터페이스로, 테스트나 시뮬레이션 시 임의의 시간을 주입할 수 있게 합니다.

  • LocalDateTime.now() 호출을 엄격히 금지하고 주입된 객체만 사용하도록 강제
  • 운영 환경에서는 시스템 시계를, 디버그 환경에서는 헤더 기반 시계를 반환하는 Adapter 제공
Concept · 02

Context Propagation

하나의 작업 단위가 실행되는 동안 스레드나 프로세스 경계를 넘나들며 특정 상태 정보를 유지하고 전달하는 기술입니다.

  • Servlet 필터에서 시간 정보를 ThreadLocal에 저장하여 요청 범위 내 공유
  • 코루틴 컨텍스트 및 Kafka 헤더를 통해 비동기 작업 시에도 시간 일관성 유지
Concept · 03

Port and Adapter (Hexagonal)

애플리케이션의 핵심 비즈니스 로직을 외부 인프라나 환경으로부터 격리하기 위해 인터페이스를 사용하는 아키텍처 패턴입니다.

  • 시간 축을 교체 가능한 Port로 설계하여 환경에 구애받지 않는 비즈니스 코드 유지
  • 헤더와 설정값에 따라 동적으로 구현체(Adapter)를 교체하여 타임머신 기능 구현