Spring , JPA

[spring] Spring Webflux CRUD

seulseul 2023. 1. 16. 14:32

목차

 

    개요

    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)

    open api 화면 캡처 (http://localhost:9099/swagger-ui.html)

    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 방식에 비해 얻는 이점이 거의 없다.
      • 개선 방법
    • 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