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

최대한·2021년 8월 16일
1
post-custom-banner

서론


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

개발자마다 구현하는 방법은 여러가지가 있겠지만, 이전 글인 [스프링] 일반 클래스에서 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!
post-custom-banner

0개의 댓글