마이크로서비스에서 뿐만 아니라 모든 시스템에서 REST API를 문서화 하는 것은 매우 중요합니다.
REST API란 사용자 뿐만 아니라 다른 모듈, 응용 프로그램, 개발자들이 사용 할 수 있는 공용 인터페이스 입니다.
어떠한 API를 개발한 사람은 보통 해당 API 사용자가 아닙니다.
따라서 실제 사용자(혹은 모듈)에게 문서화해서 보여주는 것이 매우 중요합니다.
그 중 가장 많이 알려진 방법이 Swagger 입니다.
JSON 또는 YAML 메타 데이터를 이용하여 다수의 REST API를 설명 할 수 있습니다.
하지만 JSON 또는 YAML으로 사용자에게 보여주는 것은 불편하고 보기 힘들기 때문에 그에 맞는 UI도 제공합니다.
UI를 통해서 단순히 REST API 명세만 보여주는 것이 아니라 실제로 호출하고 응답을 검사 할 수 있습니다.
2. 어플리케이션에 swagger dependency 추가
REST API를 명세로 만들 즉 swagger를 통해서 제공 될 어플리케이션을 만듭니다.
기존의 customer-service와 order-service를 이용합니다.
먼저 pom.xml에 dependency를 추가합니다.
<!--swagger 기능을 제공하는 dependency 입니다.-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--swagger로 생성된 json 등의 정보를 ui로 보여주기 위한 dependency 입니다.-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
swagger 관련 config를 작성합니다.
import com.google.common.base.Predicates;
import io.swagger.annotations.Contact;
import io.swagger.annotations.Info;
import io.swagger.annotations.License;
import io.swagger.annotations.SwaggerDefinition;
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.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import static com.sds.act.coe.customer.web.rest.ApiConstants.API_V1_BASE_PATH;
@Configuration
@EnableSwagger2
@SwaggerDefinition(
info = @Info(description = "MSA-COE-CUSTOMER",
version = "1.0.0",
title = "MSA-COE-CUSTOMER",
termsOfService = "Term of Service",
contact = @Contact(
name = "Samsung SDS",
url = "https://www.samsungsds.com/",
email = "samsungsds@samsung.com"),
license = @License(name = "Apache License Version 2.0",
url = "https://www.apache.org/licenses/LICENSE-2.0")
)
)
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.boot")))
.apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.cloud")))
// .paths(PathSelectors.regex(API_V1_BASE_PATH + "/.*"))
.paths(PathSelectors.any())
.build();
}
/* @SwaggerDefinition 어노테이션 말고 아래와 같은 방법으로 정의 할 수 있습니다.
private ApiInfo getApiInfo() {
StringVendorExtension vendorExtension1 = new StringVendorExtension("name1", "value1");
StringVendorExtension vendorExtension2 = new StringVendorExtension("name2", "value2");
List<VendorExtension> vendorExtensionList = new ArrayList<>();
vendorExtensionList.add(vendorExtension1);
vendorExtensionList.add(vendorExtension2);
return new ApiInfo(
"This is swagger title",
"This is swagger description.",
"1.0.0",
"TERMS OF SERVICE URL",
new Contact("name","url","mail address"),
"Samsung SDS",
"www.samsungsds.com",
vendorExtensionList
);
}
*/
}
위에서 보다시피 swagger api에 title, description, contact user 정보 등을 추가 할 수 있습니다.
swagger ui를 통해서 개발자가 작성한 REST API 명세를 사용자가 쉽게 이해 할 수 있지만 마이크로서비스아키텍처 안에서는 몇개의 어플리케이션이 있을지 모릅니다.
따라서 각각의 어플리케이션이 제공하는 swagger 정보를 취합하는 기능이 필요합니다.
zuul gateway를 이용하거나 별도의 서버를 이용하여 구현이 가능합니다.
먼저 동일하게 gateway에 dependency를 추가합니다.
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--swagger로 생성된 json 등의 정보를 ui로 보여주기 위한 dependency 입니다.-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
customer 어플리케이션과 동일하게 SwaggerConfig 파일을 작성합니다.
import com.google.common.base.Predicates;
import io.swagger.annotations.Contact;
import io.swagger.annotations.Info;
import io.swagger.annotations.License;
import io.swagger.annotations.SwaggerDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Profile("!prod")
@Configuration
@EnableSwagger2
@SwaggerDefinition(
info = @Info(description = "MSA-COE",
version = "1.0.0",
title = "MSA-COE",
termsOfService = "Term of Service",
contact = @Contact(
name = "Samsung SDS",
url = "https://www.samsungsds.com/",
email = "samsungsds@samsung.com"),
license = @License(name = "Apache License Version 2.0",
url = "https://www.apache.org/licenses/LICENSE-2.0")
)
)
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.boot")))
.apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.cloud")))
// .paths(PathSelectors.regex("/api/.*"))
.paths(PathSelectors.any())
.build();
}
}
여기서 눈여겨 볼 점은 @Profile 어노테이션인데 개발계, 운영계에 따라서 해당 swagger 기능을 닫아야 할 필요가 존재합니다. 따라서 해당 어노테이션을 사옹하면 spring active profile에 따라서 기능을 on/off 할 수 있습니다.
그 다음 Swagger Resource들을 모으는 controller를 작성합니다.
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.List;
import java.util.stream.Collectors;
import static springfox.documentation.spi.DocumentationType.SWAGGER_2;
@Profile("!prod")
@Component
@Primary
@EnableAutoConfiguration
public class SwaggerController implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
public SwaggerController(RouteLocator routeLocator) {
this.routeLocator = routeLocator;
}
@Override
public List get() {
List<SwaggerResource> resources = routeLocator.getRoutes().stream().distinct().map(route -> {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(route.getLocation());
swaggerResource.setLocation(route.getFullPath().replace("**", "api-docs"));
swaggerResource.setSwaggerVersion(SWAGGER_2.getVersion());
return swaggerResource;
}).collect(Collectors.toList());
return resources;
}
}
RouteLocator 를 사용하여 zuul gateway에 등록 된 라우팅 정보로 부터 각 어플리케이션의 api-docs 정보를 가져옵니다. 설정 부분은 아래와 같습니다.