고딩왕 코범석

Service Layer 분리하기! 본문

Language & Framework/Spring

Service Layer 분리하기!

고딩왕 코범석 2021. 5. 5. 14:15
반응형

안녕하세요! 이번 포스팅에서는 제가 어떻게 서비스 레이어를 분리했는지 포스팅해보았습니다. 그럼 바로 가보시죠!


기존의 코드


우선, 제 프로젝트의 서비스 레이어에서는 흔히 스프링 예제에 돌아다니는 것처럼 DomainService, DomainServiceImpl로 명명했습니다. 그에 따라 모든 비즈니스 로직들이 서비스 인터페이스에 모두 담겨져있었고 그걸 구현해서 로직을 작성했습니다.

// Account > 회원 도메인
// 모든 비즈니스 로직들이 다 담겨있는 구조
public interface AccountService {
    SignUp.Response signUp(SignUp.CommonRequest signUpRequestDto);

    Account saveAccount(SignUp.CommonRequest signUpRequestDto);

    Map<String, String> login(Login.Common login) throws Exception;

    void saveSignUpDetail(SignUp.DetailRequest req);

    String authenticateEmailToken(String email, String token);

    void quit(LoginUser loginUser);

    Update.ProfileResponse getProfile(LoginUser loginUser);

    void updateProfile(LoginUser loginUser, Update.ProfileRequest request);

    void updatePassword(LoginUser loginUser, Update.PasswordRequest request);

    Update.Interest getAccountInterests(LoginUser loginUser);

    void updateAccountInterests(LoginUser loginUser, Update.Interest request);

    Update.MainActivityZone getAccountMainActivityZones(LoginUser loginUser);

    void updateAccountMainActivityZones(LoginUser loginUser, Update.MainActivityZone request);

    Map<String, String> getAccountNickname(LoginUser loginUser);

    void updateAccountNickname(LoginUser loginUser, Update.NicknameRequest request);

    Update.Notification getAccountNotification(LoginUser loginUser);

    void updateAccountNotification(LoginUser loginUser, Update.Notification request);
}

이렇게 굉장히 장황하게 인터페이스를 작성했습니다.. 하나의 클래스는 하나의 책임만 가져야 하는 SRP 객체지향 설계원칙에 어긋났죠.. 그래서 이번 기회에 리팩토링을 진행해보았습니다.

비즈니스 로직들에 대한 설명


회원가입과 로그인


회원가입과 로그인은 일반의 경우, OAuth의 경우 두 가지로 나뉘게 됩니다. SignUpService, LoginService 인터페이스를 나눈 다음에 일반 회원가입, OAuth 회원가입 두 가지를 구현했습니다.


image


또한 일반 회원가입의 경우 메일 인증을 해야하는데 로직을 SignUpService 인터페이스에 넣을지 고민했었습니다. 메일 인증이 분명 일반 회원가입시에 진행되는 건 맞지만, 성격은 다르다고 생각해서 AuthenticationEmailService 인터페이스를 따로 만들어서 분리했습니다.


로그인의 경우도 회원가입 처럼 OAuth와 일반 로그인이 나뉩니다. 근데 회원가입 로직과는 다르게 비슷한 요소가 많더라구요! 그래서 저의 방법은


image


이렇게 작성했습니다. LoginService 인터페이스에 다음과 같이


image

image


로그인 요청 객체가 일반인지, OAuth인지 타입을 체크해주는 로직을 작성하여 구현했습니다. 또한 로그인 성공 이후 jwt를 만들어 헤더로 반환해주는 것과 로그인을 했을 때, 저희 프로젝트에서 정한 Response-Body 부분을 만들어 응답해주어야 하는데 이 부분은


image

LoginService 구현체에 LoginServiceUtil 객체 의존성을 주입받아서 처리해주었습니다.


회원가입 후 회원의 상세정보 저장


image


Account(회원)는 여러 개의 Zone(지역, ex) 서울특별시/강남구, 경기도/수원시 등등..)과 여러개의 Tag(관심분야, ex) 개발/웹개발, 프로그래밍언어/java 등등..)를 가질 수 있고 하나의 Zone, Tag도 여러 개의 Account 객체를 가질 수 있어서 일대다 다대일 매핑테이블로 분리했습니다. 다대다 관계를 풀어주는 객체의 이름을 단순하게 AccountZone, AccountTag로 작명하지 않고 의미있게 주 활동지역(MainActivityZone), 관심사(Interest)로 작명했습니다.


한 회원이 상세 정보를 저장할 떄, 여러개의 주 활동지역, 관심사를 저장하게 될텐데, 이 때 저는


image


SignUpDetailService(회원가입 상세정보 저장을 위한 객체)에서 입력 받은 상세 정보들에 대한 객체(Zone, Tag들)를 가져와 Account에 저장해주고 각 매핑 테이블의 repository에서 saveAll 메서드를 이용해 저장하는 방식입니다. InterestService(관심분야) 가 하는 역할을 그림으로 표현했습니다. (MainActivityZoneService의 역할도 마찬가지입니다!)


image


회원의 정보를 가져오고 수정하기


사실 회원의 정보를 수정하기 위한 로직들인 것은 맞지만, 수정하기 위해 회원의 정보를 가져오는 로직과 직접 수정하는 로직을 분리하여 인터페이스화 시키는게 유지보수에도 용이할 것 같아 분리했습니다.


image


image


또한 가져오는 메서드명과 수정하는 메서드명이 같은 것을 볼 수 있습니다.인터페이스를 구현할 떄 이미 Get, Update를 분리했기 때문에 메서드명을 같게 하더라도 명확하고 헷갈리지 않을 것 같아서 이러한 방식으로 작성했습니다.


네 이렇게 비즈니스 로직을 분리하는 작업을 마무리했습니다. 리팩토링이 아직은 낯설었고, 분리하다가 마주친 에러들이 은근 많아서 고생을 많이했는데 하고 나니까 제 눈에는 각 클래스들의 역할이 명확하게 보이는 점이 뿌듯했습니다. 이제 팀원들의 피드백을 기다려봐야겠네요..!


긴글 읽어주셔서 감사합니다! 피드백은 언제나 환영합니다!

반응형