티스토리 뷰

728x90
반응형

목차

     

    웹플럭스로 개발을 하는 방법은 크게 두 가지로 나뉜다.

     

    • 기존의 엔드포인트 패턴 활용
    • 함수형 엔드포인트 사용

    물론 두 가지 방법 다 비동기 처리를 구현할 수 있고, 초심자 입장에선 뭐가 더 나은 방식이라고 말할 수 없다.

     

    그래도 이왕 웹플럭스를 사용하는 김에 함수형 엔드포인트를 적용해 보자! 하고 한 달 정도 삽질을 한 뒤에

     

    느낀 점을 정리하기 위해 이 글을 적는다.

     

    결론부터 말하면 일단 적응하고 나면 일반 엔드포인트보다 훨씬 직관적인 코드 작성이 가능하다.

     

    Router

     

    함수형 엔드포인트를 사용하려면 라우터 클래스의 작성이 필수적이다.

     

    라우터는 이름 그대로 매핑하는 역할을 하는데, 여기서 매핑이란 URL 패턴과 핸들러메서드의 매핑을 가리킨다.

     

    즉 사용자의 요청이 라우터를 거쳐 해당 엔드포인트의 핸들러메서드에 닿는다고 보면 된다.

     

    라우터 클래스는 간단하게 구현이 가능한데, RouterFunction 인터페이스를 구현하는 식이다.

     

    이렇게 말하면 복잡하니까 내가 구현해서 사용하고 있는 코드를 그대로 가져와 보겠다.

    @Configuration
    public class RatingRouter {
    
    	@Bean
    	public RouterFunction<ServerResponse> ratingRoute(RatingHandler ratingHandler) {
    		return route()
    			.nest(path("/ratings"), builder ->
    				builder
    					.GET("/{ratingId}/get", ratingHandler::readRating)
    					.GET("/list", request -> ratingHandler.readRatings())
    					.PUT("/{ratingId}/edit", ratingHandler::updateRating)
    					.DELETE("/{ratingId}/delete", ratingHandler::deleteRating))
    			.POST("/beers/{beerId}/ratings/post", ratingHandler::createRating)
    			.GET("/beers/{beerId}/ratings/get", serverRequest -> {
    				int page = Integer.parseInt(serverRequest.queryParam("page").orElse("1"));
    				String sort = String.valueOf(serverRequest.queryParam("sort").orElse("new"));
    				Mono<Page<Rating>> pageMono = ratingHandler.readRatingPageMono(serverRequest, sort, page);
    				return ServerResponse.ok().body(pageMono, new ParameterizedTypeReference<Page<Rating>>() {});
    			})
    			.build();
    	}
    }

    핸들러 메서드(미리 작성해두어야 한다! 아래에 코드 있음)를 매개변수로 받아

     

    RouterFunction을 반환하는 빈을 하나생성하는 것으로 라우터 메서드와 클래스 설정은 끝난다.

     

    사용된 메서드와 연산자를 간단히 정리하면 아래와 같다.

     

    • route() - 라우터 설정의 시작점. 설정을 전부 적은 뒤에 build()를 이용해 구성을 완료한다.
    • nest()
      RouterFunction 안에 또 다른 RouterFunction을 생성하는 기능. 이렇게 말하면 어렵지만 코드를 보면
      특정 URL 패턴을 공유하는 엔드포인트를 하나로 묶어서 처리하는 역할이다. 이를 이용하면 서로 다른 URL로 시작하는
      복잡한 패턴을 분리해서 한 눈에 보이게 처리할 수 있다.

    물론 핸들러 클래스와 메서드가 미리 작성되어 있어야 하고, 추가로 페이지네이션 처리도 여기에서 할 수 있다.

     

    페이지네이션에 대해선 아래 글에 정리해 두었다.

    2023.04.05 - [Development/WebFlux] - [WebFlux]Reactive MongoDB - 쿼리, 정렬, 페이지네이션

     

    [WebFlux]Reactive MongoDB - 쿼리, 정렬, 페이지네이션

    앞으로 쓰게 될 모든 웹플럭스 실습 글에 굳이 강조하겠지만, 아래의 코드는 그야말로 내 마음대로 공식문서와 각종 레퍼런스를 짜깁기해서 만들어낸 결과물이다. 당연히 Best Choice와는 거리가

    gnidinger.tistory.com

     

    Handler Method

     

    MVC 패턴으로만 개발하다가 웹플럭스로 왔을 때의 느낌은 <라우터 = 컨트롤러, 핸들러 = 서비스>였다.

     

    지금 생각도 크게 다르지 않기는 한데, 서비스 클래스를 작성할 일이 정말 많지 않기 때문이다.

     

    아무튼 실 사용 예제를 보면서 이야기를 계속하자.

    @PreAuthorize("hasRole('ROLE_USER')")
    	public Mono<ServerResponse> createRating(ServerRequest serverRequest) {
    
    		String beerId = serverRequest.pathVariable("beerId");
    
    		Mono<User> currentUserMono = userService.getCurrentUser();
    
    		Mono<Rating> ratingMono = currentUserMono
    			.flatMap(user ->
    				serverRequest.bodyToMono(Rating.class)
    					.doOnNext(rating -> validate(rating, beerId))
    					.flatMap(rating -> beerService.findBeerByBeerId(beerId)
    						.map(beer -> {
    							if (beer == null) {
    								throw new BeerNotFoundException();
    							} else {
    								rating.addUserId(user.getId());
    								return rating;
    							}
    						}))
    					.flatMap(rating -> {
    						return beerRepository.findById(beerId)
    							.flatMap(beer -> {
    
    								Double ratingStar = rating.getStar();
    
    								beer.addTotalAverageStar(ratingStar);
    
    								switch (user.getGenderType()) {
    									case "FEMALE":
    										beer.addFemaleAverageStar(ratingStar);
    										break;
    									case "MALE":
    										beer.addMaleAverageStar(ratingStar);
    										break;
    									case "OTHERS":
    										beer.addOthersAverageStar(ratingStar);
    										break;
    								}
    
    								return beerMongoRepository.save(beer.updateBeerTag(rating));
    							})
    							.flatMap(beer -> ratingMongoRepository.insert(rating, beerId))
    							.flatMap(rating1 -> {
    								user.addRatingId(rating1.getId());
    								return userRepository.save(user)
    									.thenReturn(rating1);
    							});
    					}));
    
    		return ServerResponse.status(HttpStatus.CREATED).body(ratingMono, Rating.class);
    	}

    위 코드는 로그인했으면서 특정 Role를 가지는 사용자가 Rating을 생성하는 메서드이다.

     

    먼저 가장 특이한 부분은, 페이지네이션 같은 특별한 처리가 없는 경우에 핸들러메서드는

     

    기본적으로 ServerRequest만을 매개변수로 받는다는 것이다.

     

    이 ServerRequest는 HttpServletRequest와 비슷하지만 비동기처리에 특화된 아이라고 한다.

     

    추가로 가장 아래를 보면 리턴값 역시 ServerResponse인 것도 확인할 수 있다.

     

    계속해서 코드를 보면 일단 요청을 받고, 그 안에서 필요한 정보를 꺼내 로직을 전개하고 있는데,

     

    리액티브 스트림의 구현체답게, 모든 로직이 하나의 긴 함수형 프로그래밍으로 짜여 있다.

     

    뜬금없지만 나는 아직도 어떤 연산자를 어디서 적절하게 쓰는지 솔직히 헷갈린다.

     

    어쨌거나 하나의 스트림에서 유효성 검사부터 매핑, 비즈니스 로직 처리까지 진행하다 보니

     

    처음엔 가독성이 많이 떨어지는 느낌을 받았다.

     

    그래도 자꾸 보다 보니 편해져서, 요즘은 가끔 MVC 패턴 코드를 유지보수 해야 할 때 어색하게 느껴질 정도.

     

    어쨌거나 함수형 엔드포인트의 특징에 대해 살짝 정리하고 글을 마치자.

     

    • 리액티브 프로그래밍 지원 - 비동기/논블로킹 처리 → 고성능
    • 일반 엔드포인트에 비해 다양하고 유연한 비동기 처리 가능
    • 함수형 프로그래밍 활용
      간결한 코드 및 쉬운 테스트. 조금 설명하자면 위에서 봤듯이 하나의 순수한 함수로 이루어져 있기 때문에
      입력값과 출력값만 사용해서 테스트 로직 작성이 가능하며, 목 객체 생성이 상대적으로 적다.
      마찬가지 이유로 기능 추가와 같은 확장이 굉장히 간단한데, 스트림에 함수를 끼워넣기만 하면 되기 때문이다.
    • 요청마다 새로운 객체 생성 - Thread-Safe 보장
    • 어려운 디버깅
      이 부분은 정말 할 말이 많은데, 일단 예외가 발생해도 정확히 어디서 어떤 이유로 발생했는지 파악부터가 어렵다.
      그렇다고 포인트를 찍어서 디버깅을 해도 정보를 제대로 찾기가 굉장히 어려워서 하나 잘못 걸리면 몇 시간 날리기 십상.
      게다가 처리를 잘못하면 에러가 발생해도 스트림은 그냥 진행해 버려서 콘솔을 보지 않는 한 에러가 발생했는지도
      모를 때도 있었다. 게다가 스트림의 특성상 코드의 흐름이 일반 패턴과는 판이하게 달라서 거기에 적응하는 것만 해도
      시간이 꽤 소요되었을 정도. 테스트 코드 작성이 쉬우면서 디버깅이 어렵다는 건 재미있는 모순이다.

    가끔 보면 러닝 커브가 높다거나 배우기 어렵다는 말이 있는데, 솔직히 처음 배우면 뭐든 그렇지 않냐는 생각에

     

    특징에 넣지는 않았다.

     

    그리고 의외로 경험상 백프레셔를 적용할만한 곳이 많지 않다는 것도 적을까 했지만,

     

    제대로 구현을 했는지도 확실하지 않은데 이런 말까지는 건방진 것 같아 역시 하지 않았다.

     

    어쨌거나 직관적이고 즐겁고, 무엇보다 빠른 웹플럭스.

     

    조금만 더 해보자...

    반응형
    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    링크
    «   2024/05   »
    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
    글 보관함