비슷한 동작을 추상화하기

Kim Dong Kyun·2024년 9월 23일
0

개요

이전 회사에서, 상품권 교환소라는 도메인을 Spring Core 4.x 버전에서 SpringBoot 3.x 버전으로 마이그레이션 할 일이 있었다. 이 때 몇 가지 문제상황과, 바꾸고 싶었던 점이 있었는데, 잊기 전에 글로 남겨보려고 한다.


기존 레거시 예시

@RequiredArgsConstructor
@Service
public class LegacyService {

    private final PartnerOne partnerOne;

    private final PartnerTwo partnerTwo;

    // ...

    // 조회 (예시)
    public String retrieve(Map<String, Object> objectMap){ //
        // ...
        String identifier = (String) objectMap.get("identifier");
        switch (identifier){
            case "partnerOne" : return partnerTwo.retrieve();
            case "partnerTwo" : return partnerOne.retrieve();
            default: throw new RuntimeException("custom exception");
        }
        // ...
    }
    
    // 승인 ...
    
    // 취소 ...

기존 소스(레거시)에서 개선하고 싶었던 불편함들은 아래와 같다.

  1. 제휴사가 늘어나게 되면, (혹은 삭제되면) 산재되어 있는 switch-case 문을 수정해야 한다.
  2. Map<String, Object> 형태의 데이터는 실제 디버그 전에는 어떤 데이터가 들어가있는지 흐름을 파악하기가 어렵다.
  3. 분기문의 depth 가 너무 길거나, 한 매서드가 너무 많은 일을 해서 소스 파악이 힘들다.

해결 포인트

  1. 코드단에서 입출력되는 데이터를 파악 가능하도록 변경 : 컨트롤러 패러미터 및 리턴 타입을 명확한 이름과 타입이 있는 데이터로 변경.
  • 이를 통해 어떤 용도로 어떤 데이터가 입출력되는지 코드단에서 파악이 가능
  1. 제휴사별 공통 동작을 인터페이스로 추상화.
  • 각 제휴사는 이 인터페이스를 구현하여 고유의 동작들(retrieve, approve, cancel)을 정의하고, getIdentifier() 메서드를 통해 각 제휴사를 식별할 수 있도록 변경

  • 또한, 팩토리 패턴을 도입하여 switch-case 문을 대신하여 제휴사를 식별하고 적절한 서비스 클래스를 반환하는 구조로 변경.

  • 이러한 구조 변경을 통해 유연성과 확장성을 개선

개선한 서비스 예시

  • 인터페이스를 이용하여 파트너사가 공통으로 가지는 로직을 추상화
public interface PartnerService {
    String retrieve();
    String approve();
    String cancel();
    Identifier getIdentifier();
}

// getIdentifier() 매서드를 통해 파트너사를 구분하도록 각각에 맞는 enum타입 리턴
  • 추상화된 List형태의 파트너사 서비스 Bean을 리스트로 주입받아, Map<Enum, Service> 형태로 저장 및 적절한 빈 분배
@Component
@RequiredArgsConstructor
public class PartnerServiceFactory {

    private final Map<Identifier, PartnerService> partnerServiceBeans = new HashMap<>();

    public PartnerServiceFactory(List<PartnerService> partnerServiceList) { // List 형태의 파트너서비스 beans 주입
        partnerServiceList.forEach(each -> partnerServiceBeans.put(each.getIdentifier(), each));
    }

    public PartnerService getPartnerService(Identifier identifier) {
        return partnerServiceBeans.get(identifier);
    }
}
  • 조회, 승인, 취소 등 API 별 서비스에서는 PartnerService 가 어떤 구현체를 사용하는지 알지 않고도 사용이 가능 (따라서 변경, 추가 등이 용이함)
  • Map<String, Object> 형태의 Request/Response 데이터를 DTO로 변경
@Service
@RequiredArgsConstructor
public class RetrieveService {
    
    private final PartnerServiceFactory partnerServiceFactory;
    
    public String retrieve(RequestDto requestDto){
        PartnerService partnerService = partnerServiceFactory.getPartnerService(requestDto.identifier());
        return partnerService.retrieve();
    }
}

개선함으로써 얻어낸 효과

  1. 제휴사가 추가, 수정, 삭제 될 때마다 switch-case 부분을 하나하나 찾아가며 변경 할 필요가 없다.
  2. Map<String, Object> 형태의 데이터를 DTO로 바꿔서, 어떤 요청과 응답 데이터들이 필요 한 지 코드 상에서 확인이 가능하다.
  3. 매서드가 적절히 나누어져서 로직의 흐름을 파악하는 데에 유리하다.

0개의 댓글