목차
개요
demo-fe , demo-api 로 총 2개의 Spring Boot프로젝트를 만들어서 WebClient 와 WebFlux CRUD 를 진행했다.
1.demo-fe
- Application 역할 : FrontEnd , View 역할 , DB 연결 X
- Tech Stack
- WEB-Client 기반의 REST-API (webflux) , Spring Open API 3 , thymeleaf
- gradle 7.5 , jdk 11 , spring-boot 2.6.12
1.1. WebClient 설정
build.gradle (gradle 사용시)
dependencies {
<생략>
implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
먼저 WebClient 를 사용하기 위해 webflux dependencies 를 추가해준다.
WebclientConfig.java
1.2. Thymeleaf 설정 (for Spring Webflux)
build.gradle (gradle 사용시)
dependencies {
<생략>
// thymeleaf for webflux
implementation 'org.thymeleaf:thymeleaf-spring5'
// thymeleaf 날짜 포맷팅
implementation 'org.thymeleaf.extras:thymeleaf-extras-java8time'
// thymeleaf layout
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
}
thymelaef-spring5 의존성을 추가해준다.
WebConfig.java
1.3. swagger (open api) 설정 (for Spring Webflux)
build.gradle
implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.6.11'
swagger 3 버전부터 open api 로 이름이 바뀌었다.
webflux 는 swgger 3 버전만 사용할 수 있기 때문에 open api 를 적용. ( swagger 로 여러번 시도해본 후 실패… )
Open API 는 Swagger 와 달리 configuration (설정 파일) 을 작성하지 않고, dependency 만 추가한 후 ,
http://{webserver-url}/swagger-ui.html 로 접속할 수 있다는 편리함이 있다.
1.4. 게시물 작성
private final WebClientConfig webclient;
// 게시물 작성
public Mono<Board> insertBoard(Board board){
WebClient client = this.webclient.getWebClient();
return client
.post()
.uri("/boards")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(board)
.retrieve()
.bodyToMono(Board.class);
}
- retrieve() 는 response body 를 받아 디코딩 하는 가장 간단한 메서드 이다.
- exchange 메서드 는 메모리 릭이 발생할 가능성이 높아, Spring 에서는 retrieve() 메서드 사용을 권고한다.
1.5. 게시물 삭제
// 게시물 삭제
public Mono<Void> deleteBoard(Long boardId){
WebClient client = this.webclient.getWebClient();
return client
.delete()
.uri("/boards/" + boardId)
.retrieve()
.bodyToMono(Void.class);
}
delete() 함수 의 특성상 response는 Void.class 로 처리했다.
2.demo-api
- Application 역할 : DB CRUD
- Tech Stack
- Spring DATA JPA , Spring WebFlux , mariaDB
- gradle 7.5 , jdk 11 , spring-boot 2.6.12
2.1. WebServerConfig.java
@Configuration
public class WebServerConfig {
@Bean
BoardHandler boardHandler(BoardRepository boardRepository) {
return new BoardHandler(boardRepository);
}
@Bean
RouterFunction<ServerResponse> routerFunction(BoardHandler boardHandler) {
return RouterFunctions.route()
.GET("/boards",accept(APPLICATION_JSON),boardHandler::list)
.GET("/boards/{boardId}",accept(APPLICATION_JSON),boardHandler::show)
.POST("/boards", boardHandler::save)
.DELETE("/boards/{boardId}",boardHandler::delete)
.PUT("/boards/{boardId}",accept(APPLICATION_JSON),boardHandler::update)
.build();
}
}
- Webflux 에서는 어노테이션 모델 (Controller > Service 로직 ) / 함수형 모델 (RouterFunction > Handler 로직) 모두 지원한다.
- Rest API 의 양식에 맞는 GET , POST , DELETE , PUT … 메서드를 지원한다.
- .GET("/boards",accept(APPLICATION_JSON),boardHandler::list) 의 역할은 GET "/board" 요청이 올 경우 BoardHandler클래스의 list 메서드로 라우팅 하는 것이다.
- WebFlux 함수형 스타일의 장점
- 모든 웹 요청처리 작업을 명시적인 코드로 작성
- 메서드 시그니처 관례와 타입 체크가 불가능한 Annotation 에 의존하는 @MVC 스타일 보다 명확함
- 정확한 타입체크 가능
- 함수 조합을 통함 편리한 구성, 추상화에 유리
- 테스트 작성의 편리함
- 핸들러 로직, 요청 매핑 리턴값처리 단위테스트로 작성가능
- 모든 웹 요청처리 작업을 명시적인 코드로 작성
- WebFlux 함수형 스타일의 단점
- 함수형 스타일의 코드 작성이 편하지 않으면 코드 작성과 이해 모두 어려움
- 익숙한 방식으로 가능한데, 새로운 방식을 배워야할 수 있음
- 익숙한 방식 : @MVC 스타일 Controller , Service
2.2. Webflux + Spring Data Jpa
2.2.1. 게시물 리스트
- BoardHander.java 일부
// 게시물 리스트
public Mono<ServerResponse> list(ServerRequest request) {
Flux<BoardEntity> boardFlux = Flux.defer(()
-> Flux.fromIterable(boardRepository.findAll())).subscribeOn(Schedulers.boundedElastic());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(boardFlux, BoardEntity.class);
}
- Flux 는 0~N 데이터를 전달 할 때 사용하고, Mono 는 0~1개의 데이터를 전달할 때 사용한다.
- Spring Data JPA 는 내부적으로 JDBC 를 사용하기 때문에 DB I/O 가 Blocking 으로 동작한다.
- 리액티브 스택은 블로킹 되는 구간이 있다면 전통적인 MVC 방식에 비해 얻는 이점이 거의 없다.
- 개선 방법
- 이와 같은 패턴을 사용하여 개선 할수 있다고 함 Reactor 3 Reference Guide
- Spring Data JPA > Spring Data R2DBC 로 변경해서 사용해볼수 있다. Spring Data R2DBC - Reference Documentation
- Schedulers.boundedElastic() 는 여유 있는 스레드가 있으면 재사용하는 옵션이다.
- Schedulers 에 관한 옵션은 Spring 공식문서에 다양한 옵션이 설명돼있다.
2.2.2. 게시물 삭제
- BoardHander.java 일부
import static org.springframework.web.reactive.function.server.ServerResponse.noContent;
import static org.springframework.web.reactive.function.server.ServerResponse.notFound;
@RequiredArgsConstructor
@Slf4j
public class BoardHandler {
private final BoardRepository boardRepository;
// < 생략 >
// 게시물 삭제
public Mono<ServerResponse> delete(ServerRequest request) {
Long boardId = Long.valueOf(request.pathVariable("boardId"));
return this.boardRepository.findById(boardId)
.map(
board -> {
this.boardRepository.delete(board);
return noContent().build();
}
).orElse(notFound().build());
}
}
- DELETE boards/{boardId}
- boardId를 DB 에서 조회한 후 존재하면 삭제 후 no Content (204 code) return, 존재하지 않으면 not Found (404 code) return
소스코드는 GitHub 에 있습니다.
참고
'Spring , JPA' 카테고리의 다른 글
Spring Boot , Java Application Graceful Shutdown (0) | 2023.03.13 |
---|---|
[spring] Spring Webflux 란? (0) | 2023.01.17 |
[kafka] kafka Install (0) | 2023.01.09 |
Webflux with MDC (0) | 2023.01.04 |
[Spring Cloud Gateway] JWT & Opaque Token (0) | 2023.01.04 |