Body 데이터에서 DB 엔터티 사용으로 인한 외래키 참조 불가 문제 발생
이게 무슨 뜻인지 코드를 통해 살펴보겠다.
문제발생
문제가 발생했던 게시판 조회 컨트롤러의 내용은 아래와 같다.
@PostMapping("/board/selectBoardList")
@ResponseBody
public Page<Board> selectBoardList(@RequestBody Board board) {
return boardService.getPagedBoard(board.getPageNo(), board.getPageUnit());
}
...
이하 생략
해당 코드에서 파일 엔터티를 게시판 엔터티와 연관 관계를 매핑하였을 때 글 조회 자체가 막히는 오류가 발생하였다.
1차 적으로 발생한 이슈는 @RequestBody Board board 해당 부분에서 DTO가 아닌 엔터티를 사용한 점이다. 필자는 Board 엔터티에 @Transient 어노테이션을 사용하여 마치 DTO의 역할을 하도록 지정하였다. @Transient는 엔티티 객체의 데이터와 테이블의 컬럼과 매핑하고 있는 관계를 제외하기 위해 사용한다. 좀 더 자세하게 말하자면, 영속 대상에서 제외시키기 위해 사용한다.
해당 어노테이션을 사용한 이유는 Board 자체를 불러오지만 Board 내부에 있는 pageNo 와 pageUnit은 데이터 베이스에 적용하지 않지만 DTO로는 사용이 가능해서 유연성 있는 어노테이션 이라고 생각이 들었기 때문이다.
프로젝트를 진행하면서 파일 엔터티를 추가하기 전까지는 이슈가 발생하지 않아 문제가 없는 코드라고 생각했지만, 해당 부분에서 엔터티를 참조할 수 없다는 이슈가 발생하였다.
왜 코드가 작성 작동 하지 않았을까?
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL)
@JsonIgnore
private List<FileEntity> files;
Board 엔터티의 일부분이다. 해당 코드는 파일 엔터티와 관계를 매핑하는 코드이다. 게시글 조회시 Board 객체를 사용하면, 해당 엔터티에 대한 참조 문제가 발생한다.
해결 과정
Spring의 고급 개념인 Spring Hexagonal Architecture에 따르면 도메인 객체가 윕 계층과 직접적으로 결합되선 안된다고 한다.
그게 대체 뭔데? 깊게 들어가지말고 얕게 알아보자.
Hexagonal Architecture
아키텍처 중 하나로, 어플리케이션의 핵심 비지니스 로직을 외부 요소로 부터 분리하여 느슨하게 결합된 구성 요소를 만드는 것을 목표로 한다.
- 핵심 도메인 로직:
- Hexagonal Architecture의 중심에는 애플리케이션의 핵심 비즈니스 로직이 위치
- 이 로직은 외부의 어떤 요소에도 의존하지 않고 독립적으로 존재
- 포트(Ports):
- 포트는 애플리케이션의 핵심 로직이 외부 세계와 상호작용하는 지점을 정의
- 포트는 인터페이스로 구현되며, 애플리케이션의 핵심 로직이 외부 시스템과 통신하는 방법을 명시
- 어댑터(Adapters):
- 어댑터는 포트 인터페이스를 구현하여 외부 시스템과의 실제 통신을 처리
- 주변 어댑터(Primary Adapters): 사용자 인터페이스, API, CLI 등 사용자가 애플리케이션과 상호작용하는 외부 인터페이스를 처리
- 보조 어댑터(Secondary Adapters): 데이터베이스, 메시지 큐, 외부 서비스 등 애플리케이션이 사용하는 외부 시스템과의 상호작용을 처리
- 어댑터는 포트 인터페이스를 구현하여 외부 시스템과의 실제 통신을 처리
- 의존성 규칙:
- 핵심 도메인 로직은 외부 시스템에 의존하지 않는다.
- 외부 시스템이 핵심 로직에 의존하게 설계된다.
- 이를 통해 핵심 로직이 변경되지 않고도 외부 시스템을 쉽게 교체하거나 수정할 수 있다.
필자는 위 아키텍처에서 영감을 받아 게시글 조회 코드를 아래와 같이 작성하였다.
컨트롤러
@PostMapping("/board/selectBoardList")
@ResponseBody
public ResponseEntity<Page<Board>> selectBoardList(@RequestBody PageRequestDTO pageRequestDTO) {
Page<Board> boardPage = boardService.getBoardPage("Y",pageRequestDTO);
System.out.println(boardPage);
return ResponseEntity.ok().body(boardPage);
}
해당 컨트롤러가 주변 어댑터의 역할을 한다.
- ResponseEntity<Page<Board>>
- HTTP 응답의 상태 코드와 바디를 포함
- @RequestBody PageRequestDTO pageRequestDTO
- 클라이언트가 전송한 JSON 데이터를 PageRequestDTO 객체로 매핑
- return ResponseEntity.ok().body(boardPage)
- ResponseEntity 객체를 생성하여 HTTP 200 상태 코드와 함께 boardPage 객체를 응답의 바디로 설정
DTO
@Getter
@NoArgsConstructor
public class PageRequestDTO {
// 번호 제목 작성자 등록일
private int pageNo;
private int pageSize;
public PageRequestDTO(int pageNo, int pageSize){
this.pageNo = pageNo;
this.pageSize = pageSize;
}
}
Hexagonal Architecture 측면에서의 설명
- DTO 사용: PageRequestDTO는 클라이언트로부터 들어오는 데이터를 캡슐화하여 도메인 모델과 분리
- 서비스 계층: BoardService는 비즈니스 로직을 처리하며, 컨트롤러는 단순히 요청을 받아서 서비스 계층에 위임
- 유연한 아키텍처: 웹 계층은 BoardService에 의존하기 때문에, 비즈니스 로직의 구현을 쉽게 변경하거나 확장 가능
'인실리코젠' 카테고리의 다른 글
[인실리코젠] Spring Security Interceptor 와 JSESSION ID에 대하여 (1) | 2024.09.22 |
---|---|
[인실리코젠] Spring Security + JWT + JPA (0) | 2024.03.15 |
[인실리코젠] Spring CRUD - 회원 가입 (0) | 2024.02.16 |
[인실리코젠] Spring CRUD 프로젝트 요구 사항 분석 및 데이터베이스 설계 (1) | 2024.02.16 |
[인실리코젠] Web Crawling - Finish (1) | 2024.02.02 |