반응형
@ SpringBoot
@ JDK 21
의존성 역전에서 인터페이스의 구현체가 여러 개인 경우가 종종 발생합니다.
예를 들어, DBMS로 MySQL과 PostgreSQL을 동시에 사용하는 상황에서
구현체를 선택하고 구별하여 로직을 처리하는 방법을 고민할 필요가 있습니다.
아래에서 대표적인 접근 방법들을 정리했습니다.
1. 비권장 방식: instanceof 및 명시적 타입 변환
if (orderWriter instanceof MySqlOrderAdapter mysql) {
mysql.saveMySqlWay(...);
}
- 단점
- Open-Closed Principle(OCP) 위반
- 인터페이스를 사용한 추상화가 무의미해짐
- 확장 시 if 조건문이 늘어나 유지보수 어려움
2. 권장 방식: 스프링 DI + Qualifier, Profile, Strategy 패턴
A. 정적 선택: @Qualifier 방식
@Component
@Qualifier("mysql")
public class MySqlOrderAdapter implements OrderWriter { ... }
@Component
@Qualifier("postgres")
public class PostgresOrderAdapter implements OrderWriter { ... }
@Service
public class PlaceOrderService {
private final OrderWriter orderWriter;
public PlaceOrderService(@Qualifier("mysql") OrderWriter orderWriter) {
this.orderWriter = orderWriter;
}
}
개발할 때는 명확하고 직관적이어서 개발할 때의 편의성은 있습니다.
다만, 런타임에서 존건 분기가 어렵습니다.
예를 들어, 클라이언트에서 데이터에 따라 분기할 때, 분기를 핸들링할 값이 필요합니다.
다음 조건을 확인해봅시다
B. 런타임 조건 분기: Strategy 패턴 + Factory 활용
- Step 1: 전략 인터페이스 정의
public interface OrderWriter {
void save(Order order);
}
- Step 2: 구현체 전략 키 설정
@Component("mysql")
public class MySqlOrderAdapter implements OrderWriter { ... }
@Component("postgres")
public class PostgresOrderAdapter implements OrderWriter { ... }
- Step 3: Factory로 전략 선택
@Component
public class OrderWriterStrategyFactory {
private final Map<String, OrderWriter> strategies;
public OrderWriterStrategyFactory(List<OrderWriter> implementations) {
this.strategies = implementations.stream()
.collect(Collectors.toMap(
impl -> impl.getClass().getAnnotation(Component.class).value(),
impl -> impl
));
}
public OrderWriter getWriter(String dbType) {
return strategies.get(dbType); // "mysql" or "postgres"
}
}
- Step 4: 서비스에서 선택 적용
@Service
public class PlaceOrderService {
private final OrderWriterStrategyFactory writerFactory;
public PlaceOrderService(OrderWriterStrategyFactory factory) {
this.writerFactory = factory;
}
public void place(OrderCommand command) {
String dbType = determineDbType(); // 조건에 따라 DB 선택
OrderWriter writer = writerFactory.getWriter(dbType);
writer.save(command.toOrder());
}
}
위 방식으로 확장성 및 유연성이 매우 뛰어나 런타임 조건이 필요한 경우에 적합하게 되었습니다.
C. 환경 기반 선택: Spring Profile 사용
@Component
@Profile("mysql")
public class MySqlOrderAdapter implements OrderWriter { ... }
@Component
@Profile("postgres")
public class PostgresOrderAdapter implements OrderWriter { ... }
지금까지, [A]는 개발자만 생각한 경우이고, [B] 방식은 사용자에 더 초점을 맞춘 개발방식입니다.
여기서 더 나아가 [C] 방법을 사용하면 환경에 따라 구현체가 자동 선택되어 관리되는 방식을 사용할 수 있습니다.
다만, 런타임에서 조건 변경 불가능하여 실행 환경에 따라 결정됨
선택 가이드
방식 OOP 원칙 적합도 권장 상황
instanceof | ❌ OCP 위반, 추상화 깨짐 | 지양 |
@Qualifier | ✅ 명확성, 정적 선택 | 간단한 정적 선택 상황 |
전략패턴 + Factory | ✅ 확장성 및 유연성 최적 | 동적 런타임 조건 분기 |
Spring Profile | ✅ 환경 기반 구현체 선택 | 운영 환경 분리 필요 시 |
최종 결론
현실적이고 유연한 구현체 선택을 위해서는 전략 패턴과 Factory 조합이 가장 권장되며, 환경에 따른 선택이 필요하다면 Spring Profile을 활용하는 것이 좋습니다.
반응형
'Java' 카테고리의 다른 글
[Java] record는 뭐야? (1) | 2025.03.25 |
---|---|
REST API에서 요청 DTO를 매번 만들어야 하나? (0) | 2025.03.25 |
REST API 설계, GET 방식과 보안이슈 (0) | 2025.03.25 |
Java 버전별 변천사 ( Java 6 ~ ) (2) | 2025.03.15 |
자바의 특징 (0) | 2025.03.13 |