1. API Docs가 필요한 이유 - Swagger란?
본인이 맛집 서비스를 구현한다고 가정해봅시다.
그러면 대한민국 지도 데이터( 도/시, 군/구, 읍/면/동 ), 마커 표시 (위도/경도, 아이콘 ) 등을 모두 구현해야 할 것입니다.
하지만 이미 잘 구현된 라이브러리가 공개되어 있다면?
즉, Kakao 지도 API, Naver 지도 API 등을 사용하면 시간도 절약할 수 있고, 완성도가 높은 서비스를 나의 서비스에 적용시킬 수 있습니다.
API Documents는 API 사용 방법을 사용자(end user)에게 알려주는 문서입니다.
API를 잘 사용하기 위해서는 API 문서를 잘 읽어야 합니다.
위에서 언급한 API뿐만 아니라, 외부에 공개하는 모든 API들은 Documents가 존재합니다.
- 요청 URL
- 해당 API에 대한 설명
- 헤더 정보
- 요청 파라미터
- 응답 데이터
- 요청 파라미터와 응답 데이터에 대한 설명
- API 호출/응답 샘플
- ...
이러한 정보들을 제공해주는 문서는 잘 설명되어 있어야 사용자들이 문의 없이 API를 잘 사용할 수 있을 것입니다.
또한 MSA에서는 API로 주로 통신하기 때문에, 문서화가 잘 되어 있어야 합니다.
API docs는 이처럼 중요하지만, API 스펙을 문서로 관리하기가 귀찮은 일입니다.
개인 프로젝트라면 디자인도 깔끔하게 안나오고, 문서화에 시간을 쓰고 싶지도 않고요...
이렇게 귀찮은 일은 자동화 시키는게 훌륭한 방법인데요, 이를 위한 라이브러리가 Swagger 입니다.
Sprinboot에서 Swagger를 사용하면, 컨트롤러에 명시된 어노테이션을 해석하여 API 문서를 자동으로 만들어줍니다.
또한 Swagger에서 만들어주는 docs 페이지에서 테스트까지 할 수 있으므로 가볍게 적용하기에 좋은 것 같습니다.
( 개인적으로는 토이프로젝트나 사내용 수준으로 적용하기는 괜찮을것 같습니다. )
참고로 Swagger는 Java에 종속된 라이브러리는 아니고, Node.js( swagger-ui-express , swagger-jsdoc ), Laravel ( L5 Swagger ) 등...
언어마다 다양한 에코시스템 프로젝트가 있습니다. ( 참고 )
2. 의존성 추가
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
swagger를 사용하기 위해서는 springfox-swagger2를 의존성에 추가해야 합니다.
추가적으로 문서를 예쁘게 보여주고, 테스트 기능을 위해서 springfox-swagger-ui 의존성도 추가하겠습니다.
3. Swagger 설정
swagger 설정을 위해 SwaggerConfig 클래스를 생성합니다. ( 클래스명은 마음대로 작성 가능 )
com.victloee.swaggerexam.config.SwaggerConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
private String version;
private String title;
@Bean
public Docket apiV1() {
version = "V1";
title = "victolee API " + version;
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.groupName(version)
.select()
.apis(RequestHandlerSelectors.basePackage("com.victolee.swaggerexam.api.v1"))
.paths(PathSelectors.ant("/v1/api/**"))
.build()
.apiInfo(apiInfo(title, version));
}
@Bean
public Docket apiV2() {
version = "V2";
title = "victolee API " + version;
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.groupName(version)
.select()
.apis(RequestHandlerSelectors.basePackage("com.victolee.swaggerexam.api.v2"))
.paths(PathSelectors.ant("/v2/api/**"))
.build()
.apiInfo(apiInfo(title, version));
}
private ApiInfo apiInfo(String title, String version) {
return new ApiInfo(
title,
"Swagger로 생성한 API Docs",
version,
"www.example.com",
new Contact("Contact Me", "www.example.com", "foo@example.com"),
"Licenses","www.example.com",
new ArrayList<>());
}
}
- @EnableSwagger2
- Swagger2 버전을 활성화 하겠다는 어노테이션입니다.
- Docket
- Swagger 설정의 핵심이 되는 Bean입니다.
- API 자체에 대한 스펙은 컨트롤러에서 작성합니다.
- 설정 정보
- useDefaultResponseMessages()
- false로 설정하면, swagger에서 제공해주는 응답코드 ( 200,401,403,404 )에 대한 기본 메시지를 제거합니다.
- 불필요한 응답코드와 메시지를 제거하기 위함이며, 컨트롤러에서 명시해줄 것입니다.
- groupName()
- Docket Bean이 한 개일 경우
- 기본 값은 default이므로, 생략가능
- 여러 Docket Bean을 생성했을 경우
- groupName이 충돌하지 않아야 하므로, 여기서는 각 Docket Bean의 버전을 명시해줬습니다.
- select()
- ApiSelectorBuilder를 생성합니다.
- apis()
- api 스펙이 작성되어 있는 패키지를 지정합니다.
- 즉, 컨트롤러가 존재하는 패키지를 basepackage로 지정하여, RequestMapping( GetMapping, PostMapping ... )이 선언된 API를 문서화합니다.
- paths()
- apis()로 선택되어진 API중 특정 path 조건에 맞는 API들을 다시 필터링하여 문서화합니다.
- apiInfo()
- 제목, 설명 등 문서에 대한 정보들을 보여주기 위해 호출합니다.
- 파라미터 정보는 다음과 같습니다.
- public ApiInfo( title, description, version, termsOfServiceUrl, contact, license, licenseUrl, vendorExtensions )
Docket Bean을 설정하는 더 많은 방법은 여기를 참고해주세요!
설정만으로는 실행할 수 없지만, 각 설정의 정보들이 어디에 노출되는지 확인하기 위해 미리 swagger-ui를 가져와봤습니다.
- 1) URL에 /swagger-ui.html 으로 접근하면 보시는 swagger가 만들어주는 페이지에 접근할 수 있습니다.
- 2) Docket을 2개 생성 했으므로 2개의 스펙이 존재하며, 해당 이름은 groupName 값입니다.
- 3) ApiInfo의 title
- 4) ApiInfo의 version
- 5) ApiInfo의 description
- 6) APiInfo의 나머지 정보들
4. 컨트롤러
src/main/java/com/victolee/swaggerexam/api/v1/BoardControllerV1.java
src/main/java/com/victolee/swaggerexam/api/v2/BoardControllerV2.java
파일은 버전을 2개로 생성하기 위해 BoardControllerV2.java를 구현한 것일 뿐이고, BoardControllerV1.java와 코드가 같습니다.
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@Api(value = "BoardController V2")
@RequestMapping("/v1/api")
public class BoardControllerV1 {
@ApiOperation(value = "exam", notes = "예제입니다.")
@ApiResponses({
@ApiResponse(code = 200, message = "OK !!"),
@ApiResponse(code = 500, message = "Internal Server Error !!"),
@ApiResponse(code = 404, message = "Not Found !!")
})
@GetMapping(value = "/board")
public Map<String, String> selectOneBoard(@ApiParam(value = "게시판번호", required = true, example = "1") @RequestParam String no) {
Map<String, String> result = new HashMap<>();
result.put("author", "victolee");
result.put("content", "V1 API 내용");
return result;
}
}
- @Api
- 해당 클래스가 Swagger 리소스라는 것을 명시합니다.
- value
- 태그를 작성합니다.
- tags
- 사용하여 여러 개의 태그를 정의할 수도 있습니다.
- @ApiOperation
- 한 개의 operation(즉 API URL과 Method)을 선언합니다.
- value
- API에 대한 간략한 설명(제목같은 느낌으로)을 작성합니다.
- notes
- 더 자세한 설명을 작성해줍니다.
- @ApiResponse
- operation의 가능한 response를 명시합니다.
- code
- 응답코드를 작성합니다.
- message
- 응답에 대한 설명을 작성합니다.
- responseHeaders
- 헤더를 추가할 수도 있습니다.
- @ApiParam
- 파라미터에 대한 정보를 명시합니다.
- value
- 파라미터 정보를 작성합니다.
- required
- 필수 파라미터이면 true, 아니면 false를 작성합니다.
- example
- 테스트를 할 때 보여줄 예시를 작성합니다.
해당 어노테이션은 Annotations 1.5X 문서와 Swagger 2.X Annotations 문서를 참고했습니다.
모든 어노테이션을 다룰수는 없을므로, 더 많은 정보는 문서를 참고해주세요!
위에서 작성한 operation을 swagger-ui에서 확인해보면 다음과 같습니다.
http://localhost:8080/v2/api-docs URL에서도 API 스펙을 확인할 수 있지만, 시각적으로 불편하므로 swagger-ui로 확인합니다.
여기서 초록색 버튼의 "Try it out" 버튼을 클릭하면 실제 API를 바로 테스트 해볼 수 있습니다.
5. response를 글로벌 메시지로 설정
각 operation마다 response를 작성해주는 것은 귀찮을 수 있습니다.
그래서 SwaggerConfig 파일에서 globalResponseMessage로 모든 operation에 공통된 응답 메시지를 작성해줄 수도 있습니다.
개인적으로는 컨트롤러에 있는 것보다 설정 파일에 있는 것이 더 깔끔해보이긴하네요.
com.victloee.swaggerexam.config.SwaggerConfig.java
@Bean
public Docket apiV1() {
version = "V1";
title = "victolee API " + version;
List<ResponseMessage> responseMessages = new ArrayList<>();
responseMessages.add(new ResponseMessageBuilder()
.code(200)
.message("OK ~~")
.build());
responseMessages.add(new ResponseMessageBuilder()
.code(404)
.message("Not Found ~~")
.build());
responseMessages.add(new ResponseMessageBuilder()
.code(500)
.message("Internal Server Error ~~")
.build());
return new Docket(DocumentationType.SWAGGER_2)
.groupName(version)
.select()
.apis(RequestHandlerSelectors.basePackage("com.victolee.swaggerexam.api.v1"))
.paths(PathSelectors.ant("/v1/api/**"))
.build()
.apiInfo(apiInfo(title, version))
.globalResponseMessage(RequestMethod.GET, responseMessages);
}
이상으로 API Docs를 Swagger로 자동 생성하는 방법에 대해 알아보았습니다.
이글에서는 기본적인 것들을 알아본 것이고, https://editor.swagger.io/ 에서 완성된 모습을 볼 수 있습니다.
또한 editor에서 작성하면 .zip 파일로 압축된 java 파일도 생성해주는데요, 참고하셔도 좋을 것 같습니다.
[참고 자료]
https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X
https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations
https://yookeun.github.io/java/2017/02/26/java-swagger/