본문 바로가기

프로젝트/고민

소셜로그인에 팩토리 메소드 패턴 적용하기

고민사항

OAuth를 통해 로그인을 구현할 때 해당 OAuth 로그인을 제공하는 api의 주체가 어느 회사인지 판단하는 로직을 Dto 객체에서 하고 있다. 해당 역할을 Dto 객체가 하는게 맞는지에 대한 의문과 함께 굳이 이 로직을 if-else문을 사용해야 할까? 라는 고민을 하였다. 결론적으로, 팩토리 메소드 패턴을 적용하는게 알맞다는 생각을 갖게 돼 해당 디자인 패턴으로 리팩토링 하였다.

 

기존의 코드

@Getter
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;
    private String picture;

    @Builder
    public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email, String picture) {
        this.attributes = attributes;
        this.nameAttributeKey = nameAttributeKey;
        this.name = name;
        this.email = email;
        this.picture = picture;
    }

    public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String, Object> attributes) {
        if (registrationId.equals("naver")) {
            return ofNaver("id", attributes);
        } else if (registrationId.equals("kakao")){
            return ofKakao("id", attributes);
        }
        return ofGoogle(userNameAttributeName, attributes);
    }

    private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> kakao_account = (Map<String, Object>) attributes.get("kakao_account");
        Map<String, Object> profile = (Map<String, Object>) kakao_account.get("profile");
        return OAuthAttributes.builder()
                .name((String) profile.get("nickname"))
                .email((String) kakao_account.get("email"))
                .picture((String) profile.get("profile_image_url"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    private static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> response = (Map<String, Object>) attributes.get("response");

        return OAuthAttributes.builder()
                .name((String) response.get("name"))
                .email((String) response.get("email"))
                .picture((String) response.get("profile_image"))
                .attributes(response)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
        return OAuthAttributes.builder()
                .name((String) attributes.get("name"))
                .email((String) attributes.get("email"))
                .picture((String) attributes.get("picture"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    public User toEntity() {
        return User.builder()
                .name(name)
                .email(email)
                .picture(picture)
                .role(Role.USER)
                .build();
    }
}

위에서 설명했다 싶이 Dto에서 너무 많은 역할을 수행하고 있다. OAuth API가 naver, kakao, google인지 판단하는 작업과 함께 User 클래스로 엔티티를 변화시키는 작업까지 하고 있다. 또한 페이스북, 마이크로소프트 등 추가적인 소셜 로그인 기능을 추가하게 된다면 해당 dto의 로직을 수정하는 작업을 해야 한다. 이러한 문제점을 개선하고자 다음과 같은 코드를 작성하엿다.

 

개선후 코드

1. 먼저 소셜 로그인을 제공해주는 팩토리를 enum 클래스로 만들었다. 이때 Supplier 클래스는 Java 8에서 추가된 인터페이스의 구현체를 반환해주는 클래스이다.

 

 

2. 이후 각 소셜로그인 API에 따라 다른 객체를 반환시켜주기 위한 공통 메소드를 인터페이스로 추상화 하였다.

 

3. 이후 각 api별 구현체를 작성했다. 다음은 구글 로그인에 대한 구현체이다.

 

 

4. 이후 dto 객체는 다음과 같이 변화하였다.

 

 

이제 어떠한 API가 추가되더라도 dto의 코드를 변경하는 것이 아닌 구현체 클래스를 생성하고 팩토리에 추가해주기만 하면 되는 코드가 되었다.

 

마무리

팩토리 메소드 패턴을 적용한 결과, 객체 생성 책임이 분리되어 확장성이 향상되었다는 점을 뚜렷하게 느꼈다. 그러나 관리해야 할 클래스가 증가함에 따라 오버 엔지니어링이 발생할 수 있다는 우려도 생겼다. 여러 디자인 패턴을 알고 있다 하더라도, 상황에 적합한 코드를 작성하는 것이 중요하다는 인식을 갖게 되었다.