API 키의 존재를 묻는 데 앱 전체가 필요했다

S2S 인증 가드는 튜터 기능이 활성화된 인스턴스인지 확인해야 한다. AI Chat / Tutor의 당시 활성화 조건은 튜터 API 키가 설정돼 있는지였다. 이 작은 조회에 AppService를 주입하면서 AuthModule이 루트 AppModule에도 의존하게 됐다.

웹 서버만 보면 자연스러워 보일 수 있지만, 인증 모듈은 워커와 예약 작업에서도 간접적으로 사용한다. 루트 모듈을 인증 모듈의 import에 넣으면 그 실행 경로에도 루트의 넓은 의존성이 들어온다. 변경 이력은 이 의존과 Worker → Statistics → User → Auth → CourseBot → Statistics 순환을 기동 문제로 연결했다.

여기서 살펴볼 것은 API 키 검사 자체의 복잡도가 아니라 그 검사를 얻기 위해 만들어진 모듈 연결이다. 함수 한 개만 쓰더라도 그 함수를 제공하는 모듈을 통째로 가져오는 비용은 작지 않을 수 있다.

forwardRef보다 먼저 없앨 수 있는 연결을 찾았다

수정은 S2sJwtGuard의 AppService 주입과 AuthModule의 AppModule import를 제거했다. 이미 사용하는 ConfigService에서 필요한 키만 읽도록 바꾼 부분은 다음과 같다.

if (
  audience === 'aitutor' &&
  !this.configService.get<string>('AI_TUTOR_OPENAI_API_KEY')
) {
  throw new ServiceUnavailableException(
    'AI Tutor is not enabled on this instance',
  );
}

루트 모듈 연결에 forwardRef를 붙이는 것은 연결을 없애지 않는다. 설정 하나를 직접 조회하면 그 연결 자체가 필요 없어지고, 워커에서 루트 앱까지 가져오는 이유도 사라진다. 반면 남아 있는 CourseBot→Statistics 연결에는 forwardRef를 적용했다. 모든 순환을 제거한 구조로 바뀐 것은 아니다.

가드에서 키의 존재를 직접 확인하는 비용도 있다. AppService의 활성화 정책이 나중에 다른 조건을 포함하면 두 구현이 어긋날 수 있다. 작은 공통 활성화 provider로 정책을 한곳에 둘 수도 있지만, 이 변경은 기존의 단순 조건을 좁은 설정 조회로 옮긴 방식이다.

인증 단위 테스트와 워커 기동은 다른 확인이다

변경된 가드 테스트는 AppService 모킹을 ConfigService 모킹으로 바꿨다. 튜터 키가 없으면 503, 키가 있으면 정상 JWT 검증으로 진행하는 경우와 필수 claim·헤더·비밀키 조건을 다룬다. 가드의 조건이 유지됐는지 보는 데 적절하지만 워커와 cron의 전체 모듈 그래프를 구성하는 검사는 아니다.

AuthModule의 루트 연결이 제거되고 남은 순환의 처리가 분리돼도, 가드 단위 검사만으로 실제 워커 기동을 확인할 수는 없다. 공유 인증 코드의 수정에서는 요청 동작과 더불어 그 코드를 사용하는 다른 실행 진입점의 기동도 별도로 확인해야 한다.