Springboot가 아니라 서럽다ㅏㅏㅏㅏㅏ
Spring 5.3.x에서 API 개발 중인데, Notion으로 명세하기보다는 테스트도 되는 Swagger를 꼭 쓰고 싶었다.
현재 하고있는 프로젝트가 학부 4학년 시절 했던 프로젝트를 재공학하는 과정인데 그 당시에도 Node.js에서 Swagger를 최대한 열심히 썼던 기억이 난다. 물론 yaml파일로 한땀한땀 만드느라 정말 고생했다.
Spring에서도 Swagger를 적용할 수 있는 라이브러리들이 있었다. 고전적으로는 Springfox가 있는데 현재 업데이트가 나오지 않고 있어 대부분이 Springdoc를 사용하는 것 같다.
근데 일단 지금은 Springfox를 사용해서 구현해놓은 상태이고, Spring을 좀 더 자유자재로 다룰 수 있게 되면 Springdoc로 마이그레이션하지 않을까 싶다.
저번 게시글에서도 거듭 말했듯이 boot에서는 자동으로 설정되는 것들이 Spring에서는 일일히 구현하고 상속받고 해서 빈 객체를 만들어줘야하기 때문에 공이 한 세 배정도 더 들어가는 것 같다. Security를 적용할 때 얼마나 고통받을까 상상이 되지 않는다.
Springfox도 3.0.0으로 넘어오면서 OAS 3.0으로 명세할 수 있게 되었다. 그러니까 구닥다리 취급을 하기보다는 잘 명세하도록 하자
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-oas -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-oas</artifactId>
<version>3.0.0</version>
</dependency>
너무 많은 시도를 해서 정확하지는 않을 수 있다. 특히 org.webjars.bootstrap 같은 의존성은 필요하지 않을 수 있다. 근데 내가 이걸 포함한 이유는 swagger-ui가 내부적으로 webjars의 정적 리소스들을 활용하는데 이로인해서 swagger ui를 보지 못할수도 있어서 추가해주었다.
기본적인 설정을 위해서는 한 개의 빈이 필요하다.
@Configuration
@EnableWebMvc
@EnableOpenApi
public class SwaggerConfig implements WebMvcConfigurer{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.
addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.resourceChain(false);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/swagger-ui/")
.setViewName("forward:" + "/swagger-ui/index.html");
}
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.ignoredParameterTypes(HttpSession.class)
.select()
.apis(RequestHandlerSelectors.basePackage("cuk.api"))
.paths(PathSelectors.any())
.build()
.groupName("API 1.0.0")
.useDefaultResponseMessages(false)
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Gajang API Document")
.description("오류 있으면 연락주세요")
.version("v1")
.build();
}
}
일단 Spring에서 @EnableWebMvc 태그로 자동으로 리소스들이 설정되지 않기 때문에, WebMvcConfigurer 인터페이스를 상속받아서 직접 재정의를 해준다. 그러고나서 Swagger Config를 작성한다.
여기서 api() 메소드 내에 ignoredparameterTypes에 HttpSession이 들어있는 이유는 자동 명세를 작성할 때, HttpSession이 파라미터에 들어있는 경우 이것마저 명세해버리는 문제가 있다...
나머지는 대충 알 것이라 예상하는데
.apis()는 컨트롤러들이 있는 패키지를 탐색할건데 어디 범위에서 탐색할건지를 정하고
.paths()는 어떤 url에 대해서 명세할건지를 정할 수 있다. 지금은 모든 url에 대해서 명세하고 있다.
문제는 이상태로 swagger를 실행하면, baseurl이 Localhost가 된다. 만약 이 상태로 웹 앱을 톰캣에 띄우더라도 swagger에 들어가면 내 서버 주소가 아니라 localhost가 된다.
이건 문제가 될 수 있는데 시놀로지같은 NAS 도커에서 띄울경우 localhost:8080포트는 웹 앱 자신으로 포워딩 되지 않기 때문에 테스트고 뭐고 아무것도 못한다.
그래서 이걸 방지하기 위해서 local 개발용으로 하나, 배포되었을 때 하나 이렇게 baseurl을 설정할 수 있다.
아래는 업그레이드된 Configuration이다. 추가적으로 빈이 하나 더 필요하다.
@Configuration
@EnableWebMvc
@EnableOpenApi
public class SwaggerConfig implements WebMvcConfigurer{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.
addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.resourceChain(false);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/swagger-ui/")
.setViewName("forward:" + "/swagger-ui/index.html");
}
@Bean
public Docket api() {
Server publicServer = new Server("Public", "https://내 서버 주소", "실제 api url", Collections.emptyList(), Collections.emptyList());
Server localServer = new Server("Local", "http://localhost:8080", "로컬 테스트", Collections.emptyList(), Collections.emptyList());
return new Docket(DocumentationType.OAS_30)
.servers(publicServer, localServer)
.ignoredParameterTypes(HttpSession.class)
.select()
.apis(RequestHandlerSelectors.basePackage("cuk.api"))
.paths(PathSelectors.any())
.build()
.groupName("API 1.0.0")
.useDefaultResponseMessages(false)
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Gajang API Document")
.description("오류 있으면 연락주세요")
.version("v1")
.build();
}
}
이렇게 Server를 두 개 만들고 api() 내에서 등록해준다.
그리고 Swagger filter를 하나 생성하여 경우에 아까 주소를 변환해주는 형식에 맞게 변환해주는 방법이 있다.
@Component
public class SwaggerFilter implements WebMvcOpenApiTransformationFilter {
@Override
public OpenAPI transform(OpenApiTransformationContext<HttpServletRequest> context) {
OpenAPI openApi = context.getSpecification();
Server localServer = new Server();
localServer.setDescription("local");
localServer.setUrl("http://localhost:8080");
Server testServer = new Server();
testServer.setDescription("server");
testServer.setUrl("https://내 서버 주소");
openApi.setServers(Arrays.asList(localServer, testServer));
return openApi;
}
@Override
public boolean supports(DocumentationType documentationType) {
return documentationType.equals(DocumentationType.OAS_30);
}
}
이렇게 하면 모든 설정이 완료된다.
조금 더 Spring을 공부해서 springdoc으로 마이그레이션하는 방법도 올려보고 싶다. 지금은 이걸 구현하는데에도 한 1~2일정도가 소요되었다.
왼쪽 위에 servers에서 때에 따라 알맞은 서버를 선택하여 테스트 해볼 수 있다.
근데 Swagger가 어렵기 때문에 나같은 경우 Notion에도 이중 명세를 한다. 힘이 많이 들기는 하겠지만 프론트와의 적절한 협업을 위해서는 자연어로 자세하게 기술할 수 있는 무언가가 추가적으로 필요한 것은 맞는 것 같다!!
이건 지금까지 열심히 한 내용들인데, 자랑하고 싶어서 마지막에 올렸다 ㅋㅋㅋㅋ