[Getting Started]Building a Reactive RESTful Web Service

2018. 3. 2. 14:19Java/Spring Boot

우아... Spring Boot 2.0 GA 정식 버전  출시를 기념으로 , 아마도 Reactive 에 대한 이야기들이 
엄청나게 쏟아져 나올것이라고 예상합니다.

트윗에서도 가이드들이 하나 둘씩 쏟아져 나오고 있어서, 읽어봐야 할 꺼리가 상당히 많아 지고 있습니다.
그래도 Spring.io 에서 정식 가이드 한 튜토리얼을 숙지하는 것이 우선이라 생각하기에 번역을 시작합니다.

매번 그렇지만, 리딩, 독해가 너무 약하다..(그렇다고, 말하기 듣기가 유연하게 되는것도 아니잖아)

Building a Reactive RESTful Web Service

Getting Started

이 가이드는 Spring WebFlux(Spring Framework 5 새로운 버전)을 가지고 
"Hello,Spring!" RESTful 웹 서비스를 만드는 절차를 통해서 진행하도록 합니다.
그다음 WebClient(이것도 Spring Framework 5 새로운 버전)을 가지고 
웹서비스를 받아처리하는 것으로 진행합니다.

이 가이드는 Spring WebFlux을 사용하는 함수적인 방법을 보여줍니다.
여러분은 WebFlux에 대한 어노테이션을 사용할수 있습니다.
(링크가 없네요)

빌드 할 것은?
이제부터 여러분은 Spring WebFlux을 가지고 RESTful 웹 서비스와 웹 서비스를 처리하는 WebClient을 빌드할것입니다.
여러분은 System.out 과 아래 링크를 통해서 출력을 확인 할수 있습니다.


필요한건? 

 
이 가이드를 완료하는 법

다른 Getting Started Guide들과 마찬가지로, 처음부터(from scratch) 시작해서, 차근차근 각 단계를 완성하거나,
이미 익숙한 기본 셋업은 건너뛸수 있습니다.
어찌되었든(Either way), 코드 작성하는 것을 끝마칠수 있습니다.
  • 처음부터 시작하는 것은 Build with Gradle로 이동하세요
  • 기본단계를 건너뛰려면, 아래를 따라가세요.
완료하면, 여러분은 아래 결과 코드와 여러분이 만든 결과를 비교해보시면 됩니다.
gs-reactive-rest-service/complete

Bulid with Gradle

우선 기본 빌드 스크립트를 셋업합니다.
Spring으로 앱을 구축할때, 여러분이 좋아하는 어떠한 빌드 시스템을 사용해도 됩니다.
하지만  Gradle 과 Maven을 가지고 작업하고자 하는 코드는 여기에 포함되어 있습니다.
둘다 별로 안편하면, 가이드를 읽어보시는 것을 추천합니다.
- Building Java Projects with Gradle : https://spring.io/guides/gs/gradle
- Building Java Projects with Maven : https://spring.io/guides/gs/maven

Create the directory structure

여러분이 선택한 프로젝트 디렉토리에서, 아래 하위 디렉토리 구조를 생성합니다.
예를 들면 , mkdir -p src/main/java/hello 를 *nix 시스템에서 실행하면 되겠죠
└── src
    └── main
        └── java
            └── hello
Create a Gradle build file

