비디오 스트리밍 또는 SDI 출력 기능을 구현할 때, UDP나 SRT와 같이 서로 다른 전송 프로토콜을 지원해야 하는 경우가 많습니다. 각 프로토콜은 고유한 네트워크 설정과 초기화 로직을 요구하며, 향후 새로운 프로토콜이 추가될 가능성을 고려해야 합니다. 이러한 상황에서 단순한 조건문(if-else)은 코드의 유지보수성을 심각하게 떨어뜨립니다. 전략 패턴(Strategy Pattern)을 적용하여 프로토콜별 로직을 분리할 수 있지만, 컨텍스트에서 적절한 전략을 선택하는 과정에서 여전히 조건문이 발생하는 한계가 있습니다. 이를 해결하기 위해 팩토리 패턴(Factory Pattern)을 결합하여 조건문을 완전히 제거하고 확장 가능한 구조를 구축하는 방법을 살펴봅니다.
전략 인터페이스 설계
먼저 각 프로토콜별 동작을 추상화하는 인터페이스를 정의합니다. 팩토리 클래스에서 조건문 없이 자동으로 전략을 매핑할 수 있도록, 자신이 지원하는 프로토콜 타입을 반환하는 메서드를 인터페이스에 포함시킵니다.
public interface StreamOutputStrategy {
/**
* 현재 전략이 처리할 수 있는 프로토콜 타입을 반환합니다.
*/
ProtocolType getSupportedProtocol();
/**
* 네트워크 경로를 설정하고 스트림 출력을 초기화합니다.
*/
void provisionStream(NetworkRoute route, StreamCreationRequest request);
}
프로토콜별 전략 구현
인터페이스를 구현하여 각 프로토콜에 맞는 구체적인 로직을 작성합니다. Spring의 @Component 어노테이션을 사용하여 빈으로 등록합니다.
@Component
public class UdpStreamOutput implements StreamOutputStrategy {
@Override
public ProtocolType getSupportedProtocol() {
return ProtocolType.UDP;
}
@Override
public void provisionStream(NetworkRoute route, StreamCreationRequest request) {
// UDP 내부망 스트림 초기화 로직 (IP 바인딩, 버퍼 설정 등)
}
}
@Component
public class SrtStreamOutput implements StreamOutputStrategy {
@Override
public ProtocolType getSupportedProtocol() {
return ProtocolType.SRT;
}
@Override
public void provisionStream(NetworkRoute route, StreamCreationRequest request) {
// SRT 외부망 스트림 초기화 로직 (포트 할당, 지연시간, 암호화, TTL 설정 등)
}
}
프로토콜 타입 정의
프로토콜을 안전하게 관리하고 문자열을 열거형으로 변환하기 위한 Enum 클래스를 작성합니다.
public enum ProtocolType {
UDP("udp"),
SRT("srt");
private final String value;
ProtocolType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static ProtocolType fromValue(String value) {
for (ProtocolType type : values()) {
if (type.value.equalsIgnoreCase(value)) {
return type;
}
}
throw new IllegalArgumentException("지원하지 않는 프로토콜 타입: " + value);
}
}
팩토리 패턴을 통한 전략 관리
기존의 if-else나 switch 문을 사용하여 전략을 선택하는 방식 대신, Spring의 의존성 주입 기능을 활용하여 모든 전략 implementation을 리스트로 주입받고, 이를 Map으로 변환하여 O(1) 시간 복잡도로 전략을 조회하는 팩토리를 구현합니다. 이 방식은 새로운 프로토콜이 추가되어도 팩토리 코드를 수정할 필요가 없는 OCP(개방-폐쇄 원칙)를 완벽하게 준수합니다.
@Component
public class StreamStrategyFactory {
private final Map<ProtocolType, StreamOutputStrategy> strategyMap;
public StreamStrategyFactory(List<StreamOutputStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(
StreamOutputStrategy::getSupportedProtocol,
Function.identity()
));
}
public StreamOutputStrategy getStrategy(String protocolValue) {
ProtocolType type = ProtocolType.fromValue(protocolValue);
return Optional.ofNullable(strategyMap.get(type))
.orElseThrow(() -> new IllegalStateException("등록되지 않은 스트림 전략: " + type));
}
}
서비스 계층에서의 호출
최종적으로 비즈니스 로직을 처리하는 서비스 계층에서는 팩토리를 통해 적절한 전략을 가져와 실행하기만 하면 됩니다. 이로 인해 서비스 코드는 프로토콜의 종류에 대해 완전히 독립적으로 동작하게 됩니다.
@Service
@RequiredArgsConstructor
public class StreamProvisioningService {
private final StreamStrategyFactory strategyFactory;
public void createStreamDestination(NetworkRoute route, StreamCreationRequest request) {
String protocol = request.getProtocol();
StreamOutputStrategy strategy = strategyFactory.getStrategy(protocol);
strategy.provisionStream(route, request);
}
}