
목차
이번에는 Java 개발 중에 접한 순환참조(Circular Reference) 문제와 그 해결 방법에 대해 이야기하고자 합니다. 제가 경험한 문제 상황을 토대로, 순환참조 문제에 대한 설명과 그 해결 방법, 그리고 이를 통해 배운 점들을 공유하려고 합니다.
1. 순환참조 문제 상황
개발 과정에서 서비스 클래스 간에 순환 참조 문제에 봉착하게 되었습니다. 구체적으로는 업무 서비스인 TestService가 PDF 변환을 담당하는 ConvertorService를 참조하고, 그와 동시에 ConvertorService에서 TestService를 참조하는 상황이었습니다.
순환 참조란, 두 개 이상의 객체가 서로를 참조하여 ‘끝 없는 루프’를 형성하는 것을 말합니다. 이러한 문제가 발생하면, 어플리케이션이 무한 루프에 빠져서 정상적으로 작동하지 못하게 됩니다.
2. 해결 방법: Pub/Sub Design Pattern
순환참조 문제를 해결하기 위해 여러 방안을 고민해 본 결과, ‘Pub/Sub Design Pattern’을 적용하면 될 것 같다는 생각이 들었습니다. Pub/Sub, 즉 Publish/Subscribe 디자인 패턴은 메시지를 발행하는 Publisher와 그 메시지를 구독하는 Subscriber가 서로를 직접 참조하지 않고, 중간에 Broker가 위치하여 메시지를 전달하는 방식입니다. 이를 통해 각 컴포넌트 간의 결합도를 낮추고 확장성을 향상시킬 수 있습니다.
이 패턴을 적용하기 위해, Spring Framework에서 제공하는 ApplicationEventPublisher를 사용하였습니다. ApplicationEventPublisher는 Spring의 이벤트 발행 기능을 담당하는 인터페이스로, 이를 이용하면 특정 이벤트가 발생했음을 다른 컴포넌트에게 알릴 수 있습니다.
3. 코드 구현
-- 1. ConvertorEvent
@Getter
@Builder
public class ConvertorEvent {
private ConvertorDTO convertorDTO;
public static ConvertorEvent doConvertor(ConvertorDTO convertorDTO) {
return ConvertorEvent.builder().ConvertorDTO(convertorDTO).build();
}
}
먼저 ConvertorEvent라는 이벤트 객체를 만들었습니다. 이 객체는 변환할 데이터를 담고 있는 ConvertorDTO를 멤버로 가지며, 이벤트를 생성하는 팩토리 메소드 doConvertor를 제공합니다.
-- 2. ConvertorPublisher
@Component
@RequireArgsConstructor
publid class ConvertorEventPublisher {
private final ApplicationEventPublisher applicationEventPublisher;
public void doConvertor(ConvertorEvent convertorEvent) {
applicationEventPublisher.publishEvent(convertorEvent);
}
}
그 다음에는 이벤트를 발행하는 ConvertorEventPublisher를 만들었습니다. 이 클래스는 ApplicationEventPublisher를 주입 받아 이벤트를 발행하는 doConvertor 메소드를 제공합니다.
-- 3. 이벤트 발생(업무서비스)
@RequiredArgsConstructor
@Service
public class TestServiceImpl implements TestService {
private final ConvertorEventPublisher convertorEventPublisher;
@Override
publid void test() {
ConvertorDTO dto = ConvertorDTO.builder().id("1").build();
ConvertorEvent convert = ConvertorEvent.doConvertor(dto);
convertorEventPublisher.doConvertor(convert);
}
}
그리고 TestService에서는 ConvertorEventPublisher를 주입 받아, 메소드가 호출될 때마다 ConvertorEvent를 발행합니다. 여기서는 ConvertorDTO를 생성하여 이를 바탕으로 ConvertorEvent를 생성하고, 이를 발행하였습니다.
-- 4. PDF ConvertorService(이벤트 구독)
@RequiredArgsConstructor
@Service
public class ConvertorServiceimpl implements ConvertorService {
@EventListener
public void doConvertor(ConvertorEvent convertorEvent) {
///... logic
}
}
마지막으로 ConvertorService에서는 @EventListener 어노테이션을 이용하여 ConvertorEvent를 구독하였습니다. 이벤트가 발행되면 이벤트 핸들러인 doConvertor 메소드가 호출되어 변환 작업을 수행하게 됩니다.
이렇게 함으로써, 서비스 클래스 간에 순환 참조 문제를 해결하였습니다. 이벤트 기반으로 컴포넌트 간에 데이터를 주고 받음으로써, 각 컴포넌트는 서로를 직접 참조하지 않아도 됩니다.
4. 마무리
이번 문제 해결을 통해 Pub/Sub Design Pattern과 ApplicationEventPublisher의 중요성을 더욱 깊이 이해할 수 있었습니다. 이벤트 기반의 통신 방식을 통해 컴포넌트 간의 의존성을 낮추고, 순환 참조와 같은 문제를 효과적으로 해결할 수 있습니다.
또한, 이 방법은 이벤트를 발생시킨 후, 여러 서비스에서 해당 이벤트에 대한 로직을 구현할 때 매우 유용하게 사용될 수 있습니다. 이벤트를 발행하면 관련된 모든 컴포넌트에서 해당 이벤트를 구독하고 동작을 수행하게 됩니다.
이런 점에서 이번 경험은 앞으로의 개발에 많은 도움이 될 것 같습니다. 다음에도 이러한 경험과 배운 점들을 계속 공유하도록 하겠습니다. 감사합니다.