Adventure Time - Finn 3

새소식

Spring

멀티모듈 서비스 레이어 분리

  • -

멀티모듈의 패키지 구성요소에 따라서 서비스 레이어를 분리해서 서비스 계층의 책임을 재분배하는 리팩토링을 거칠려고한다.

 

그 전에는 필요한 의존성만 가져와서 써야하는데 계층의 책임이 맞지 않게 가져다 사용해서 여러 문제가 발생했다..

 

우선 CustomOAuth2UserService 부터 변경해보자.

 

기존 코드는 

package sellyourunhappiness.core.user.application;

import static sellyourunhappiness.core.user.domain.enums.SocialType.*;

import java.util.Collections;
import java.util.Map;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import sellyourunhappiness.core.security.CustomOAuth2User;
import sellyourunhappiness.core.user.domain.User;
import sellyourunhappiness.core.user.domain.enums.SocialType;
import sellyourunhappiness.core.user.infrastructure.UserRepository;

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
	private final UserRepository userRepository;

	@Override
	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
		OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
		OAuth2User oAuth2User = delegate.loadUser(userRequest);

		String Accesstoken = userRequest.getAccessToken().getTokenValue();
		System.out.println("Accesstoken = " + Accesstoken);
		String registrationId = userRequest.getClientRegistration().getRegistrationId();
		SocialType socialType = getSocialType(registrationId);
		String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
		OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName, oAuth2User.getAttributes());
		Map<String, Object> attributes = oAuth2User.getAttributes();

		User createdUser = getUser(extractAttributes, socialType);

		return new CustomOAuth2User(
			Collections.singleton(new SimpleGrantedAuthority(createdUser.getRole().getKey())),
			attributes,
			extractAttributes.getNameAttributeKey(),
			createdUser.getEmail(),
			createdUser.getRole()
		);
	}

	private SocialType getSocialType(String registrationId) {
		if ("google".equals(registrationId)) {
			return GOOGLE;
		}
		throw new IllegalArgumentException("Unknown SocialType: " + registrationId);
	}


	private User getUser(OAuthAttributes attributes, SocialType socialType) {
		User findUser = userRepository.findBySocialTypeAndEmail(socialType,
			attributes.getOauth2UserInfo().getEmail()).orElse(null);

		if(findUser == null) {
			return saveUser(attributes, socialType);
		}
		return findUser;
	}

	private User saveUser(OAuthAttributes attributes, SocialType socialType) {
		User createdUser = attributes.toEntity(socialType, attributes.getOauth2UserInfo());
		return userRepository.save(createdUser);
	}
}

 

위와 같았는데 해당 코드에서는 loadUser만 사용하므로 UserService와 CustomOAuth2UserService로 분리해보자.

 

package sellyourunhappiness.global.dto;

import static sellyourunhappiness.core.user.domain.enums.SocialType.*;

import java.util.Collections;
import java.util.Map;

import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import sellyourunhappiness.api.user.application.UserBroker;
import sellyourunhappiness.core.security.CustomOAuth2User;
import sellyourunhappiness.core.user.application.OAuthAttributes;
import sellyourunhappiness.core.user.domain.User;
import sellyourunhappiness.core.user.domain.enums.SocialType;

@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

	private final UserBroker userBroker;

	@Override
	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
		OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
		OAuth2User oAuth2User = delegate.loadUser(userRequest);

		String registrationId = userRequest.getClientRegistration().getRegistrationId();
		SocialType socialType = getSocialType(registrationId);
		String userNameAttributeName = userRequest.getClientRegistration()
			.getProviderDetails()
			.getUserInfoEndpoint()
			.getUserNameAttributeName();
		OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName,
			oAuth2User.getAttributes());
		Map<String, Object> attributes = oAuth2User.getAttributes();

		User createdUser = userBroker.getUser(extractAttributes, socialType);

		return new CustomOAuth2User(
			Collections.singleton(new SimpleGrantedAuthority(createdUser.getRole().getKey())),
			attributes,
			extractAttributes.getNameAttributeKey(),
			createdUser.getEmail(),
			createdUser.getRole()
		);
	}

	private SocialType getSocialType(String registrationId) {
		if ("google".equals(registrationId)) {
			return GOOGLE;
		}
		throw new IllegalArgumentException("SocialType이 일치하지 않습니다.: " + registrationId);
	}
}

 

 

이건 CustomOAuth2UserService이다. 원래는 하위모듈에서 사용되었지만 로그인과정에서 사용되는 서비스이므로 상위 모듈로 필요한 코드들만 가지고 옮겼다.

 

package sellyourunhappiness.core.user.application;

import java.util.Optional;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import sellyourunhappiness.core.user.domain.User;
import sellyourunhappiness.core.user.domain.enums.SocialType;
import sellyourunhappiness.core.user.infrastructure.UserRepository;

@Service
@RequiredArgsConstructor
public class UserService {
	private final UserRepository userRepository;

	public Optional<User> findByEmail(String email) {
		return userRepository.findByEmail(email);
	}

	public User saveUser(OAuthAttributes attributes, SocialType socialType) {
		User createdUser = attributes.toEntity(socialType, attributes.getOauth2UserInfo());
		return userRepository.save(createdUser);
	}

	public Optional<User> findBySocialTypeAndEmail(SocialType socialType, String email) {
		return userRepository.findBySocialTypeAndEmail(socialType, email);
	}

}



이건 UserService이다. 기존에는 계층이 안나눠져있어서 다른 코드들의 의존성을 받아 사용했지만 변경 후에는 UserRepository에만 의존하게 수정했다. UserController에서 사용하기전에 UserBroker를 추가해 책임을 더 분리했다.

package sellyourunhappiness.api.user.application;

import java.util.Map;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import sellyourunhappiness.core.user.application.JwtService;
import sellyourunhappiness.core.user.application.OAuthAttributes;
import sellyourunhappiness.core.user.application.UserService;
import sellyourunhappiness.core.user.domain.User;
import sellyourunhappiness.core.user.domain.enums.SocialType;

@RequiredArgsConstructor
@Service
public class UserBroker {
	private final UserService userService;
	private final JwtService jwtService;

	public User getUserByEmail(String email) {
		return userService.findByEmail(email)
			.orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다.: " + email));
	}

	public Map<String, String> refreshAccessToken(String refreshToken) {
		return jwtService.refreshAccessToken(refreshToken);
	}

	public User getUser(OAuthAttributes attributes, SocialType socialType) {
		User findUser = userService.findBySocialTypeAndEmail(socialType, attributes.getOauth2UserInfo().getEmail())
			.orElseGet(() -> userService.saveUser(attributes, socialType));
		return findUser;
	}

}



UserBroker는 UserService만 가져와서 사용하기때문에 각 서비스마다 책임이 분리되어서 사용되는 개념이다.

 

이렇게 UserService와 관련된 코드들을 분리해서 나눠봤습니다.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.