본문 바로가기

java/스프링인액션

11 리액티브 API 개발

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의 핵심 컴포넌트를 가져왔다.

 

스프링 5는 웹플럭스라는 새로운 웹 프레임워크

 

스프링 MVC 스프링 WebFlux
자바 서블릿 API?
서블릿 컨테이너? == 톰캣, 제우스(유료 컨테이너)
톰캣?
서블릿 API와 연계 되지 않는다.
서블릿 API == 리액티브 HTTP API
서블릿 컨테이너가 필요없다.
블로킹이 없는 어떤 웹 컨테이너에서도 실행됨

 

서블릿이란?

동영상 보면서 이해하려고 함: https://www.youtube.com/watch?v=calGCwG_B4Y 

 

http 요청과 http 응답을 주고 받을때 규칙 지키기 힘들잖아? 로직만 입력하게끔 도와주는거지. 그밖에는 그냥 알아서 servlet이 설정해줄거야.

 

서블릿을 이용하여 웹요청을 한다

개발자는 가운데 처리 로직만 집중하면 된다

규칙을 지켜주면 http요청 정보를 쉽게 사용할 수 있다.

처리 결과를 쉽게 응답으로 변환할 수 있다.

 

 

유저가 요청하면 서블릿컨테이너 중에서 뒤져서 그 인스턴스가 있는지 확인 후 있으면 인스턴스를 사용하고 없으면 새로 만든다.
서블릿

 

서비스의 로직 수행.. 요청에 따라 do~로 보낸다
doGet으로 받으면 출력한다.
설정 파일

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