spring rest-doc-2015-11

61
Spring REST Docs 병욱 2015.11 Platform Architecture

Upload: eric-ahn

Post on 16-Apr-2017

739 views

Category:

Technology


6 download

TRANSCRIPT

Spring REST Docs안 병욱

2015.11 Platform Architecture 팀

references• github : https://github.com/spring-projects/spring-restdocs

• Docs : http://docs.spring.io/spring-restdocs/docs/current/reference/html5/

• API Guide : http://docs.spring.io/spring-restdocs/docs/current/samples/restful-notes/api-guide.html

• blog :

• https://www.opencredo.com/2015/07/28/rest-api-tooling-review/

• http://info.michael-simons.eu/2015/11/05/documenting-your-api-with-spring-rest-docs/

• Slide :https://speakerdeck.com/ankinson/documenting-restful-apis-webinar by Andy Wilkinson, Pivotal

ref : https://www.opencredo.com/2015/07/28/rest-api-tooling-review/

Spring REST Docs

http://docs.spring.io/spring-restdocs/docs/1.0.x/reference/html5/#getting-started

Introduction• 2015/11

• 1.0.0 current, GA

• 1.0.1 snapshot

• asciidoctor 사용

• Spring MVC Test로 작성 :

• http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#spring-mvc-test-framework

http://docs.spring.io/spring-restdocs/docs/1.0.x/reference/html5/#getting-started

asciidoctor• http://asciidoctor.org/

• plain text을 HTML5, DocBook5 등으로 변환

• Ruby로 작성됨.

• Syntax : http://asciidoctor.org/docs/user-manual/

asciidoctor 사용 법$ vi test.adoc

= Hello, AsciiDoc! Doc Writer <[email protected]>

An introduction to http://asciidoc.org[AsciiDoc].

== First Section

* item 1 * item 2

[source,ruby] puts "Hello, World!”

$ asciidoctor test.doc -o test.html

Spring REST Docs : Getting started

Sample Application

• Spring Data REST : • https://github.com/spring-projects/spring-restdocs/tree/master/samples/rest-notes-spring-data-rest

• Spring HATEOAS : • https://github.com/spring-projects/spring-restdocs/tree/master/samples/rest-notes-spring-hateoas

• HATEOAS : Hypermedia As the Engine Of Application State

• api-guide.adoc : API guide

• ApiDocumentation.java

• getting-started-guide.adoc : getting started guide

• GettingStartedDocumentation.java

• Spring Data REST

Build configuration

• Grade

• Maven

Spring Data REST sample => 생성된 문서의 HTML은?

http://docs.spring.io/spring-restdocs/docs/current/samples/restful-notes/api-guide.html

<dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <version>1.0.0.RELEASE</version> <scope>test</scope> </dependency>

<properties> <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory> </properties>

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Documentation.java</include> </includes> </configuration> </plugin> <plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.2</version> <executions> <execution> <id>generate-docs</id> <phase>package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${snippetsDirectory}</snippets> </attributes> </configuration> </execution> </executions> </plugin> </plugins> </build>

Spring Data REST sample : pom.xml

<dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <version>1.0.0.RELEASE</version> <scope>test</scope> </dependency>

