Hexagonal Architecture 적용해보기

h.Im·2025년 5월 9일

회고록

목록 보기
4/5

https://velog.io/@fddsgt123/hexagonal-spring

이전 포스팅에서 회사 프로젝트에서 hexagonal architecture를 적용한 내용에 대해 기록을 남겼습니다. 이번 사이드 프로젝트를 진행하면서 hexagonal architecture 적용을 고도화해본 내용에 대한 회고록을 남깁니다.

Hexagonal Architecture의 특성

Hexagonal Architecture는 도메인 단위(비즈니스 로직)가 외부와 연결되는 부분을 추상화하는 특성을 가지고 있습니다. 도메인이 외부와 독립적으로 존재하여 비즈니스 로직에 집중할 수 있다는 장점이 있지만, 하나의 도메인을 여러 port에 연결하여 사용할 수 있는 장점 또한 가지고 있습니다.
회사 프로젝트는 하나의 도메인에 하나의 port 연결만을 가진 구조이기 때문에 이번 사이드 프로젝트를 진행하면서 하나의 도메인에 여러 port를 연결하는 방식을 적용해 보았습니다.

회사 프로젝트는 REST Adapter만 존재하는 구조, 이번 사이드 프로젝트에서 gRPC Adapter가 아닌 EventListener Adapter를 적용

EventListener Adapter

REST Adapter

@RestController
@RequiredArgsConstructor
public class NoticeControllerImpl implements NoticeControllerApi {

	...

    @Override
    public ResponseEntity<DeleteNoticeResDTO> deleteNotice(DeleteNoticeReqDTO deleteNoticeReqDTO) throws Exception {
        NoticeDomain noticeDomain = NoticeDomain.builder()
                .notiId(deleteNoticeReqDTO.getNotiId())
                .build();
        return ResponseEntity.ok(modelMapper.map(noticeUseCase.deleteNotice(noticeDomain), DeleteNoticeResDTO.class));
    }
}

REST Adapter는 @RestController 어노테이션이 붙은 컨트롤러를 의미합니다. RestController가 추상화된 도메인(noticeUseCase)을 호출합니다.

EventListener

@EventListener
public void handleNotification(DeleteNotiEvent event) throws Exception {
	NoticeDomain noticeDomain = NoticeDomain.builder()
		.notiId(event.getNotiId())
		.build();

	noticeUseCase.deleteNotice(noticeDomain);
}

EventLister는 다른 도메인 단위에서 ApplicationEventPublisher를 통해 호출됩니다. deleteNotice라는 하나의 기능을 여러 가지 방식(port)으로 사용할 수 있도록 구현했습니다.

비동기 적용

ApplicationEventPublisher를 통해 하나의 도메인에서 다른 도메인을 호출하는 경우, 기본적으로 동기 방식으로 동작합니다. 그러나 도메인이 분리되었다는 것은 독립적인 동작이 필요하다는 것을 의미하기 때문에 비동기 방식으로 이벤트 전달이 필요합니다.

예를 들어, 리뷰를 생성하는 경우 자동 생성 댓글이 추가되어야 하는데, 자동 댓글이 생성될 때까지 리뷰 생성 요청이 대기하거나 자동 댓글 생성이 실패한다고 해서 리뷰 생성이 불가하면 안 되기 때문입니다.

@Async
private void sendDeleteReviewEvent(ReviewDomain reviewDomain) {
	DeleteCommentEvent event = DeleteCommentEvent.builder()
		.reviewId(reviewDomain.getReviewId())
		.build();
	eventPublisher.publishEvent(event);
}

이럴 경우, 위와 같이 Async 어노테이션을 이용해 이벤트 전달은 비동기 방식으로 이루어지도록 작성하였습니다.

0개의 댓글