시도/시군구 국문명 중 DB에 없는 항목이 존재할 수 있는데, "천안군"처럼 지역명이 최신화되어있지 않은 상태이거나 아예 내용이 없는(null) 경우 등의 경우가 존재하였다.
이는 고객 측에서 자료를 넘겨줄때 데이터가 너무 많아서 간헐적으로 발생하는 오류인데, 이 오류로 인해 전체 작업시간이 배로 늘어나는 악순환이 발생하였다.
이 문제를 기존 단순하게 static Map으로 DB에 있는 지역명으로 치환할 수 있는 간단한 로직을 구성하였는데, 이전 분기에서 발생한 오류를 살펴보니 예측이 불가능하고 매핑해주어야 하는 지역명이 계속 신규 발생하였음을 확인하였다.
따라서 변경점이 너무 많아 별도 관리가 필요한데, 영속화하지 않은 항목들에 대해 프로젝트 내 코드레벨에서 유지관리성을 향상할 수 있도록 전략 패턴과 팩토리 패턴을 혼합 도입하게 되었다.
기존 문자열 치환 방식은 하드코딩적인 부분이 너무 많고 OCP원칙을 위배하여, 추가적인 치환 문자열 형태를 매핑해야할 경우 유지관리가 어려웠다.
특히 다른 개발자가 어떻게 해서든 매핑 로직을 이해할 수는 있겠지만, 그 크기가 커졌을때 패턴을 확장하고 변경점을 적용하는 상황에서는 불필요한 소모가 과다하게 발생할 것으로 판단하였다.
구체적으로 살펴보자.
.map(entry -> {
if(SIGUNGU.equals(ExcelColumnEnum.getByKorName(entry.getKey())) || SIDO.equals(ExcelColumnEnum.getByKorName(entry.getKey())))
return this.getFilteredValue(entry.getValue());
else
return entry.getValue();
})
오류가 발생하였던 컬럼(항목)을 if 조건절에 넣고 하드코딩성으로 단순하게 문자열을 치환하도록 구성하였다.
private String getFilteredValue(String originalValue){
if(regionChangingMaps.containsKey(originalValue)){
return regionChangingMaps.get(originalValue);
}
return originalValue;
}
해당 함수는 문자열 치환 형태에 대한 정보가 들어간 Map에서 key값과 일치하는 항목이 생기면 치환을 해주는 형태이다.
private static Map<String, String> regionChangingMaps = new HashMap<>(){
{
put("천안군", "천안시");
put("포천군", "포천시");
put("","NO_DATA");
}
};
이 치환을 해야 할 문자열을 관리하는 Map과 이를 활용하여 조건절에 분기로 처리한 부분 등 수정해야할 부분들이 많고 다른 개발자가 보았을때 알아야 하는 사항도 너무 많았다.
이를 전략패턴과 팩토리 패턴을 혼합하여 유지관리성과 확장성을 개선하였다.
유지관리성, 확장성, 책임분리 등 아키텍칭적인 부분에서 좋은 요소를 모두 확보할 수 있도록 개선방안을 생각해보았고, 그 방안이 전략 패턴과 팩토리 패턴의 혼합 도입이다.
일단 먼저 두 전략을 어떻게 구성하고 적용할지 시나리오를 구성해보았다.
전략 패턴의 목적은 "로직을 교체할 수 있도록 캡슐화"하는 것이고,
팩토리 패턴의 목적은 "적절한 전략 객체를 생성하거나 선택"한다.
따라서 각 전략을 적절하게 혼합하여 팩토리는 전략을 "반환"하는 구조로 진행, 책임 분리와 확장성 향상 등 여러가지 긍정적인 요소를 최대한 얻고자 하였다.
먼저 전략 패턴을 통해 치환 문자열을 얻어야 하므로 전략 패턴 인터페이스를 먼저 구성하였다.
@FunctionalInterface
public interface RegionMapperStrategyInterface {
String replace(String originalValue);
}
그리고 이 인터페이스를 각 시도/시군구 등 오류가 발생하였던 컬럼에 대해 전략 패턴을 구성하였고, 변경점 발생 시 해당 컬럼에 대해서만 일괄적인 변경 및 확장이 이루어질 수 있도록 매핑 항목을 분리하였다.
public class SIGUNGUStrategy implements RegionMapperStrategyInterface {
private static Map<String, String> regionMap = new HashMap<>(){
{
/*
* 시군구명 추가 필요 시 반영
* */
put("천안군", "천안시");
put("포천군", "포천시");
put("","NO_DATA");
}
};
@Override
public String replace(String originalValue) {
return regionMap.getOrDefault(originalValue, originalValue);
}
}
이후 해당 전략을 type(시도명/시군구명 등)을 통해 추출할 수 있는 팩토리 클래스를 구성해주었고, 위에서의 "전략 패턴" 객체 생성을 위임해주도록 구현하였다.
public class RegionMapperFactory {
public static RegionMapperStrategyInterface getRegionMapperStrategy(String type){
return switch(type) {
case "SIDO" -> new SIDOStrategy();
case "SIGUNGU" -> new SIGUNGUStrategy();
default -> new DefaultStrategy();
};
}
}
이를 최종적으로 적용하게 되면 아래와 같다.
.map(entry -> {
/*
* 추가 매핑 필요 시 추가
* */
if(SIGUNGU.equals(ExcelColumnEnum.getByKorName(entry.getKey()))){
return RegionMapperFactory.getRegionMapperStrategy(SIGUNGU).replace(entry.getValue());
}else if(SIDO.equals(ExcelColumnEnum.getByKorName(entry.getKey()))){
return RegionMapperFactory.getRegionMapperStrategy(SIDO).replace(entry.getValue());
}else
return entry.getValue();
})
전략 패턴과 팩토리 패턴의 혼용을 통해
이처럼 두 전략의 혼용을 통해 여러모로 긍정적인 효과를 많이 얻을 수 있었다.
전략패턴을 바로 도입하기에는 항목별로 치환할 문자열을 전략 패턴에서 바로 추출해내야 해서 이 역시 하드코딩적인 요소가 많고 확장에는 불리하였다.
그 중간과정에 팩토리 패턴을 도입하여 직관적이고 관리가 쉬운 형태로 변경하였고, 코드수준에서 매핑 문자열 관리를 가능하게 하여한 곳에서 매핑 로직 책임을 과다하게 위임받는 것을 방지해줄 수 있었다.
Strategy pattern Megazine : https://medium.com/javaguides/why-senior-java-developers-love-the-strategy-pattern-ec77ba77de57