Introducing
Spring Auto REST Docs
Florian Benz
@flbenz
@spring_io
#springio17
DR REST DOCS
OR: HOW I LEARNED TO STOP WORRYING
AND LOVE DOCUMENTATION
@spring_io
#springio17
Florian Benz
Software Engineer
@flbenz
@spring_io
#springio17
Scalable Capital
• Europe’s fastest growing Digital Wealth Manager
• Authorized financial institute in Germany and the UK
• From scratch with Spring Boot
• Joined effort with Juraj Misur @juraj_misur
@spring_io
#springio17
Spring Auto REST Docs
Scalable Capital
founded
Dec 2014
Proof of concept
Jul 2015
First article
Nov 2015
@spring_io
#springio17
Spring Auto REST Docs
Open source
&
First release
Dec 2016
DZone article
Jan 2017
Two releases
with fixes and
features
Feb & Mar 2017
@spring_io
#springio17
Our Story
@spring_io
#springio17
Manual Documentation
DocumentationCode
@spring_io
#springio17
A single big document
@spring_io
#springio17
Specification-Driven
Documentation
DocumentationCode
Specification
@spring_io
#springio17
RAML Specification
/weather:
get:
queryParameters:
city:
description: Name of a city in the given country.
responses:
200:
body:
application/json:
schema: |
{ "$schema": "http://json-schema.org/schema",
"type": "object",
"description": "Weather information",
"properties": {
"temperature": { "type": "number" }
}
}
@spring_io
#springio17
Swagger / OpenAPI
@GetMapping("weatherParam")
@ApiOperation("weather")
@ApiImplicitParams({
@ApiImplicitParam(name = "country", value = "Country code", required = true,
dataType = "string", paramType = "query"),
@ApiImplicitParam(name = "city", value = "City", required = true,
dataType = "string", paramType = "query")
})
@ApiResponses({
@ApiResponse(code = 200, message = "Success", response =
WeatherResponse.class)
})
public WeatherResponse weatherParam(@RequestParam @IsoCountryCode String
country,
@RequestParam String city) {
return new WeatherResponse(20);
}
@spring_io
#springio17
Swagger / OpenAPI
@spring_io
#springio17
Postman
@spring_io
#springio17
Test-Driven Documentation
DocumentationCode
Tests
@spring_io
#springio17
Spring REST Docs
Generated
snippets
Tests
Hand-written
documentation
Documentation
@spring_io
#springio17
Spring MVC Test
@Test
public void shouldReturnWeatherForBarcelona() throws Exception {
mockMvc.perform(
post("/weather")
.contentType(MediaType.APPLICATION_JSON)
.content("{"country": "ES", "city": "Barcelona"}")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.temperature", is(20)));
}
@spring_io
#springio17
Spring REST Docs
@Test
public void shouldReturnWeatherForBarcelona() throws Exception {
mockMvc.perform(
post("/weather")
.contentType(MediaType.APPLICATION_JSON)
.content("{"country": "ES", "city": "Barcelona"}")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.temperature", is(20)));
.andDo(document("weather",
requestFields(
fieldWithPath("country").description("Country code"),
fieldWithPath("city").description("City name"))));
}
@spring_io
#springio17
Generated snippet
|===
|Path|Type|Optional|Description
|country
|String
|false
|Country Code.
|city
|false
|true
|City name.
|===
@spring_io
#springio17
AsciiDoc
[[resources-weather]]
= Weather for your city
`POST /weather`
Up-to-date temperature for the given city
== Response structure
include::{snippets}/weather/response-fields.adoc[]
== Example request/response
include::{snippets}/weather/curl-request.adoc[]
include::{snippets}/weather/http-response.adoc[]
@spring_io
#springio17
Spring REST Docs
@spring_io
#springio17
Spring REST Docs
@spring_io
#springio17
Spring REST Docs
Controller
POJO
Response Entity
Jackson
HTTP response Documented
@spring_io
#springio17
Extending
Spring REST Docs
@spring_io
#springio17
Motivation
.andDo(document("weather",
requestFields(
fieldWithPath("country").description("Country code"),
fieldWithPath("city").description("Name of a city"))));
We are lazy
@spring_io
#springio17
Proof of concept
@spring_io
#springio17
Spring Auto REST Docs
Controller
POJO
Response Entity
Jackson
HTTP response
Javadoc
Introspection
@spring_io
#springio17
Spring REST Docs
@Test
public void shouldReturnWeatherForBarcelona() throws Exception {
mockMvc.perform(
post("/weather")
.contentType(MediaType.APPLICATION_JSON)
.content("{"country": "ES", "city": "Barcelona"}")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.temperature", is(20)));
.andDo(document("weather",
requestFields(
fieldWithPath("country").optional().description("Country code"),
fieldWithPath("city").optional().description("City name"))));
}
@spring_io
#springio17
Spring Auto REST Docs
@Test
public void shouldReturnWeatherForBarcelona() throws Exception {
mockMvc.perform(
post("/weather")
.contentType(MediaType.APPLICATION_JSON)
.content("{"country": "ES", "city": "Barcelona"}")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.temperature", is(20)));
.andDo(document("weather"));
}
@spring_io
#springio17
Javadoc
class WeatherRequest {
/**
* Country code.
*/
private String country;
/**
* City name.
*/
private String city;
}
Path Type Optional Description
country String true Country code.
city String true City name.
@spring_io
#springio17
Constraints
class WeatherRequest {
/**
* Country code, e.g. ES, DE, US.
*/
@NotNull
@IsoCountryCode
private String country;
/**
* City name.
*/
@NotBlank
private String city;
}
Path Type Optional Description
country String false Country code.
Must be an ISO country code.
city String false City name.
@spring_io
#springio17
Constraints
package.OneOf.description=Must be one of ${value}
package.IsoCountryCode.description=Must be an ISO country code
ConstraintDesciptions.properties
@spring_io
#springio17
Constraints
class WeatherRequest {
/**
* Country code, e.g. ES, DE, US.
*/
@NotNull
@IsoCountryCode(groups = Iso.class)
@CountryName(groups = Plain.class)
private String country;
}
Path Type Optional Description
country String false Country code.
ISO: Must be an ISO country code.
Plain: Must be a country name.
@spring_io
#springio17
Enums
class WeatherRequest {
/**
* Country code, e.g. ES, DE, US.
*/
@NotNull
private Country country;
/**
* City name.
*/
@NotBlank
private String city;
}
Path Type Optional Description
country String false Country code.
Must be one of [DE, ES, FR, PT, US].
city String false City name.
@spring_io
#springio17
Original: hand-written
2.8. Weather
POST /weather
Up-to-date weather data for cities around the globe.
[[resources-weather]]
= Weather for your city
`POST /weather`
Up-to-date temperature for the given city
@spring_io
#springio17
Extension: Javadoc on method
/**
* Up-to-date weather data for cities around the globe.
*/
@PostMapping("weather")
public WeatherResponse weather(
@RequestBody @Valid WeatherRequest weatherRequest) {
return new WeatherResponse(20);
}
2.8. Weather
POST /weather
Up-to-date weather data for cities around the globe.
@spring_io
#springio17
Original: Path Parameters
.andDo(document("weather",
pathParameters(
parameterWithName("country").description("Country code"),
parameterWithName("city").description("City name"))));
@spring_io
#springio17
Extension: Path Parameters
/**
* Up-to-date weather data for cities around the globe.
*
* @param country Country code.
* @param city City name.
*/
@GetMapping("weather/{country}/{city}")
public WeatherResponse weatherPath(
@PathVariable @IsoCountryCode String country,
@PathVariable String city) {
return new WeatherResponse(20);
}
@spring_io
#springio17
Path Parameters
@spring_io
#springio17
Path Parameters
@spring_io
#springio17
Original: Query Parameters
.andDo(document("weather",
requestParameters(
parameterWithName("country").description("Country code"),
parameterWithName("city").description("City name"))));
@spring_io
#springio17
Extension: Query Parameters
/**
* Up-to-date weather data for cities around the globe.
*
* @param country Country code.
* @param city City name.
*/
@GetMapping("weatherParam")
public WeatherResponse weatherParam(
@RequestParam @IsoCountryCode String country,
@RequestParam String city) {
return new WeatherResponse(20);
}
@spring_io
#springio17
Query Parameters
@spring_io
#springio17
Query Parameters
@spring_io
#springio17
Section Snippet
[[resources-weather]]
=== Weather for your city
`POST /weather`
Up-to-date temperature for the given city
===== Request structure
include::{snippets}/weather/request-fields.adoc[]
===== Response structure
include::{snippets}/weather/response-fields.adoc[]
===== Example request/response
include::{snippets}/weather/curl-request.adoc[]
include::{snippets}/weather/http-response.adoc[]
include::{snippets}/weather/section.adoc[]
Documentation path
Spring MVC Controller
Javadoc
Method name
@spring_io
#springio17
Authorization snippet
@spring_io
#springio17
Content Modifiers
[
1,
2,
3,
4,
5
]
[
1,
2,
3
]
array shortener
@spring_io
#springio17
Content Modifiers
%PDF-1.5
%����
12 0 obj
<<
/Length 3654
/Filter /FlateDecode
>>
<binary>
binary replacement
@spring_io
#springio17
Benefits
Less to write
Code review Maintainability
Happier developers
DRY
Accurate
@spring_io
#springio17
Sounds good!
Issues?
@spring_io
#springio17
Issues
@spring_io
#springio17
Issues
@spring_io
#springio17
Issues
@spring_io
#springio17
It’s an extension
Authorization snippet
Javadoc/introspection snippets
Content modifiers
@spring_io
#springio17
Spring Auto REST Docs
at Scalable Capital
@spring_io
#springio17
Spring Auto REST Docs
at Scalable Capital
@spring_io
#springio17
Thank you
@spring_io
#springio17
Q&A
@flbenz

Introducing Spring Auto REST Docs - Spring IO 2017