11.1~11.2 - 동민
11.3~11.4 - 워니
11.5~12.1 - 초밥
12.2 - 민이
12.3 - 가을
11.1 스프링 WebFlux 사용하기
스프링 프레임워크 | WebFlux 리액티트 웹 프레임워크 |
매 연결마다 하나의 스레드 사용 |
비동기 웹 프레임워크 적은 수의 스레드로 더 많은 요청을 처리함. |
스레드 블록킹 | 블록킹이 없다. |
다중 스레드 | 이벤트 루핑 한 스레드 당 많은 요청을 처리할 수 있다. 데이터베이스, 네트워크 작업 모든 것이 이벤트로 처리된다. |
요청이 처리 될때 스레스 풀 --> 작업 스레드 가져옴 작업 스레드 종료까지 : 요청 스레드 블록킹 됨 |
비용이 드는 작업이 필요하면 -> 해당 작업의 콜백을 등록 -> 병행으로 수행 -> 다른 이벤트 처리로 넘어감 -> 작업 완료 : 이벤트로 처리 |
블록킹 웹 프레임 워크 요청량의 증가 --> 확장이 어려움 스레드가 느리면??? 더 심각해지네.. 느린걸 또 스레드 풀로 반환하여 다른 요청을 준비하는데 더 많은 시간이 걸린다. |
HTTP API 연동하여
끊임없이 데이터를 교환한다.
웹 애플리케이션을 사용하는 고객이 증가함.
확장성이 중요해짐
11.1.1 WebFlux?
WebFlux는 리액티브 웹 프레임워크 스프링
WebFlux?
스프링 MVC에 + 리액티브 프로그래밍 모델 집어 넣는 것. 이 아니라
리액티브 프로그랭 + 스프링 MVC의 핵심 컴포넌트를 가져왔다.
스프링 MVC | 스프링 WebFlux |
자바 서블릿 API? 서블릿 컨테이너? == 톰캣, 제우스(유료 컨테이너) 톰캣? |
서블릿 API와 연계 되지 않는다. 서블릿 API == 리액티브 HTTP API 서블릿 컨테이너가 필요없다. 블로킹이 없는 어떤 웹 컨테이너에서도 실행됨 |
서블릿이란?
동영상 보면서 이해하려고 함: https://www.youtube.com/watch?v=calGCwG_B4Y
서블릿을 이용하여 웹요청을 한다
개발자는 가운데 처리 로직만 집중하면 된다
규칙을 지켜주면 http요청 정보를 쉽게 사용할 수 있다.
처리 결과를 쉽게 응답으로 변환할 수 있다.
hello라는 요청이 들어오면 HelloServlet 이란 서블릿으로 처리하겠다.
HelloServlet은 servlet.HelloServlet 클래스 파일에 정의 되어 있다
서버쪽에서 실행되면서 클라이언트의 요청에 따라 동적으로 서비스 제공 -> 자바 클래스
공통적인 컴포넌트
같은 애노테이션을 공유
함수형 프로그래밍 패러다임으로
스프링 MVC | 스프링 WebFLux |
표준 웹 스타터 의존성 추가 | 스프링 부트 webFlux 스타터 의존성 추가 |
기본 내장 서버: 톰캣 | 기본 내장 서버: 네티 |
네티는 몇 안 되는 비동기적인 이벤트 중심의 서버. 리액티브 웹 프레임워크에 잘 맞는다. |
|
인자로 받거나 반환한다 도메인 타입, 컬렉션 Mono, Flux 리액티브 타입 --> 할수 있다. |
인자로 받거나 반환한다 Mono, Flux 리액티브 타입 Observable, Single, Completable -> RxJava 타입도 처리. |
다중 스레드에 의존하여 다수의 요청을 처리하는 서블릿 기반 웹 프레임워크 |
요청이 이벤트 루프로 처리됨 -> 진정한 리액티브 웹 프레임워크 |
크게 다르지 않다 @RestController @RequestMapping @GetMapping |
크게 다르지 않다 @RestController @RequestMapping @GetMapping 리포지터리 Flux<Taco>같은 타입을 받을 때 subscribe()를 호출할 필요가 없다. 프레임워크가 호출해주기 때문이다. |
11.1.2. 리액티브 controller 작성하기
@RestController
@RequestMapping(path="/design", produces="application/json")
@CrossOrigin(origins="*")
public class DesignTacoController {
...
// Iterable<Taco>로 반환
@GetMapping("/recent")
public Iterable<Taco> recentTacos() {
PageRequest page = PageRequest.of(0, 12, Sort.by("createdAt").descending());
return tacoRepo.findAll(page).getContent();//최근에 생성된 타코들 리스트 반환
}
// Flux 로 반환
@GetMapping("/recent")
public Flux<Taco> recentTacos() {
return Flux.fromIterable(tacoRepo.findAll()).take(12);
}
//RxJava 타입 사용하기 -> Observable, Single
@GetMapping("/recent")
public Observable<Taco> recentTacos() {
return tacoService.getRecentTacos();
}
// 단일 값 반환하기
@GetMapping("/{id}")
public Taco tacoById(@PathVariable("id") Long id) {
Optional<Taco> optTaco = tacoRepo.findById(id);
if (optTaco.isPresent()) {
return optTaco.get();// 하나의 Taco객체를 반환한다
// Optional타입을 반환하므로 처리하는 코드도 작성해야한다.
}
return null;
}
//스프링 WebFlux가 리액티브 방식으로 응답을 처리한다.
@GetMapping("/{id}")
public Mono<Taco> tacoById(@PathVariable("id") Long id) {
return tacoRepo.findById(id); //Mono<Taco>로 반환
}
//RxJava 타입 사용하기 -> Observable, Single
@GetMapping("/{id}")
public Single<Taco> tacoById(@PathVariable("id") Long id) {
return tacoService.lookupTaco(id);
}
//이것은 블록킹이 2번 된다.
@PostMapping(consumes="application/json")
@ResponseStatus(HttpStatus.CREATED)
public Taco postTaco(@RequestBody Taco taco) {// Taco 객체로 입력
return tacoRepo.save(taco);// Taco 객체로 반환
}
//블록킹 안됨
@PostMapping(consumes="application/json")
@ResponseStatus(HttpStatus.CREATED)
public Mono<Taco> postTaco(@RequestBody Mono<Taco> tacoMono) { //Mono<Taco> 받음
return tacoRepo.saveAll(tacoMono).next(); //Mono<Taco> 로 반환
// saveAll은 리액티브 스트림의 어떤 인자든 받을 수 있다.(Publisher)
}
...
}
컨트롤러는
end-to-end 스택의 제일 끝에 위치해야한다.
11.2 함수형 요청 핸들러 정의
스프링 MVC의 애노테이션 기반 프로그래밍 | |
스프링 2.5부터 있었음 | |
11.3 리액티브 컨트롤러 테스트 하기
1. GET 요청
private Taco testTaco(Long number) {
Taco taco = new Taco();
taco.setId(UUID.randomUUID());
taco.setName("Taco " + number);
List<IngredientUDT> ingredients = new ArrayList<>();
ingredients.add(
new IngredientUDT("INGA", "Ingredient A", Type.WRAP));
ingredients.add(
new IngredientUDT("INGB", "Ingredient B", Type.PROTEIN));
taco.setIngredients(ingredients);
return taco;
}
package tacos;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import tacos.Ingredient.Type;
import tacos.data.TacoRepository;
import tacos.web.api.DesignTacoController;
public class DesignTacoControllerTest {
@Test
public void shouldReturnRecentTacos() {
Taco[] tacos = { // 테스트 데이터 생성
testTaco(1L), testTaco(2L),
testTaco(3L), testTaco(4L),
testTaco(5L), testTaco(6L),
testTaco(7L), testTaco(8L),
testTaco(9L), testTaco(10L),
testTaco(11L), testTaco(12L),
testTaco(13L), testTaco(14L),
testTaco(15L), testTaco(16L)};
Flux<Taco> tacoFlux = Flux.just(tacos);
TacoRepository tacoRepo = Mockito.mock(TacoRepository.class);
when(tacoRepo.findAll()).thenReturn(tacoFlux);//모의 TacoRepository
WebTestClient testClient = WebTestClient.bindToController(
new DesignTacoController(tacoRepo))
.build();// WebTestClient 생성
testClient.get().uri("/design/recent")
.exchange()//요청한다. 최근 타코 12개
.expectStatus().isOk()
.expectBody()
.jsonPath("$").isArray()
.jsonPath("$").isNotEmpty()
.jsonPath("$[0].id").isEqualTo(tacos[0].getId().toString())
.jsonPath("$[0].name").isEqualTo("Taco 1").jsonPath("$[1].id")
.isEqualTo(tacos[1].getId().toString()).jsonPath("$[1].name")
.isEqualTo("Taco 2").jsonPath("$[11].id")
.isEqualTo(tacos[11].getId().toString())
//응답 JSON 데이터가 많거나, 중첩이 심해서 복잡하면
.jsonPath()쓰기가 좀 그래.. 그럴땐 json() 사용하셈
//추가됨
ClassPathResource recentsResource = new ClassPathResource("/tacos/recent-tacos.json");
// json 파일에 완벽한 응답이 있음.
String recentsJson = StreamUtils.copyToString(
recentsResource.getInputStream(), Charset.defaultCharset()
);//json 파일을 String화
testClient.get().uri("/design/recent")
.accept(MediaType.APPLICATION_JSON)
.exchange()//요청한다. 최근 타코 12개
.expectStatus().isOk()
.expectBody()
.json(recentsJson);// String인자를 받음.
// expectBodyList사용 : 여러개의 값을 갖는 응답
testClient.get().uri("/design/recent")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBodyList(Taco.class)
.contains(Arrays.copyOf(tacos, 12));
...
.jsonPath("$[11].name").isEqualTo("Taco 12").jsonPath("$[12]")
.doesNotExist();//12에는 값 없음 배열이라서 11까지
.jsonPath("$[12]").doesNotExist();
}
...
}
2. POST 요청
3. 실행 중인 서버로
11.4 REST API를 리액티브하게 사용하기
1. 리소스 얻기 GET
2. 리소스 전송하기
3. 리소스 삭제하기
4. 에러 처리하기
5. 요청 교환하기
'java > 스프링인액션' 카테고리의 다른 글
Spring in Action (0) | 2021.12.12 |
---|---|
10. 리액터 (0) | 2021.12.12 |
8. 비동기 메시지 전송 (0) | 2021.12.05 |
[8.3] 카프카 사용하기 (0) | 2021.12.04 |
[6.3] 스프링 데이터 REST (0) | 2021.11.27 |