[스프링] Provider 를 이용하여 Enum Type 에 맞는 서비스 객체 반환하기

최대한·2021년 8월 16일

서론


개발을 하다보면 로직을 추상화한 뒤, 특정 타입에 따라 전략패턴을 적용하고 해당 타입에 맞는 서비스 객체를 이용하는 로직을 짜는 경우가 많다.

개발자마다 구현하는 방법은 여러가지가 있겠지만, 이전 글인 [스프링] 일반 클래스에서 Bean 객체에 접근하기 + Environment 를 이용해 property 쉽게 가져오기 (v2) 를 이용하여 구현하는 방법을 공유해보고자 한다.

본론


Tech Stack

JDK 8
Spring boot 2.5.0

SocialMedia 라는 Interface 를 구현하는 클래스

  • Facebook
  • Instargram

이 있다.

1. 구현부

1.1 SocialMedia.java

public interface SocialMedia {
    String post(String content);
}

1.2 Facebook.java

@Slf4j
@Service
public class Facebook implements SocialMedia{
    @Override
    public String post(String content) {
        log.info("This is a post for Facebook, :: {}", content);
        return "Posted on Facebook : " + content;
    }
}

1.3 Instagram.java

@Slf4j
@Service
public class Instagram implements SocialMedia{
    @Override
    public String post(String content) {
        log.info("This is a post for Instagram, :: {}", content);
        return "Posted on Instagram : " + content;
    }
}




위의 클래스들을 요청하는 type 에 맞추어 Enum, BeanContext, ServiceProvider 로 간단히 구현해보고자 한다.

1.4 SocialMediaType.java

public enum SocialMediaType {
    FACEBOOK(Facebook.class),
    INSTAGRAM(Instagram.class),
    ;

    @Getter
    private final Class<? extends SocialMedia> clazz;

    SocialMediaType(Class<? extends SocialMedia> clazz) {
        this.clazz = clazz;
    }

}

SocialMedia 에 맞는 Enum 타입을 선언해주고, 멤버변수로는 SocialMedia 인터페이스를 구현한 클래스를 지정해준다.


1.5 SocialMediaServiceProvider.java

public class SocialMediaServiceProvider {

    private static Map<String, Class<? extends SocialMedia>> socialMediaMap = new HashMap<>();

    static {
        for (SocialMediaType value : SocialMediaType.values()) {
            socialMediaMap.put(value.toString().toLowerCase(), value.getClazz());
        }
    }

    public static SocialMedia service(String socialMediaType){
        Class<? extends SocialMedia> serviceClass = socialMediaMap.get(socialMediaType.toLowerCase());
        if (serviceClass != null){
            return BeanContext.get(serviceClass);
        }
        throw new IllegalArgumentException("No proper type for the given media type : " + socialMediaType);
    }

}

SocialMediaServiceProvider 클래스는 외부에서 온 socialMediaType :String 에 맞게 서비스 객체를 제공해주는 역할을 한다. ( BeanContext 클래스는 이전 글 참조)



1.6 SocialMediaController.java

@Slf4j
@RestController
@RequestMapping("/api/v1/social-media")
public class SocialMediaController {

    @GetMapping("/{type}")
    public String post(@PathVariable String type, @RequestParam String content){
        log.info("The content of the post : {}", content);
        SocialMedia service = SocialMediaServiceProvider.service(type);
        return service.post(content);
    }

}

type 을 기반으로 SocialMediaService 객체를 반환 받은 뒤 post(content) 메소드를 수행한다. (RequestBody 로 받는 것이 맞으나, 편의상 requestParam 으로 전달했다.)


2. 테스트


  1. [GET] http://localhost:8080/api/v1/social-media/facebook?content=내용

  1. [GET] http://localhost:8080/api/v1/social-media/instagram?content=인스타사진올리기

  2. [GET] http://localhost:8080/api/v1/social-media/tiktok?content=잘못된요청



결론


회사에서 이에 대한 코드를 구현한 것을 보고 조금 더 나은 방법이 있지 않을까 하는 생각에서 만들어본 코드인데 Provider 는 우선 디자인 패턴이 아니고 안티 패턴이라는 말도 있다.
또한 ProviderFactory 패턴처럼 새로운 객체를 반환하는 클래스인 것으로 알고 있는데, 필자는 프로바이더라는 것에 대해 정확히 알고 구현한 것이 아니어서, 조금 더 살펴볼 필요가 있어보인다.

이번 포스트는 디자인 패턴에 대해 고찰/응용해본 것은 아니지만 그래도 디자인 패턴에 대해 생각해보자면, 디자인 패턴은 수학으로 치면 공식과도 같은 느낌이다. 공식을 몰라도 문제를 풀 수는 있지만, 알면 조금 더 쉽고, 우아하게 풀 수 있듯 프로그래밍 세계에서도 디자인 패턴을 알고 적용했을 때 특정 문제를 좀 더 수월하게 해결할 수 있을 수 있고, 후에 유지보수 / 확장성 차원에서도 많은 이점을 가져다주기 때문에 조만간 날 잡고 디자인 패턴에 대해서 깊게 학습해야 할것 같다.

profile
Awesome Dev!

0개의 댓글