<properties> <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory> </properties>

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/*Documentation.java</include> </includes> </configuration> </plugin>

1) test scope에 spring-restdocs-mockmvc 추가

2) 생성된 snippet 의 출력 위치

3) mvn test 시 사용하는 SureFire plugin 추가하고, Documentation.java 파일 찾아서 테스트 코드 실행

<plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.2</version> <executions> <execution> <id>generate-docs</id> <phase>package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${snippetsDirectory}</snippets> </attributes> </configuration> </execution> </executions> </plugin> Add the Asciidoctor plugin

4) Asciidoctor plugin 추가

5) snippets 디렉토리 설정

6) jar 파일내에서 문서를 package하려면… prepare-package 표현식을 사용하면 됨.

Generating documentation snippets

• Spring REST Docs은 MVC Test framework을 사용

• MVC test framework doc

• http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#spring-mvc-test-framework

Setting up Spring MVC Test

@Rulepublic final RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets");

@Autowired private WebApplicationContext context;

private MockMvc mockMvc;

@Beforepublic void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); }

1) snippets을 생성하기위해 @Rule annotation을 이용, RestDocumentation 을 선언, “build/generated-snippets” pom.xml 에서 선언되어있음

2) MockMvc instance를 생성키위해 @Before에서 setUp() method 선언, MockMvc의 instance는 RestDocumentationMockMvcConfigurer 를 사용해서 설정할 수 있는데, static documentationConfiguration() method로 얻을 수 있다. documentationConfiguration의 method들?

@Rulepublic final RestDocumentation restDocumentation = new RestDocumentation("build/generated-snippets");

@Autowired private WebApplicationContext context;

private MockMvc mockMvc;

@Beforepublic void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .build(); }

documentationConfiguration은 URIs 설정할 수 있는데, 아래와 같이 사용이 가능하다. documentationConfiguration(this.restDocumentation).uris() .withScheme(“https”) .withHost("example.com") .withPort(443)

this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(document("index"));

Invoking the RESTful service

1) root(/) 호출하고, application/json response 설정

2) 예상 결과를 위해 Assert

3) 서비스를 호출하고, 디렉토리에 snippets을 기록, “index”는 sub-path. RestDocumentationResultHandler 로 기록됨. 이 클래스의 인스턴스는org.springframework.restdocs.mockmvc.MockMvcRestDocumentation 의 static document method 에서 얻을 수 있음.

기본적으로, 세 가지 snippets이 기록된다. 1) <output-directory>/index/curl-request.adoc 2) <output-directory>/index/http-request.adoc 3) <output-directory>/index/http-response.adoc

pom.xml 의 <configuration> <backend>html</backend> <doctype>book</doctype> <attributes> <snippets>${snippetsDirectory}</snippets> </attributes> </configuration>

asciidoc 에서 아래와 같이 사용할 수 있다. include::{snippets}/index/curl-request.adoc[]

Using the snippets<snippets> attribute 에 지정된 값이 출력 디렉토리임.

Documenting your API• Request and response payloads

• JSON payloads • XML payloads

• Request parameters • Path parameters • HTTP headers • Documenting constraints • Default snippets • Using parameterised output directories • Customising the output • Hypermedia

2) Request and response payloads

this.mockMvc.perform(get("/user/5").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(document("index", responseFields( fieldWithPath("contact").description("The user's contact details"), fieldWithPath("contact.email").description("The user's email address"))));

response payload에 field를 기술하는 snippet을 생산하기위해 responseFields() 사용

For requests : request-fields.adoc For response : response-fields.adoc

“contact.email” path를 가진 field를 예상함.field를 기술하는

table을 포함하는 snippet

failed? undocumented field가 payload 에서 발견되는 경우, documented filed가 payload에 없는 경우

JSON payloads

{ "a":{ "b":[ { "c":"one" }, { "c":"two" }, { "d":"three" } ], "e.dot" : "four" } }

path내에 각 키를 구별하기위해 . 을 사용

[] : 각 키를 ‘ (single quotes)로 감싼다.

.andDo(document("index", responseFields( fieldWithPath("contact.email") .type(JsonFieldType.STRING) .optional() .description("The user's email address"))));

사용 법 : string 타입은 JsonFieldType.STRING enum을 사용

JSON field types

this.mockMvc.perform(get("/users?page=2&per_page=100")) .andExpect(status().isOk()) .andDo(document("users", requestParameters( parameterWithName("page").description("The page to retrieve"), parameterWithName("per_page").description("Entries per page") )));

1) GET 요청으로 page와 per_page 파라미터를 요청

request-parameters.adoc

3) Request parameters

2) requestParameters() 를 사용

3) page 파라미터, parameterWithName() method를 사용

parameter를 기술하는 table을 포함하는 snippet

this.mockMvc.perform(post("/users").param("username", "Tester")) .andExpect(status().isCreated()) .andDo(document("create-user", requestParameters( parameterWithName("username").description("The user's username") )));

1) username 을 가진 POST request임

request-parameters.adocfailed? undocumented request parameter가 request 에서 사용되는 경우, documented request parameter가 request에 없는 경우

parameter를 기술하는 table을 포함하는 snippet

4) Path parameters

this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072, 0.1275)) .andExpect(status().isOk()) .andDo(document("locations", pathParameters( parameterWithName("latitude").description("The location's latitude"), parameterWithName("longitude").description("The location's longitude") )));

1) latitude, longitude의 두 path parameter

path-parameters.adoc

2) pathParameters() method 사용

path parameters를 기술하는 table을 포함하는 snippet

5) HTTP headers

• request => requestHeaders() method

• response => responseHeaders() method

this.mockMvc

.perform(get("/people").header("Authorization", "Basic dXNlcjpzZWNyZXQ=")) .andExpect(status().isOk())

.andDo(document("headers",

requestHeaders(

headerWithName("Authorization").description(

"Basic auth credentials")),

responseHeaders(

headerWithName("X-RateLimit-Limit").description(

"The total number of requests permitted per period"),

headerWithName("X-RateLimit-Remaining").description(

"Remaining requests permitted in current period"),

headerWithName("X-RateLimit-Reset").description(

"Time at which the rate limit period will reset"))));

1) Authorization header를 가진 GET request

request-headers.doc, response-headers.adoc

2) requestHeaders method 사용하고, headerWithName method로 문서화 할 수 있다.

3) responseHeaders method 사용

headers를 기술하는 table을 포함하는 snippet

6) Documenting constraints

• constraints를 문서화할때 유용하다.

• ConstraintDescriptions의 instance 를 이용한다.

public void example() {

ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class);

List<String> descriptions = userConstraints.descriptionsForProperty("name"); }

static class UserInput {

@NotNull

@Size(min = 1)

String name;

@NotNull

@Size(min = 8) String password;

1) ConstraintDescriptions의 instance를 생성

2) name property의 constraint를 가져온다. List 에는 NotNull 조건과 Size 조건이 있다.

Bean Validation 1.1의 constraints 모두 지원한다.

AssertFalse, AssertTrue, DecimalMax, DecimalMin, Digits, Future, Max, Min, NotNull, Null, Past, Pattern, Size

.andDo(document("create-user", requestFields( attributes( key("title").value("Fields for user creation")), fieldWithPath("name") .description("The user's name") .attributes( key("constraints").value("Must not be null. Must not be empty")), fieldWithPath("email") .description("The user's email address") .attributes( key("constraints").value("Must be a valid email address")))));

request field의 extra information으로 constraints를 문서화도 가능함

name field에 “constraints” 속성을 설정할 수 있다.

7) Default snippets

• snippets들은 MockMvc.perform 을 호출할때 자동적으로 만들어지고, 설정할 수 도 있다.

8) Using parameterised output directories

• output 디렉토리를 위해 아래 parameter를 지원.

_ ( userscore) 를 사용

-(dash) 를 사용

@Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)) .alwaysDo(document("{method-name}/{step}/")) .build(); } alwaysDo 를 사용하고, method-name이

creatingANote 라면, 디렉토리는 creating-a-note/1/ 이라는 이름에 snippet이 저장된다.

9) Customising the output

• 9-1) Spring REST Docs : Mustache template를 사용

• ./spring-restdocs-core/build/resources/main/org/springframework/restdocs/templates/*.snippet

• curl-request.adoc 을 위해 template 을 사용하려면,

• src/test/resources/org/springframework/restdocs/templates/curl-request.snippet 을 만들어서 사용하면 됨.

.andDo(document("create-user", requestFields( attributes( key("title").value("Fields for user creation")), fieldWithPath("name") .description("The user's name") .attributes( key("constraints").value("Must not be null. Must not be empty")), fieldWithPath("email") .description("The user's email address") .attributes( key("constraints").value("Must be a valid email address")))));

9-2) including extra information

1) attributes() method를 사용하고, “title” attribute를 설정

2) “name” 의 constraints를 설정

3) “email” 의 constraints를 설정

custom template : request-fields.snippet 설정

custom template : request-fields.snippet .{{title}} |=== |Path|Type|Description|Constraints

{{#fields}} |{{path}} |{{type}} |{{description}} |{{constraints}}

{{/fields}} |===

1) 테이블에 title 추가

2) 새로운 column “Constraints” 추가

3) 테이블의 각 row 에 “constraints” 속성의 설명을 포함

this.mockMvc.perform(get("/")) .andExpect(status().isOk()) .andDo(document("index", preprocessRequest(removeHeaders("Foo")), preprocessResponse(prettyPrint())));

1) Preprocessing : document() 를 호출하면 된다. OperationRequestPreprocessor Instance는 preprocessRequest() 를 사용하면됨. 2) “Foo”라는 header를 삭제

Customizing requests and responses

모든 테스트에 동일한 preprocessor 적용하기 :

@Before public void setup() { this.document = document("{method-name}", preprocessRequest(removeHeaders("Foo")), preprocessResponse(prettyPrint())); this.mockMvc = MockMvcBuilders .webAppContextSetup(this.context) .alwaysDo(this.document) .build(); }

1) RestDocumentationResultHandler instance는 document() 로 생성할 수 있다. 2) Header “Foo” 삭제

MockMvc instance 생성

this.document.snippets( links(linkWithRel("self").description("Canonical self link"))); this.mockMvc.perform(get("/")) .andExpect(status().isOk());

테스트하려는 리소스에 링크를 지정

perform()은 setup()에서 alwasyDo()를 사용했기 때문에, 자동으로 문서를 만듬

Configuration• Documented URIs

• Spring Rest Docs의 기본 설정

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).uris() .withScheme("https") .withHost("example.com") .withPort(443)) .build();

RestDocumentationMockMvcConfigurer 사용하거나 documentationConfiguration()를 호출 한다.

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).snippets() .withEncoding("ISO-8859-1")) .build();

Asciidoctor에서 사용하는 Default encoding은 UTF-8 RestDocumentationMockMvcConfigurer 을 사용해서 설정할 수 있고, documentationConfiguration() Method를 이용하면 됨.

Snippet encoding

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation).snippets() .withDefaults(curlRequest())) .build();

Default snippets : 3 가지가 있다. curl-request http-request http-response

예: curl-request snippet 을 만들기 위해서는 withDefaults(curlRequest()) 설정

Default snippets

Working with Asciidoctor

• asciidoctor syntax

• http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/

• asciidoctor user manual

• http://asciidoctor.org/docs/user-manual

1) including snippets

• 특정 snippet을 포함시킬 수 있음.

• include::{snippets}/index/curl-request.adoc[]

2) Customising tables

• Formatting columns

• Configuring the title

• User manual :

• http://asciidoctor.org/docs/user-manual/#tables

[cols=1,3] include::{snippets}/index/links.adoc[]

테이블은 두 개 컬럼 두번째 컬럼이 3배 넓다

2-1) Formatting columns

2-2) Configuring the title

.Links include::{snippets}/index/links.adoc[]

테이블의 제목 : “Links” 임.

데모

spring-boot 설치

• 추천 버전

• Spring Boot v1.1.4 release

• Mac OS X 환경에 설치 $ brew tap pivotal/tap $ brew install springboot

http://docs.spring.io/autorepo/docs/spring-boot/1.1.4.RELEASE/reference/html/getting-started-installing-spring-boot.html

java & mvn & intelliJ• 최소 스펙

• Java : 1.6 +

• maven : 3.0 +

• Mac OS X

• java : 1.7.0_79

• maven : 3.2.5

• intelliJ IDEA 14.1.5+

~/spring-restdocs/samples/rest-notes-spring-data-rest/src/main/asciidoc $ ls -rw-r--r-- 1 EricAhn staff 5.8K Nov 12 15:21 api-guide.adoc -rw-r--r-- 1 EricAhn staff 5.8K Nov 12 15:21 getting-started-guide.doc

$ asciidoctor api-guide.doc

=> generated api-guide.html

?

원하는 결과 :

… [[overview-errors]] == Errors

Whenever an error response (status code >= 400) is returned, the body will contain a JSON object that describes the problem. The error object has the following structure:

include::{snippets}/error-example/response-fields.adoc[]

For example, a request that attempts to apply a non-existent tag to a note will produce a `400 Bad Request` response:

include::{snippets}/error-example/http-response.adoc[]

[[overview-hypermedia]] …

api-guide.doc

MockHttpServletRequest: HTTP Method = GET Request URI = /error Parameters = {} Headers = {}

Handler: Type = org.springframework.boot.autoconfigure.web.BasicErrorController Method = public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)

Async: Async started = false Async result = null

Resolved Exception: Type = null

ModelAndView: View name = null View = null Model = null

FlashMap:

MockHttpServletResponse: Status = 400 Error message = null Headers = {Content-Type=[application/json;charset=UTF-8]} Content type = application/json;charset=UTF-8 Body = { "timestamp" : 1447748756746, "status" : 400, "error" : "Bad Request", "message" : "The tag 'http://localhost:8080/tags/123' does not exist", "path" : "/notes"} Forwarded URL = null Redirected URL = null Cookies = []

Process finished with exit code 0

IntelliJ Output :

~/spring-restdocs/samples/rest-notes-spring-data-rest/target/generated-snippets/error-example$ ls -la total 32 drwxr-xr-x 6 EricAhn staff 204 Nov 17 17:25 . drwxr-xr-x 3 EricAhn staff 102 Nov 17 17:25 .. -rw-r--r-- 1 EricAhn staff 63 Nov 17 17:25 curl-request.adoc -rw-r--r-- 1 EricAhn staff 60 Nov 17 17:25 http-request.adoc -rw-r--r-- 1 EricAhn staff 287 Nov 17 17:25 http-response.adoc -rw-r--r-- 1 EricAhn staff 340 Nov 17 17:25 response-fields.adoc

$ cd spring-restdocs/samples/rest-notes-spring-data-rest/src/main/asciidoc $ cp -R target/generated-snippets/error-example . $ ls drwxr-xr-x 7 EricAhn staff 238 Nov 17 22:14 . drwxr-xr-x 5 EricAhn staff 170 Nov 12 15:21 .. -rw-r--r-- 1 EricAhn staff 5985 Nov 17 22:23 api-guide.adoc drwxr-xr-x 6 EricAhn staff 204 Nov 17 22:12 error-example -rw-r--r-- 1 EricAhn staff 5888 Nov 12 15:21 getting-started-guide.adoc

api-guide.doc

test를 위해 snippets 는 현재 디렉토리로 설정하기위해 추가

$ asciidoctor api-guide.adoc

$ ls drwxr-xr-x 7 EricAhn staff 238 Nov 17 22:14 . drwxr-xr-x 5 EricAhn staff 170 Nov 12 15:21 .. -rw-r--r-- 1 EricAhn staff 5985 Nov 17 22:23 api-guide.adoc -rw-r--r-- 1 EricAhn staff 52057 Nov 17 22:13 api-guide.html drwxr-xr-x 6 EricAhn staff 204 Nov 17 22:12 error-example -rw-r--r-- 1 EricAhn staff 5888 Nov 12 15:21 getting-started-guide.adoc

=> Open browser : api-guide.html

response-fields.adoc

http-response.adoc

리뷰?

• Spring HATEOAS sample

• XML payload

• asciidoctor manual