아래는 초기 Gradle 빌드 파일입니다.
build.gradle
buildscript {
    ext {
        springBootVersion = '2.0.0.RELEASE'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot"; }
        maven { url "https://repo.spring.io/milestone"; }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
jar {
    baseName = 'gs-reactive-rest-service'
    version =  '0.1.0'
}
sourceCompatibility = 1.8
repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot"; }
    maven { url "https://repo.spring.io/milestone"; }
}
dependencies {
    compile('org.springframework.boot:spring-boot-starter-webflux')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('io.projectreactor:reactor-test')
}
Spring Boot gradle 플러그인은 많은 편리한 기능들을 제공하고 있습니다.

  • 클래스패스상에 모든 jar파일을 모아주고,여러분의 서비스를 전송하고, 보다 더 편리하게 실행시켜 주는 
    단독으로 실행가능한 über-jar을 만들어 줍니다.
  • 실행가능한 클래스로써 표기하기 위해, public static void main() 메소드를 찾습니다.
  • Spring Boot dependencies와 매칭하는 버전 숫자를 배치하는 
    내장형 의존성 리졸버(built-in dependency resolver)을 제공합니다.
    여러분이 원하는 어떤 버전이라도 오버라이드 할수 있지만, Boot의 선택된 서버들의 배치에 기본할 것입니다. 


Build with Maven

Gradle과 모든 것이 동일하다고 생각하시면 되고, 빌드 스크립트만 다릅니다.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.springframework</groupId>
    <artifactId>gs-reactive-rest-service</artifactId>
    <version>0.1.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>;
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>;
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>;
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>;
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    </project>

Build with your IDE

여기서는 어떻게  이 가이드를 직접 임포트 하는지를 읽어보시면 되겠습니다.

나는 어떻게 하느냐?

저는 IDE을 IntelliJ IDEA을 사용하고 있으나,  무료버전을 사용하고 있어서,
위와 같이 자동생성되는 것을 가지고 있지 않습니다. T.T
따라서, start.spring.io 에 가서, 프로젝트를 생성해서 특정 폴더에 옮겨 놓고,
IntelliJ IDEA을 이용해서 개발을 진행합니다.

위에서 보게 되면 webflux 하나만 가져오는 것을 알수 있기 때문에, 저도 그렇게 합니다. 

너무 좋아합니다.


Create a WebFlux Handler

Spring Reactive 접근법에 따라서,
request을 처리하는 핸들러를 사용하고, 아래 예제에서 보여진것처럼 response을 만들겁니다. 

src/main/java/hello/GreetingHandler.java
package com.ksh.reactive.testwebservice.handler;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

@Component
public class GreetingHandler {

    public Mono<ServerResponse> hello(ServerRequest serverRequest){
      return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
            .body(BodyInserters.fromObject("Hello, Spring!"));
    }
}
요 단순한 리액티브 클래스는 항상 "Hello, Spring!"을 리턴합니다.
데이터베이스에서 아이템들의 스트림을 포함한 것, 계산기에 의해 생성된 아이템들의 스트림, 등등 
많은 다른 것들을 리턴할수 있습니다. 
reactive code을 기억하세요. : ServerResponse body를 잡고 있는 Mono 객체

Create a Router

이 어플리케이션에서, 우리는 아래 예제에서 보여진것 처럼, "/hello"을 노출시킨 라우터만 처리하는 router를 사용합니다.

src/main/java/hello/GreetingRouter.java

package com.ksh.reactive.testwebservice.router;

import com.ksh.reactive.testwebservice.handler.GreetingHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class GreetingRouter {

   @Bean
   public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler){
      return RouterFunctions
         .route(RequestPredicates.GET("/hello")
         .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), greetingHandler::hello);
   }
}
라우터는 /hello에 트래픽을 리스닝하고, 우리의 reactive 핸들러 클래스에서 제공하는 값을 리턴합니다.

Create a WebClient

Spring MVC RestTemplate 클래스는 기본적으로 차단하고 있습니다.
따라서, 우리는 reactive 어플리케이션에서 그것을 사용하길 원치 않습니다.
반응형 어플리케이션을 위해, Spring은 non-blocking 인 WebClient 클래스를 제공합니다.
우리는 우리의 RESTful 서비스를 소비하기 위해 WebClient 구현을 사용할 것입니다.

src/main/java/hello/GreetingWebClient.java

package com.ksh.reactive.testwebservice.client;

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

public class GreetingWebClient {
   private WebClient client = WebClient.create("http://localhost:8080";);

   private Mono<ClientResponse> result = client.get()
      .uri("/hello")
      .accept(MediaType.TEXT_PLAIN)
      .exchange();

   public String getResult(){
      return ">> result = " + result.flatMap(res -> res.bodyToMono(String.class)).block();
   }
}
WebClient는  우리가 명시하는 URI의 컨텐츠를 유지하는 Mono 형식과 
spring안에 내용을 턴하기 위한 함수( getResult 메소드에서)에서 반응형 기능들을 사용합니다.
다른 요구를 가지고 있다면, string이 아닌 다른 무언가에 그것을 나타낼것입니다.
System.out에 결과를 나타낼것이기에, string으로 합니다.

Webclient은 non-reative, blocking 서비스들도 마찬가지로 커뮤니케이션하는데 사용됩니다.

Make the Application Executable

외부 어플리케이션 서버에 배포하기 위해 전통적인 WAR 파일로 이 서비스를 패키지하는 것이 가능하지만,
아래 설명된 보다 간단한 접근이 단독 어플리케이션을 생성합니다.
여러분은  좋은 오래된 Java main 메소드에 의해 운행되는 단독의 실행가능한 JAR 파일로 모든것을 패키징 합니다.
그 방법에 따라, 외부 인스턴에 배포하는 것 대신  여러분은 HTTP 런타임 서버로써 
내장된 Netty 서버을 위한 Reactive Spring의 지원을 사용하게 될 것입니다.

src/main/java/hello/Application.java
package com.ksh.reactive.testwebservice;

import com.ksh.reactive.testwebservice.client.GreetingWebClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestwebserviceApplication {

   public static void main(String[] args) {
      SpringApplication.run(TestwebserviceApplication.class, args);

      GreetingWebClient gwc = new GreetingWebClient();
      System.out.println(gwc.getResult());
   }
}

