💡 Spring Webflux는 비동기적 서비스를 위한 Spring 프레임워크의 한 요소로 ‘Controller’와 ‘Router’라는 두 가지 주요 컴포넌트를 제공합니다.
💡 Controller 방식은 Spring MVC와 유사한 방식으로 HTTP 요청을 처리하는 데 사용됩니다. 이 컴포넌트는 주로 RESTful API 엔드포인트를 구현하는 데 사용됩니다. 💡 Router 방식은 HTTP 요청을 처리하는 데 사용되는 라우터 및 핸들러 함수를 정의하는 데 사용됩니다. 비동기적으로 실행되며 스레드가 블로킹되지 않아 더 높은 처리량을 달성할 수 있습니다.
HandlerFunction 또는 WebFlux.fn.ServerResponse 타입의 객체를 반환한다.
@RestController 어노테이션을 사용하여 해당 클래스의 모든 메서드가 HTTP 응답을 반환한다는 것을 나타낸다.
예외 처리
RouterFunction에서 예외 처리를 할 수 있다.
@ControllerAdvice 어노테이션을 사용하여 예외 처리 핸들러를 등록할 수 있다.
[ 더 알아보기 ] 💡 엔드포인트(Endpoint)
- RESTful API에서 URI(Uniform Resource Identifier)로 식별되는 특정한 인터넷 자원에 대한 접근 경로를 말합니다. - 클라이언트는 URI를 통해 서버에 요청을 보내고, 서버는 해당 요청에 대한 응답을 반환합니다. - 예를 들어, https://example.com/api/users는 사용자 정보를 가져오기 위한 엔드포인트입니다. 💡 Java DSL(Java Domain Specific Languague)
- 특정 도메인에서 사용되는 언어를 의미하며 자바 코드를 사용하여 Router를 정의할 수 있습니다. Router는 요청을 처리하기 위해 적절한 핸들러 함수를 선택하는 메커니즘을 제공합니다. - Java DSL을 사용하면 코드 가독성을 높일 수 있고, Router에 대한 정적 타입 검사를 수행할 수 있습니다.
- Handler Function을 호출하는 역할을 합니다. 이를 통해 HTTP 요청을 받고 적절한 Handler Function으로 라우팅 할 수 있습니다. - Router Function은 RouterFunctions 클래스를 사용하여 생성할 수 있으며 다양한 메서드를 사용하여 HTTP 요청에 따라 적절한 Handler Function으로 라우팅 할 수 있습니다. - Router Function을 사용하면 라우팅 규칙을 보다 직관적이고 유연하게 제어할 수 있습니다.
- Spring WebFlux에서 HTTP 요청을 처리하기 위한 RouterFunction을 작성할 때 사용되는 요청 매핑 조건을 나타내는 클래스입니다. - RequestPredicates 클래스는 정적 메서드로 여러 요청 매핑 조건을 만들 수 있습니다.
3. Handler Function
💡 Handler Function
- 단일 입력과 단일 출력을 가지며, Spring WebFlux Framework가 HTTP 요청을 처리하기 위해 호출하는 메서드입니다.
- Handler Function은 Router Function에 매핑됩니다. Router Function은 HTTP 요청을 Handler Function으로 라우팅 하는 Spring WebFlux Framework의 메서드입니다.
@Service
public class ServiceHandler {
private final ServiceRepository serviceRepository;
public ServiceHandler(ServiceRepository serviceRepository) {
this.serviceRepository = serviceRepository;
}
public Mono<ServerResponse> getService(ServerRequest request) {
return serviceRepository.findById(request.pathVariable("id"))
.flatMap(service -> ServerResponse.ok().body(BodyInserters.fromValue(service)))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> getServices(ServerRequest request) {
return ServerResponse.ok().body(serviceRepository.findAll(), Service.class);
}
public Mono<ServerResponse> createService(ServerRequest request) {
return request.bodyToMono(Service.class)
.flatMap(service -> serviceRepository.save(service))
.flatMap(service -> ServerResponse.created(URI.create("/service/" + service.getId())).build());
}
public Mono<ServerResponse> updateService(ServerRequest request) {
return request.bodyToMono(Service.class)
.flatMap(service -> serviceRepository.findById(request.pathVariable("id"))
.flatMap(existingService -> {
existingService.setName(service.getName());
existingService.setDescription(service.getDescription());
return serviceRepository.save(existingService);
}))
.flatMap(service -> ServerResponse.noContent().build())
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> deleteService(ServerRequest request) {
return serviceRepository.deleteById(request.pathVariable("id"))
.flatMap(result -> ServerResponse.noContent().build())
.switchIfEmpty(ServerResponse.notFound().build());
}
}
4. Mono
💡 Mono - Reactor 라이브러리에서 제공하는 Reactive Streams의 Publisher 중 하나로 오직 ‘0개 또는 하나의 데이터항목 생성’하고 이 결과가 생성되고 나면 스트림이 종료되면 결과 생성을 종료합니다.
- Mono를 사용하여 비동기적으로 결과를 반환하면 해당 결과를 구독하는 클라이언트는 결과가 생성될 때까지 블로킹하지 않고 다른 작업을 수행할 수 있습니다.
public Mono<String> getData() {
// perform some database or API call to get data
return Mono.just("example data");
}
5. Flux
💡 Flux
- Reactor 라이브러리에서 제공하는 Reactive Streams의 Publisher 중 하나로 Mono와 달리 ‘여러 개의 데이터 항목’를 생성하고 스트림이 종료되면 결과 생성을 종료합니다.
- 비동기 작업을 수행하면 작업이 완료될 때까지 블로킹하지 않고 다른 작업을 수행할 수 있습니다. - Spring WebFlux에서 Flux를 사용하여 HTTP 요청을 처리하는 경우, 요청을 수신한 즉시 해당 요청을 처리하고 결과를 생성하는 대신 결과 생성이 완료될 때까지 다른 요청을 처리할 수 있습니다.
public Flux<String> getStreamedData() {
// perform a continuous stream of data
return Flux.just("streamed data 1", "streamed data 2", "streamed data 3");
}
2) 환경 구성
1. 개발환경
[ 더 알아보기 ]
💡 Webflux랑 SQL Mapper(MyBatis)는 함께 사용이 불가능 한가?
- 함께 사용하기가 어렵다. WebFlux의 경우는 비동기적이고 논 블로킹 방식으로 동작하며, MyBatis는 동기적 블로킹 방식으로 동작하기 때문에 같이 사용하면 MyBatis가 디폴트로 사용하는 스레드 풀을 블로킹하게 되어 전체적인 성능에 악영향을 미칠 수 있다고 합니다. 💡 그럼 Webflux랑 ORM(Object-Relational Mapping)는 함께 사용이 가능한가?
- WebFlux는 비동기 및 논 블로킹 방식으로 동작하며, ORM은 동기적인 블로킹 방식으로 동작하므로 함께 사용하면 전체적인 성능에 악영향을 미칠 수 있습니다. 💡 그럼 SQL Mapper, ORM 말고 무엇과 Webflux를 함께 사용해야하는가? - RDBMS를 사용하는 것이 아닌 MongoDB를 함께 사용하는 것이 일반적입니다. MongoDB는 비동기적이고 논 블로킹 방식으로 동작하기 때문입니다.
2. gradle.build 라이브러리 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux' // Spring Boot Webflux
}
Publisher가 갖는 값을 동기적으로 반환하는 메소드입니다. 해당 Publisher가 값을 방출할 때까지 대기한 후 값을 반환합니다.
4. 종합 결과
1. WebClient 인스턴스를 최초 생성합니다. 2. WebClient 인스턴스를 기반으로 HTTP Method의 POST 방식을 이용합니다. 3. 최종 전달하려는 http://localhost:8000/api/v1/code/code로 POST의 Body 데이터를 담아 전송합니다. 4. 전송이 되면 반환값으로 CodeDto.class 객체의 값으로 반환받습니다.
💡 아래의 두 개의 코드가 있습니다. - 하나는 일반적으로 사용하는 WebClient.create() 방식과 다른 하나는 Builder()를 이용한 방식입니다. - 빌더를 사용하면 defaultHeader 메서드를 통해 공통적으로 사용되는 헤더를 추가할 수 있습니다. 또한 header 메서드를 통해 요청에 특정 헤더를 추가할 수 있습니다.