1. 문제 상황
- 대회 신청 프로세스가 약관 동의 → 기념품 선택 → 배송지 입력 → 결제 등 여러 서비스 호출과 Saga 이벤트로 분산되어 있어,
- 각 단계별로 Micrometer 카운터·타이머를 일관되게 설정·기록하기 어려웠음
- 로그백(LokiAppender) 설정 또한, 호출 위치가 흩어져 있어 로그 레이블(
traceId
, spanId
등) 누락 및 형식 불일치가 자주 발생
2. 원인 분석
- 서비스 레이어(
CompetitionService
, ParticipantService
, SagaService
)에서 직접 모니터링 지표를 기록하려다 보니,
- 코드 중복(같은 타이머·카운터 로직이 여러 곳에)
- 설정 누락(특정 단계 로깅이 빠짐)
- 변경 시 매번 여러 클래스 수정 필요
- 결과적으로 “어느 단계에서 지표가 누락됐는지” 파악이 어려웠고, 로그 포맷 일관성 확보도 불가능
3. 해결 방안: Facade 패턴 도입
CompetitionApplicationFacade
클래스 추가
- 대회 신청의 단일 진입점(Entry Point) 역할을 수행
- MeterRegistry로 정의된 모든 Counter/Timer 빈을 한 곳에서 주입
- 단계별 모니터링 일원화
- 신청 시작 시
applicationStartCounter.increment()
- 약관 동의, 기념품 선택, 배송지 입력, 결제 완료 단계에서 각각의 카운터·타이머 호출
- 최종 완료 시
applicationCompleteCounter
및 전체 타이머 기록
- 로그백 설정 단순화
CompetitionApplicationFacade
내 로그 호출 지점을 집중시켜,
- 모든 로그가 동일한 패턴(
%X{traceId}
, %X{spanId}
포함)으로 출력되도록 보장
@Slf4j
@Service
public class CompetitionApplicationFacade {
public String applyForCompetition(CompleteAppDto appDto) {
applicationStartCounter.increment();
log.info("신청 시작: competition={}, participant={}", appDto.getCompetitionId(), appDto.getParticipantId());
try {
if (appDto.getTermsAgreed()) {
termsAgreementCounter.increment();
termsStepTimeTimer.record();
log.info("약관 동의 완료");
}
applicationCompleteCounter.increment();
applicationTotalTimeTimer.record();
log.info("신청 완료");
return "SUCCESS";
} catch (Exception e) {
applicationFailCounter.increment();
log.error("신청 실패: {}", e.getMessage(), e);
throw e;
}
}
}
4. 결과
- 코드 가독성·유지보수성 대폭 향상
- 신규 단계 추가 시 Facade 한 곳만 수정
- 로그·모니터링 설정 누락 현상 해소
- 모든 지표·로그가 한 클래스에서 집중 관리되어 일관된 형식 보장
- 장애 대응 속도 개선
- 어느 단계에서 실패·느려짐이 발생했는지 Grafana 대시보드에서 즉시 파악 가능