일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- db
- Junit 5
- 자바
- Java
- backend
- ObjectMapper
- mapstruct
- enumSet
- Test Doulbe
- Service 계층 테스트
- 테크쇼
- 인프콘2023
- RequestBody
- MySQL
- Server
- Test code
- proxyFactory
- 일상
- softeer
- JPA
- ExceptionResolver
- Coputer Science
- Test
- Spring
- modelmapper
- OS
- 소프티어
- 공룡책
- FCM
- JPQL
- Today
- Total
공부내용공유
Spring에서 Factory 패턴 사용하기 본문
서론
프로젝트를 진행하면서 사용자가 원하는 종류에 따라서 기능을 실행해야 하는 요구사항이 있었다.
간단하게 예시를 들자면 Kakao, naver, google, iphone 과 같은 써드파티 로그인 기능들을 구현해야 하는 상황이라고 생각할 수 있다.
위와 같은 로그인 기능을 예시로 프로젝트에서 어떤 식으로 팩토리 패턴을 적용했는지 글을 작성할 예정이다.
본론
일단 팩토리 패턴을 적용하지 않은 간단한 예시 코드를 만들어보자. (각 써드파티 로그인 별로 인증 로직이 조금 다르다고 가정하겠다. )
// controller
public Response login (@RequestBody loginRequest) {
Boolean success = userService.login(loginRequest);
return Response.of(success);
}
//service
public Boolean login(LoginRequest loginRequest){
LoginType loginType = loginRequest.getLoginType();
String id = loginRequest.getId();
String password = loginRequest.getPassword();
switch (loginType) {
case APPLE -> {
String key = getApplekey();
AppleAuth appleAuth = veryDifficultAppleAuthentication();
applAuth.login(id,password);
}
case NAVER-> {
Connection con = getNaverConnection();
NaverAuth na = con.getAuthProvider();
na.login(id,password);
}
case GOOGLE-> {
GoogleAuth googleAuth = getGoogleAuth();
GoogleLoginManager googleLoginManager = googleAuth.credit(googleKey);
googleLoginManager.login(id,passowrd);
}
case KAKAO-> {
KakaoTalk kakaoTalk = getKakaoTalk();
KaKaoAuth kakaoAuth = kakoTalk.send("give me auth");
kakaoAuth.login(id,password);
}
}
return true;
}
진짜 아무렇게나 만든 예시이니 로그인 타입들이 있고 각기 로그인 로직들이 다르구나 정도로만 생각하자.
이렇게 사용자의 요청에 따라 각기 다른 로직을 갖고 있는 API를 만드려면 가장 단순하게 생각했을 때는 switch문을 통하여 분기처리를 해주는 것이다.
다만 현재 코드를 보면 불편함을 감출 수 없을 것이다. 정확히 무엇이 문제일까?
- SRP 원칙 위배
- 위 메서드는 각 기능 별 메서드를 전부 가지고 있고 사용자의 요청에 따라 로직을 호출해주는 분기처리 로직도 가지고 있다.
- OCP 원칙 위배
- 만약 특정 도메인의 로그인 방식이 수정된다면 바뀌지 않은 도메인들도 변경된다.
- 로그인 도메인을 추가할 때도 switch문에 분기 처리를 추가로 함에 따라 메서드에 변경이 일어난다.
이러한 SOLID 원칙에 위배되는 코드를 어떻게 개선할 수 있을까?
개선된 코드 (팩토리 패턴 적용)
// 로그인 기능을 가진 인터페이스
public interface Login{
void execute(String id, String password);
LoginType getType();
}
먼저 로그인 로직들을 추상화 시켜줄 인터페이스를 선언한다.
- login 메서드는 도메인에 따라 맞춰서 구현할 메서드이다.
- getType 은 추후에 설명하겠다.
//인터페이스를 구현한 각 도메인별 로그인 클래스
@Component
public class NaverLogin implements Login{
@Override
public void execute(String id, String password){
Connection con = getNaverConnection();
NaverAuth na = con.getAuthProvider();
na.login(id,password);
}
@Override
public LoginType getType(){ return LoginType.NAVER }
}
@Component
public class KakaoLogin implements Login{
@Override
public void execute(String id, String password){
KakaoTalk kakaoTalk = getKakaoTalk();
KaKaoAuth kakaoAuth = kakoTalk.send("give me auth");
kakaoAuth.login(id,password);
}
@Override
public LoginType getType(){ return LoginType.KAKAO }
}
@Component
public class GoogleLogin implements Login{
@Override
public void execute(String id, String password){
GoogleAuth googleAuth = getGoogleAuth();
GoogleLoginManager googleLoginManager = googleAuth.credit(googleKey);
googleLoginManager.login(id,passowrd);
}
@Override
public LoginType getType(){ return LoginType.GOOGLE }
}
login 인터페이스를 구현한 각 클래스이다.
로그인 메서드에는 도메인별 로직이 들어가 있고 getType 에서는 각 도메인별 LoginType(Enum)을 반환하고 있다.
// 분기 처리를 해주는 클래스
@Component
public class LoginClassifier{
private final Map<LoginType, Login> loginMap;
public LoginClassifier(List<Login> logins) {
HashMap<LoginType, Login> hashMap = new HashMap<>();
for (Login login : logins ) {
hashMap.put(login.getType(), login);
}
this.judgeMap = hashMap;
}
public LoginType getLogin(LoginType loginType) {
return judgeMap.get(loginType);
}
}
위 클래스는 각 도메인별 로그인 클래스들을 저장하고 클라이언트에게 전달해 주는 (분기 처리를 해주는) 클래스이다.
생성자 주입시 인터페이스를 생성자에 넣음으로 해당 인터페이스를 구현하는 모든 bean 들이 주입되고 LoginType 과 함께 Map에 들어가게된다.
이러한 classifier 클래스를 이용해 클라이언트에서는
private final LoginClassifier loginClassifier;
public Boolean login(LoginRequest loginRequest){
LoginType loginType = loginRequest.getLoginType();
String id = loginRequest.getId();
String password = loginRequest.getPassword();
Login login = loginClassifier.getLogin(loginType);
login.execute(id,password);
}
이런 식으로 분기처리를 안해도 되고 각 도메인별 로그인 로직을 알 필요 없게된다.
결론
이런식으로 팩토리 패턴을 적용했을 때
- 특정 도메인 로그인 방식이 변경되었을 때 해당 클래스만 변경된다.
- 도메인이 추가되었을 때 다른 코드의 변경 없이 해당 도메인 클래스 하나만 추가하면 된다.
- 각 클래스별로 로그인, 분기처리등 명확한 단일 책임을 가지고 있다.
등등 좀 더 객체지향적인 코드로 바꿀 수 있었다.