일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 |
30 | 31 |
- java
- MESSAGEBROKER
- es_java_home
- 알고리즘
- CODETREE
- 카카오엔터프라이즈
- sonarqube
- 완전탐색
- db
- s3
- 인가인증
- bfs
- objectstorage
- 동전 퍼즐
- 함수 종속성
- dockercompose
- 카카오클라우드
- DFS
- 자바
- 코드트리
- jsonwebtoken
- DP
- On-Premise
- softeer
- bitmask
- 구름
- 소프티어
- BFS
- 백엔드 개발
- 정렬
- Today
- Total
wooing
도메인간 높은 결합도로 인한 순환참조를 해결하는 방법 본문
게시글 서비스를 구현하다보니 도메인간에 결합도가 높아 서비스 로직을 작성하는 과정에서 서로 다른 도메인의 서비스로직들이 순환참조되는 경우가 발생했다. 처음엔 순환참조를 회피하기 위해 컨트롤러에 여러 서비스를 호출하는 방법 등 상위 레이어에 기능을 부여하는 방법으로 해결했었다. 그러나 작업이 진행됨에 따라 너무 높은 결합도로 이제는 회피할 수 없는 단계에 이르렀다고 판단했고 결합도를 낮추는 원천적인 방법으로 해결해야겠다고 생각했다.
아래의 코드는 실제 작업하고 있는 게시글 서비스 코드중 일부이다. 보면 게시글 외에도 편집자, 소켓통신을 담당하는 Article서비스 등 여러 서비스를 참조하고있다.
@Service
@RequiredArgsConstructor
public class PostsCommandServiceImpl implements PostsCommandService{
private final PostsRepository postsRepository;
private final PostsQueryService postsQueryService;
private final EditorCommandService editorCommandService;
private final EditorQueryService editorQueryService;
private final ArticlesCommandService articlesCommandService;
private final RabbitMQService rabbitMQService;
해결방법
1. Service 레이어 분할
서비스레이어를 하나(현재는 CQRS패턴을 적용하고있어서 command와 query로 두개임)에서 컴포넌트와 모듈 서비스로 분할하는것이다. 모듈서비스는 DAO만을 의존하고, 간단한 기능을 구현한 저수준의 인터페이스 계층이다. 컴포넌트 서비스는 Facade pattern 방식 으로 모듈 서비스를 묶는 역할을 한다. 컴포넌트 서비스는 모듈 서비스를 의존하여 고수준의 작업을 하고, 트랜잭션 관리를 한다. 그리고 컴포넌트 서비스끼리는 의존하지 않도록 한다. 모듈서비스 또한 컴포넌트 서비스처럼 독립적으로 사용 가능하다.
https://jangjjolkit.tistory.com/62
2. 코드 중복으로 서비스 순환참조 해결
다른 도메인에 대한 의존이 필요한 경우 서비스를 참조하는것이 아닌, repository를 참조하여 코드를 중복시켜 서비스간 결합도를 줄이는 방식이다. 가장 쉽게 해결할 수 있지만, 단일책임원칙이나 유지보수 측면에서 안좋다.
적용한 방법 - Service 레이어 분할 도입
위에서 간략하게 순환참조 해결 방법을 설명했다. 그 중 나는 Service 레이어 분할하는 방식으로 코드 리팩토링하여 문제를 해결하였다.
현재 게시글 서비스는 CQRS패턴을 적용하여 Command(CUD담당)과 Query(R담당)로 분할한 상태이다. 구현되어있는 각 서비스를 모듈 서비스로 전환하여 최소 단위의 작업만을 수행하도록 하였다. 그리고 트랜잭션 관리가 필요한 경우 Compose 서비스를 생성하여 Compose 서비스에서 Command서비스나 Query서비스 또는 필요의 경우 다른 도메인의 모듈 서비스를 참조하여 트랜잭션 관리를 하였다.
서비스를 분할할때 각각의 역할과 고려사항은 다음과 같다.
- Controller는 같은 도메인의 아무 서비스나 호출할 수 있다.
- Compose 서비스는 아무 도메인의 모듈 서비스를 호출할 수 있다.
- Compose 서비스는 다른 도메인의 Compose 서비스를 호출할 수 없다.
- 모듈 서비스는 Repository와 Entity만을 참조한다.
- 모듈서비스 중 Command서비스는 CUD를, Query 서비스는 R을 담당한다.
컨트롤러/서비스의 구조를 그림으로 나타내면 아래와 같이 나타낼 수 있다. 상위 계층으로 갈수록 하위 계층에 대해 제약없이 참조/호출 할 수 있고 하위계층으로 갈수록 제약을 받도록 하였다. 결론적으로 최하단에 위치한 모듈 서비스가 참조하는 다른 서비스가 없기때문에 순환참조가 발생할 수 없고, 또한 같은 계층의 다른 도메인을 참조하지 못하기 때문에 순환참조가 발생하지 않는다.
분할 전의 서비스
아래는 분할 전의 Post도메인에 관련한 Command 서비스이다. Post 생성, 업데이트, 삭제 등의 기능을 담당한다. 그러나 서비스로직 구현 및 기능에 대한 트랜잭션 관리를 위해 아래처럼 다른 도메인의 서비스들을 참조하고 있는 상황이다.
@Service
@RequiredArgsConstructor
public class PostsCommandServiceImpl implements PostsCommandService{
private final PostsRepository postsRepository;
private final PostsQueryService postsQueryService;
private final EditorCommandService editorCommandService;
private final EditorQueryService editorQueryService;
private final ArticlesCommandService articlesCommandService;
private final RabbitMQService rabbitMQService;
@Override
public Posts updatePost(String userID, PostUpdateRequestDTO postUpdateDTO) {
// 요청 유저의 권한 확인
editorQueryService.getEditorByPostIdAndUserID(postUpdateDTO.getId(), userID);
Posts post = postsQueryService.getPost(postUpdateDTO.getId());
post.updateByDTO(postUpdateDTO);
postsRepository.save(post);
return post;
}
분할 후의 서비스들
아래는 모듈 서비스로 전환한 PostsCommandService이다. 다른 도메인의 서비스를 참조하던것을 모두 제거하고, 최소 단위의 작업을 수행하도록 비즈니스 로직을 컴포즈 서비스로 이동시켰다. 코드 내용적으로 변경된 점은 게시글 작성 권한이 있는 유저만 수정할 수 있도록 하기 위해 편집자 도메인으로부터 검증을 한 후에 게시글을 수정하도록 하였지만 검증 로직은 컴포즈 서비스로 이동시키고 수정하는 기능만 하도록 하였다.
@Service
@RequiredArgsConstructor
public class PostsCommandServiceImpl implements PostsCommandService{
private final PostsRepository postsRepository;
@Override
public Posts updatePost(String userID, PostUpdateRequestDTO postUpdateDTO) {
Posts post = postsRepository.findById(postUpdateDTO.getId()).orElseThrow(() -> new ApiException(
ErrorStatus._POSTS_NOT_FOUND));
post.updateByDTO(postUpdateDTO);
postsRepository.save(post);
return post;
}
[PostsComposeServiceImpl.java]
아래의 코드는 컴포즈 서비스 코드이다. 결국 서비스로직을 구현하기위해 Command 서비스에서 참조하던 다른 도메인의 서비스들을 컴포즈 서비스가 참조하여 호출하도록 하였다. 그러나 모두 모듈서비스이므로 순환참조는 발생하지 않고 같은 로직을 수행할 수 있도록 되었다.
@Service
@RequiredArgsConstructor
public class PostsComposeServiceImpl implements PostsComposeService{
private final PostsCommandService postsCommandService;
private final EditorCommandService editorCommandService;
private final EditorQueryService editorQueryService;
private final ArticlesCommandService articlesCommandService;
private final RabbitMQService rabbitMQService;
@Override
public Posts updatePost(String userID, PostUpdateRequestDTO postUpdateDTO) {
// 요청 유저의 권한 확인
editorQueryService.getEditorByPostIdAndUserID(postUpdateDTO.getId(), userID);
return postsCommandService.updatePost(userID, postUpdateDTO);
}
References
'Server' 카테고리의 다른 글
VM에 각 서비스에 사용할 DB 설치하기 (0) | 2024.05.20 |
---|