Service, ServiceImpl#
Spring 프로젝트를 하면 종종 관례적으로 Service를 interface로 기능 명세를 한 뒤 ServiceImpl에 기능을
구현하게 되는 Factory Pattern을 사용하게 됩니다.
interface는 기능을 추상화하여 클래스간 결합도를 낮추어 주고, 협업 시 업무분담도 용이합니다.
게임으로 예를 들면 스타크래프트에서 모든 유닛의 기본적인 특성 HP, 이동하기를 interface로
기능만 명시하고 각각 분업하여 유닛에 대한 HP나 이동속도를 구현할 수 있습니다.
하지만 일반적인 Spring 웹프로젝트에서는 Service interface는 1:1 구조인 경우가 많습니다. 만약 확장성을
고려한 1:N의 경우에는 interface로 가는 것이 좋지만 너무 막연한 경우에는 그냥 class로 생성 후 추후 시나리오
변경 또는 로직상 확장성이 필요한 경우 interface로 변경하는 것이 좋다고 생각합니다.
그렇다면 interface를 사용하는 경우는 어떤 경우에 사용해야할까요??
보통 하나의 기능에서 여러 곳으로 파생되는 것을 interface로 나누는게 좋을 것 같습니다.
예를 들어 소셜로그인, 패스워드 변경(개인정보수정, 패스워드찾기), 아이디 찾기(휴대폰 인증, 이메일 인증, 기타 등등), 카드 결제(카드사 별 결제 취소),
게임(게임별 플레이, 종료)등이 있습니다.
공통적으로 쓰이는 기능을하나의 기능에서 충분히 확장될 수 있는 경우 interface를 사용하는 것이 좋습니다.
로그인의 기능을 만들 때 Spring Security의 OAuth2
를 이용하여 보통
기능 구현을 합니다.
그렇지만 oauth2를 사용하지 않고 기능을 구현하는 경우를 샘플 코드
를 이용하여 설명해보겠습니다.
먼저 로그인 유형에 대한 정의를 합니다.
1
2
3
4
5
6
7
| public enum AuthProvider {
local,
google,
kakao,
github,
naver
}
|
그런 뒤 LoginService
로 인터페이스를 생성합니다.
1
2
3
| public interface LoginService {
LoginDto.Response login(LoginDto.Request dto);
}
|
그리고 소셜에 따라 로그인 서비스에 맞게 구현 로직을 추가합니다. 이제 로그인 요청이 들어오면 해당
소셜 로그인이 동작하도록 LoginFactory
클래스를 생성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public LoginService getLoginType(LoginDto.Request dto) {
if (dto == null) {
return null;
}
if (dto.getAuthProvider().equals(AuthProvider.github)) {
return new LoginGithubService();
} else if (dto.getAuthProvider().equals(AuthProvider.google)) {
return new LoginGoogleService();
} else if (dto.getAuthProvider().equals(AuthProvider.kakao)) {
return new LoginKakaoService();
} else if (dto.getAuthProvider().equals(AuthProvider.naver)) {
return new LoginGoogleService();
} else if (dto.getAuthProvider().equals(AuthProvider.local)) {
return new LoginLocalService();
}
return null;
}
|
안의 내용들은 구현하지 않고 해당 로직을 타는지 print만 했습니다.
위와 같이 구현하면 dto 요청에서 소셜 로그인 정보와 일치하는 Service의 로직이 동작합니다.
Test코드로 확인하기 위해 정상적으로 하는지 확인해봅니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| @Test
void 로그인_타입_테스트() {
LoginFactory loginFactory = new LoginFactory();
LoginDto.Request request = new LoginDto.Request();
LoginService loginService = new LoginLocalService();
request.setAuthProvider(AuthProvider.github);
loginService = loginFactory.getLoginType(request);
loginService.login(request);
request.setAuthProvider(AuthProvider.google);
loginService = loginFactory.getLoginType(request);
loginService.login(request);
request.setAuthProvider(AuthProvider.kakao);
loginService = loginFactory.getLoginType(request);
loginService.login(request);
request.setAuthProvider(AuthProvider.naver);
loginService = loginFactory.getLoginType(request);
loginService.login(request);
request.setAuthProvider(AuthProvider.local);
loginService = loginFactory.getLoginType(request);
loginService.login(request);
}
|
테스트 코드를 통해 service가 정상적으로 동작하는지 확인 할 수 있습니다.
Reference#
- https://www.manty.co.kr/bbs/detail/develop?id=13
- https://itzjamie96.github.io/2021/01/24/spring-service-and-serviceimpl/
- https://cheese10yun.github.io/spring-oop-04/
- https://blog.jiniworld.me/55
- https://www.tutorialspoint.com/design_pattern/factory_pattern.htm