Service Layer 책임 (관심사의 분리)

Panda·2022년 4월 6일
4

Spring

목록 보기
20/45

Service 책임 문제

문제 발생 이유 와 고민

프로젝트를 진행하면서 하나의 Service에서 너무많은 책임(기능)을 가져서
하나의 함수가 40줄이여서 가독성도 불편했고
무엇보다 Repository도 많이 가지고 있어서 너무많은 책임을 가지고 있었습니다.

그래서 이거를 어떻게 해결할까 고민을 하다가 2가지 방법을 고민해봤습니다.

  1. 기존 Service의 기능들을 하위 Service로 구현하고 상위 Service가 하위 Service를 참조하는 형태로 만들면 어떨까 라고 생각을 해봤습니다.
    • 구글링 해보니까 순환참조가 이루어져서 스파게티 코드가 되기 딱 좋다고 하더라고요 ㅠㅠㅠ
    • 기존 Layer 계층을 무너뜨림.
  2. 하나의 Response에 많은 정보를 담는 것이 문제 인것 같아서 Controller를 세분화 시켜 Response의 정보를 줄여 자연스럽게 Service 책임이 분할 되는 방식이 괜찮지 않을까 라고 생각해보았습니다.
    • 근데 클라이언트 쪽에서 하나의 정보를 얻기 위해 여러 api 요청을 여러번 보내야한다는 점.
    • Rest API의 단점인가?? (GraphQL 같은 것도 나중에 써보면 좋을 것 같아요.)

최종 결론

둘다 단점이 있어서 어떻게 해야할지 고민을 좀 하다가
Controller 단에서 Service를 조합하여 최종적인 Response를 내보내면 좋을 것같다고 생각했습니다.

기존 코드

// PostController 예시
@GetMapping("/post/{id}")
public ResponseFormat<Post> getPost(@PathVariable Long id) {
	Post post = postService.findPost(id, getUser());
    return new ResponseFormat(post);
}
// Post Service 예시
@Service
@RequiredArgsConstructor
public class PostService {
    private final PostRepository postRepository;
    private final VoteRepository voteRepository;
    private final CategoryInPostRepository categoryInPostRepository;
    private final VoteSelectRepository voteSelectRepository;
    private final PostLikeRepository postLikeRepository;
    
    @Transactional
    public PostDto findPost(Long id, UserEntity userEntity) {
        // 1. post 정보 얻기
        Post 정보 얻어오는 코드들
        ...
        
        // 2. vote 정보 얻기
        Vote 정보 얻어오는 코드들
        ...
       
        // 3. 해당 Post가 어떤 category를 가지고 있는지 정보 얻기
        Category 정보 얻어오는 코드들
        ...
       
        // 4. 현재 user가 어떤 투표를 했는지 얻기
        현재 User가 투표한 결과 얻어오는 코드들
        ...
   
        // 5. 현재 user가 좋아요 했는지 얻기
        User가 좋아요 했는지 얻어오는 코드들
        ...
        
        // 6. DTO 로 변환하는 코드들
        ...
      
        return postDto;
    }
}

이런식으로 PostService에서 특정 Post를 얻는 함수가 43줄이라는 함수가 완성되었습니다!!
코드를 보시면 총 6가지 기능을 하는 것을 볼수가 있었습니다.
게다가 하나의 Service가 가지고 있는 Repository들을 보면.......

하나의 함수가 6가지 기능을 수행한다? 너무나 끔찍한 코드를 저는 완성시킨거였습니다 ㅠㅠ
그래서 저는 저 기능들을 여러 Service들로 구성하여 Controller 계층에서 조립을 하였습니다.

변경 코드

// PostController
@GetMapping("/post/{id}")
public ResponseFormat<Post> getPost(@PathVariable Long id) {
	UserEntity userEntity = getUser();
    PostEntity postEntity = postService.findPost(id);
    List<Vote> votes = voteService.findVotes(postEntity);
    List<Category> categories = categoryInPostService.findCagegoriesInPost(postEntity);
    Integer voteResult = voteSelectService.findVoteSelectResult(userEntity, postEntity);
    boolean isLike = postLikeService.findPostIsLike(userEntity, postEntity);
	
    // DTO 변환 하는 코드들
    ...
    return new ResponseFormat(post);
}
// PostService
private final PostRepository postRepository;

@Transactional
public PostEntity findPost(Long id) {
	// post 정보 얻기 (단 3줄)
    return postEntity;
}
// VoteService
@Transactional
public List<Vote> findVotes(PostEntity postEntity) {
	// vote 정보 얻기 (단 4줄)
    return votes;
}
// CategoryService
@Transactional
public List<Category> findCagegoriesInPost(PostEntity postEntity) {
	// 해당 Post가 어떤 category를 가지고 있는지 정보 얻기 (단 5줄)
    return categories;
}
// VoteSelectService
@Transactional
public Integer findVoteSelectResult(UserEntity userEntity, PostEntity postEntity) {
	// 현재 user가 어떤 투표를 했는지 얻기 (단 4줄)
    return voteResult;
}
// PostLikeService
@Transactional
public boolean findPostIsLike(UserEntity userEntity, PostEntity postEntity) {
	// 현재 user가 좋아요 했는지 얻기 (단 4줄)
	return isLike;
}

자 이런식으로 책임을 분할하여 여러 Service를 만들었고 Controller에서 Service들의 결과를 조립하여 Response를 만들어 보냈습니다.

이렇게 해서 얻었던 이점들은

  • Service에는 Repository와 1대1 매칭
  • 책임의 분할로 다른 비즈니스의 로직의 영향을 안받음. (관심사의 분리)
  • 다른 Controller에서 그냥 필요한 Service 조합하기가 너무 편해짐.

Controller -> Service -> Repository -> DB
이러한 기존 계층 구조를 유지하면서 서비스의 책임을 줄여가지고
비즈니스 로직을 짜기 수월하게 됐는데
제가 생각했을 때는 이게 최선의 방법 같습니다.

느낀 점

스스로 특정 Service가 책임이 많다고 느끼고 관심사의 분리의 필요성을 느껴
제대로 리팩토링 한게 처음인거같은데
각성한것처럼 뿌듯하네요 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

그리고 Controller -> Service -> Repository -> DB
계층 구조이기 때문에 관심사의 분리를 좀더 수월하게 할 수있지 않았나 생각됩니다.
그래서 다양한 디자인패턴과 여러 패턴들이 관심사의 분리를 위해서 나온
개발자들의 많은 고민과 노력이 들어간 패턴들이라고 생각합니다.

다른 코드들을 참고하다가
하나의 Service 내에서 기능별 함수들을 만들어서
함수를 조합해서 쓰는 코드를 봤었는데
기능별로 함수를 구현해서 함수 자체는 간결해졌지만
이 역시 하나의 Service가 여러 책임(기능)들을 가지고 있기 때문에
하나의 Service가 무겁지 않나 라는 생각이 들어서 그렇게 좋은 코드는 아니라고 생각합니다.

참고 사이트

profile
실력있는 개발자가 되보자!

2개의 댓글

comment-user-thumbnail
2022년 4월 7일

너무 잘봤습니다 플젝에 반영해봐야겠네요!! 감사합니다^^

답글 달기
comment-user-thumbnail
2024년 12월 6일

facadeService사용하지 않은 이유가 순환참조 때문이라고 하셨는데, 하위 service에서 facadeService호출하지 않는다고 규칙 정하면 되는 것 아닐까요? 어떻게 생각하시나요?

답글 달기