@SpringBootApplication은 아래 모든것을 추가하는 편리한 어노테이션입니다. 

  • @Configuration 은 어플리케이션 컨텍스트에 대한 빈 정의 출처로 클래스를 태그합니다.
  • @EnableAutoConfiguration은 Spring Boot에게 classpath 셋팅들, 다른 빈즈 그리고 다양한 프로퍼티 셋팅들에 기반한 빈즈들을 추가하는 것을 시작하라고 전달합니다.
  • 일반적으로 여러분은 Spring MVC app에 대해서 @EnableWebMvc을 추가했었으나,
    클래스 패스상에 spring-webmvc 를 확인 했을때, Spring Boot는 그것을 자동으로 추가합니다.
    이것은 어플리케이션이 웹 어플리케이션이라고 표시하고, 
    DispatcherServlet 을 셋업하는 것과 같은 핵심 행동들을 활성화합니다.
  • @ComponentScan은 Spring에게 hello 패키지에서 다른 컴포넌트들, 구성들, 서비스들을 찾고,
     컨트롤러들을 찾을 수 있도록 지시합니다.

main() 메소드는 어플리케이션이 런칭하도록 Spring Boot의 SpringApplication.run() 메소드를 사용합니다.
XML의 단일 라인이 없다는 것을 알고 있었습니까?
아닙니다. web.xml 파일 둘다 없습니다.
이 웹 어플리케이션은 100% 순수 자바이고, 어떤 Plumbing 이나 infrastructure를 구성하는 것을 다뤄서는 안됩니다.

Build an excutable JAR

Gardle 이나 Maven을 가지고 커맨드라인으로 부터 어플리케이션을 기동할수 있습니다.
또는 모든 필수적인 의존성, 클래스들, 리소스들 포함하고, 그걸 실행하는 단독 실행가능한 JAR 파일을 빌드 할수 있습니다.
이것은 개발 라이프사이클, 다른 환경을 넘어서, 그 밖에 것들을 통해 띄우고, 버전관리하고, 배포하는 것을 어플리케이션으로 서비스를 매우 쉽게 만들어 줍니다.

Gradle을 사용하고 있으면, ./gradlew bootRun을 사용해서 어플리케이션을 기동할수 있습니다.
또는 ./gradlew build를 사용해서 JAR 파일로 빌드 할수 있습니다. 그리고 나서 JAR 파일을 기동할수 있습니다.

java -jar build/libs/gs-reactive-rest-service-0.1.0.jar

Maven을 사용하고 있으며, ./mvnw spring-boot:run 을 사용해서 어플리케이션을 기동할 수 있습니다.
또는 ./mvnw clean package을 가지고 JAR 파일로 빌드 할수 있습니다. 그리고 나서, JAR 파일을 기동 할 수 있습니다.

java -jar target/gs-reactive-rest-service-0.1.0.jar

위의 프로시저는 실행가능한 JAR를 생성할것입니다.
여러분은 또한 클래식한 WAR 파일로도 만들수 있습니다.

아웃풋을 로깅하는 것을 보여줍니다.
이 서비스는 실행되어 있고, 수초 내로 동작합니다.
한번 서비스가 기동하면, 여러분들은 아래 메세지를 볼수 있답니다.

>> result = Hello, Spring!

위 라인은 WebClient에 의해 처리되어진 reactive 컨텐츠로 부터 왔습니다.
일반적으로, 여러분은 System.out에 출력한 것 보다 여러분의 출력을 가지고 보다 더 재미있는 것을 찾을수 있습니다.

Test the Application

이제 어플리케이션은 돌아가고 있고, 여러분은 그걸 테스트 할 수 있습니다.
브라우저를 열고, http://localhost:8080/hello 로 이동하면, "Hello, Spring!"을 볼수 있습니다.
이 가이드를 위해, 우리는 테스트 클래스도 생성했습니다.
WebTestClient 클래스를 가지고 테스트 하는 것을 여러분이 시작할 수 있도록 

src/test/java/hello/GreetingRouterTest.java
package com.ksh.reactive.testwebservice;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

@RunWith(SpringRunner.class)
// we create a @SpringBootTest, starting an actual server on a 'RANDOM_PORT'
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestwebserviceApplicationTests {

   // Spring Boot will create a WebTestClient for you
   // , already configure and ready to issue request against "localhost:RANDOM_PORT"
   @Autowired
   private WebTestClient webTestClient;

   @Test
   public void testHello() {
      webTestClient
         // Create a GET request to test an endpoint
         .get().uri("/hello")
         .accept(MediaType.TEXT_PLAIN)
         .exchange()
         // and use the dedicated DSL to test assertion against the response
         .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, Spring!");
   }

}
Summary

 축하해, RESTful 서비스를 처리하는 WebClient가 포함된 Reactive Spring application을 개발했어.



-----------------------------
역시 전혀 어렵지는 않다.. 역시 Spring Boot로 하게 된다면,
하지만 Mono 와 Flux 에 대한 이해도 필요하고, 거기에 앞서서, 
Reactive 에 대한 개념도 좀 더 필요하다.

끝....