💁♀️ 앗! 세상은 위험하니 이 API 문서화 자동화 툴을 챙겨 가렴!
📌 시작하기 전, 내가 API나 REST API에 대해 잘 모른다면?
✏️ API는 Application Programming Interface(애플리케이션 프로그램 인터페이스)의 줄임말이다.
이렇게 말하면 한국어인데도 뭔 소린지 모르겠다. (영어가 섞여있긴 해?)
좀 더 간단하게 풀어보자면, 일종의 식당 종업원이라고 보면 된다.
식당에 방문한 손님이 메뉴도 보지 않고, 주방장에게 먹고 싶은 음식을 바로 요청한다고 생각해 보자.
우리 집은 보리밥 정식 파는 집인데 바삭한 황금올리브 한 마리 냉큼 튀겨오라고 난동을 부리는 거다. 정말 곤란하다.
따라서 API를 앞단에 세워 정해진 규격에 맞춰진 요청만 받을 수 있게 하는 거다.
이렇게 종업원을 이용하면서 요청과 응답의 규격을 통일할 수 있고, 허가된 데이터에만 접근을 허용할 수 있다.
✏️ 그럼 REST API는 또 뭘까? REST API는 RESTful 한 API다. (펀쿨섹)
위에서 API를 식당 종업원이라고 했는데, 이제 이 종업원을 좀 더 프로페셔널하게 만드는 거다. 종업원만 봐도 주문을 알 수 있게!
HTTP 프로토콜 위에서, HTTP method를 이용해 행위를, URI를 이용해 리소스(메뉴)를 표현한다.
뜨끈한 국밥 하나 주세요(GET)~ 제가 아까 주문한 국밥에 고기 추가해 주세요(PUT)~ 아까 넣은 주문 취소해 주세요~ (DELETE)
이런 식으로 API 스펙만 봐도 유저가 어떤 데이터를 어떤 용도로 요청했는지 알 수 있게 만든 것을 REST API라고 보면 된다.
간단하게 API와 REST API를 알아봤다. 더 얘기할게 많지만, 본 포스팅은 Swagger을 다루는 포스팅이니 이만 줄인다! 😄
개요
개발을 하다 보면 자주 보이는 것들이 있다. 테스트 코드, 배포, 캐시, 도커... 뭐 그런 것들.
보통 개발을 하다 보면 일정이 바쁘니까 크게 중요하지 않은 태스크는 후속으로 미루기도 한다. 아 내일이 배폰데 이제 와서 컨테이너에 올리긴 좀 힘들자너.
하지만 짬 내서 해두면 결국엔 생산성을 높여주는 일들이 있다. 그중 하나가 API 문서화 자동화 툴이다.
어디 어디 부트캠프나 해커톤 등에서 github에 프로젝트 결과물을 올려둔걸 많이 봤을 거다.
해당 프로젝트 Readme나 Readme에 첨부된 Notion을 보면 아래와 같은 친구들이 꼭 있다.
백 (서버)에서 만든 API를 프론트 (클라이언트)가 가져다 쓰려면, 어떤 API가 있고, 해당 API를 쓰기 위해 어떤 양식을 갖춰야 하는지를 알아야 한다. 메뉴판에 어떤 메뉴가 있고, 그 메뉴를 주문하려면 무슨 옵션이 있는지 알아야 하는 것과 동일한 맥락이다.
위처럼 그때그때 정해진 API 스펙을 직접 문서로 만들 수 있다.
기능 요구사항 확인 -> 필요한 데이터 정리 -> 프론트엔드와 스펙 논의 -> 결정 내용 문서화 -> 피드백받고 업데이트
이런 과정을 거쳐서 뚝딱뚝딱 문서 작업을 해주면 된다. 와! 듣기만 해도 번거로운걸?!
💁♀️ 수동 API 문서화의 단점
- 반복 작업이 너무 많다.
- 내 프로젝트에 JWT를 추가했다면? 그래서 API request header마다 access token을 넣어줘야 한다면?
- 모든 API 문서마다 request 포맷을 작성해주어야 한다. 어우 귀찮아...
- Response 포맷이 고정되어 있다면?
- 만약 개발 중 Response 포맷에 변경이 생긴다면?
- 작성했던 모든 API 문서를 다시 업데이트 해주어야 한다. (혹시 놓친게 있는지 확인하는 과정은 덤이다.)
- 내 프로젝트에 JWT를 추가했다면? 그래서 API request header마다 access token을 넣어줘야 한다면?
- API 스펙의 변경을 직접 추적해야 한다.
- 높은 확률로 개발 도중에 API 스펙이 바뀔 것이다.
- 기획의 변경이든, UI의 변경이든, API를 잘못 만든거든....
- 수만가지 이유로 개발 도중 API를 수정해야 하는 상황이 발생할 것이다. 그런 상황 없었다구요? 부럽습니다
- 그럼 바뀐 API 내용대로 API 문서를 업데이트 해주어야 한다.
- 바꾼게 또 바뀌면? 개발이 너무 바빠서 API 문서화 갱신을 깜빡했다면?
- 높은 확률로 개발 도중에 API 스펙이 바뀔 것이다.
- API를 바로 실행해볼 수 없다.
- 보통은 Postman을 이용해 API를 테스트 할 것이다.
- Notion, Markdown, Excel 등을 이용해 텍스트로 친 경우, 직접 API를 하나하나 복사해서 테스트해야 한다.
- Endpoint, Request param, Request body 등을 하나하나 복사하고, 예시를 집어 넣는 상상.. 😇
- 프론트라면 ajax 등을 이용해 바로 화면단에 연결할 수도 있다.
- 하지만 아직 화면단 구현이 안되어 있다면? API를 연동할 수 있는 환경이 아니라면?
- 연동한다고 해도, API response가 뭐가 올 지 모르는데 console.log로 일일히 확인할건가?
물론 동료들과 적극적으로 소통하며 손수 하나하나 타자를 쳐주는 것도 좋은,, 경험이지만, 우리는 개발자다.
이런 단점을 고수하면서 API를 수동으로 업데이트 쳐주는 대신, 차라리 리팩토링과 성능 개선에 더 많은 리소스를 투자하자 🔨
토스는 똑같은 작업이 3번 이상 반복된다면, 그건 자동화할 수 있는 업무라고 말했다. 듣고 기립 박수를 쳤다.
토스의 아름다운 마인드를 본받아 무럭무럭 자라나는 우리들도 자동화에 도전해 보자.
Swagger
Swagger 키워드로 검색해서 들어오신 분이 계시다면 슬슬 짜증이 나실 거다. 오래 기다리셨습니다 고갱님.
일단 Swagger를 처음 들어 보신 분들도, 맥락 상 이 친구가 어떤 친구인지 대충 감이 잡히셨을 것 같다. 아니라고요?
Swagger는 API 문서 작업을 대신 해주는 친구에요~
위에서 언급한 수동 API 문서 작업의 단점을 한 줄로 요약하면 "귀찮다" 이다.
똑같은 내용을 반복해서 쓰는 것도, 변경 히스토리를 그때그때 반영하는 것도, 실행하기 위해 복붙하는 것도 전부 귀찮다.
그러니 이 귀찮은 작업들을 어떻게 잘 남에게 떠넘길 수 없을까? 할 때 쓸 수 있는 친구가 Swagger 이다.
🤨 뭔 소리에요! 걔가 어떻게 내 API 스펙을 알 수 있는데요!
💁♀️ 이렇게 알 수 있어요!
@GetMapping("/{memberId}")
@Operation(summary = "Get member profile")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공",
content = {@Content(schema = @Schema(implementation = MemberProfileRes.class))}),
@ApiResponse(responseCode = "404", description = "해당 ID의 유저가 존재하지 않습니다."),
})
public MemberProfileRes getMemberProfile(
@PathVariable
@Positive(message = "유저 ID는 양수입니다.")
@Schema(description = "Member ID", example = "1")
Long memberId,
// TODO: Replace with member ID from JWT or that from any other authentication method
@Parameter(name = "loginId", description = "로그인 유저 ID 값", example = "3", required = true)
@Positive(message = "유저 ID는 양수입니다.") @RequestParam final Long loginId
) {
return memberMapper.toResponse(
memberService.findProfileByMemberId(memberId, loginId)
);
}
우리가 일상적으로 사용하는 평범한 controller 코드에 몇 가지 어노테이션을 추가해주면, Swagger가 어노테이션을 이용해 API 정보를 읽는다.
그리고 읽어온 데이터를 이용해 이렇게 웹 UI를 통해 API 문서를 자동으로 제공해준다! 🥳
🤭 어떻게 그런 일이 가능한가요?
Spring Boot가 Java 기반 프레임워크라는 사실을 기억하자!
JDK가 패키지로 제공해주는 JavaDoc을 이용하면 API의 설명, 매개변수, 반환형 등의 메타 데이터를 기술할 수 있다.
/**
* 유저 정보 수정 API.
*
* @param memberId 수정할 유저의 ID
* @param request 수정할 유저 정보
*/
@PutMapping("/me")
public void updateMyProfile(
// TODO: Replace with member ID from JWT or that from any other authentication method
@RequestParam Long memberId,
@RequestBody @Valid MemberProfileUpdateReq request
) {
memberService.updateMyProfile(memberId, memberMapper.toDTO(request));
}
Swagger은 이 JavaDoc을 이용해 API의 메타데이터를 읽고, 이 메타데이터를 웹 UI에 제공하는 방식으로 API를 문서화해준다.
🤔 그럼 복잡하게 어노테이션을 쓰지 말고, JavaDoc만 써도 Swagger UI가 만들어지나요?
사실 굳이 JavaDoc을 안써도, Spring framework의 기본 어노테이션만 써도 Swagger UI를 만들 수 있다.
@PutMapping("/me")
public void updateMyProfile(
// TODO: Replace with member ID from JWT or that from any other authentication method
@RequestParam Long memberId,
@RequestBody @Valid MemberProfileUpdateReq request
) {
memberService.updateMyProfile(memberId, memberMapper.toDTO(request));
}
@PutMapping, @RequestParam, @RequestBody, 메서드 반환형 등을 Swagger가 알아서 읽어주기 때문이다. 그럼에도 불구하고 어노테이션을 쓰는 이유는, JavaDoc이나 Spring 기본 제공으로는 표현할 수 없는 디테일한 정보를 표기하기 위함이다.
@Parameter(name = "loginId", description = "로그인 유저 ID 값", example = "3", required = true)
처럼 파라미터의 의미, 예시, 필수값 여부를 설명할 수 있는 건 물론,
@Length(max = 20, message = "사용자 닉네임은 20글자 이하로 입력해야 합니다.")
Spring validation을 이용해 request 값 조건도 기재할 수 있다!
따라서 프론트엔드가 더더더 API를 쉽고 빠르게 이해하기 위해, 동료와의 의사소통을 위해 API 문서를 상세하게 작성할 때 Swagger 어노테이션을 활용할 수 있다 😎
SpringDoc
이쯤와서 이 글의 제목을 떠올려보자. 아직 언급되지 않은 친구가 하나 숨어있다.
[Spring Boot] SpringDoc과 Swagger를 이용해 API 문서화 자동화하기
🤨 얜 또 뭔가요?
사실 Swagger은 Java, Spring 전용 프레임워크가 아니다. 정확히는 OAS (OpenAPI Specification)를 위한 프레임워크라, JS, Python 등등 언어별로 전부 사용할 수 있다.
따라서 Spring 환경에서 Swagger를 사용하려면 Swagger UI의 설정, Swagger 어노테이션으로 API 메타데이터를 읽는 과정 등을 직접 구현해줘야 하는데, 이제 요게 보통 일이 아니다 이거다. 일을 줄이려다가 오히려 일을 만들고 있는 상황..!
그래서 이런 작업을 대신해주는 라이브러리, SpringDoc 또는 SpringFox를 사용한다.
정리하자면 SpringDoc과 SpringFox는 Swagger UI를 만들고, Swagger 어노테이션을 유저가 쓰기 쉽게 제공하는 라이브러리이다.
😗 SpringDoc이랑 SpringFox 중 뭘 써야 하는데요?
사실 서치 돌리면 레퍼런스가 많이 나오는 건 SpringFox인데, 나는 SpringDoc을 쓰기를 추천한다.
일단 SpringFox는 2020년 이후로 업데이트를 멈춘 반면, SpringDoc은 최근까지도 계속 업데이트를 진행하고 있다.
따라서 SpringDoc이 비교적 레거시도 적고, 쌓인 이슈도 적은 편이다.
또, SpringFox가 개발을 멈춘 사이 SpringDoc은 Spring WebFlux (논 블록킹 비동기 방식의 웹 개발)도 지원하도록 업데이트 되었다.
이처럼 계속 발전 중인 SpringDoc이 좀 더 최신 웹 개발 환경에 적합하며, 실제 세팅과 사용 방법도 간편한 편이다.
(심지어 최근에 나온 Spring Boot 3을 이용할 경우, SpringFox는 아예 동작하지 않는다고 한다!)
제일 중요한 API 문서 작성의 경우엔 두 라이브러리에 큰 차이가 없다.
비슷한 기능이라면 더 최신 코드를 사용하는 라이브러리를, 더 많은 환경을 지원하는 라이브러리를 쓰는게 좋지 않을까?
Spring Boot 2.6에서 Swagger 사용하기
Programming Language : Java 11
Framework : Spring Boot 2.6.7
Build Tool : Gradle 6.4
1️⃣ gradle 의존성 추가
// mvn 기준, 최근 1년 내 업데이트 된 버전 중 다운로드 수가 제일 높은 버전을 사용했습니다.
implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
2️⃣ Swagger 설정 파일 추가
없어도 동작하지만, 저는 JWT 를 이용해 accessToken 인증 작업을 추가해야 해서 설정 파일을 작성했습니다.
@OpenAPIDefinition(
info = @Info(
title = "00 프로젝트 API 명세서",
description = "00 프로젝트에 사용되는 API 명세서",
version = "v1"
)
)
@Configuration
public class SwaggerConfig {
private static final String BEARER_TOKEN_PREFIX = "Bearer";
@Bean
// 운영 환경에는 Swagger를 비활성화하기 위해 추가했습니다.
@Profile("!Prod")
public OpenAPI openAPI() {
String jwtSchemeName = JwtTokenProvider.AUTHORIZATION_HEADER;
SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);
Components components = new Components()
.addSecuritySchemes(jwtSchemeName, new SecurityScheme()
.name(jwtSchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme(BEARER_TOKEN_PREFIX)
.bearerFormat(JwtTokenProvider.TYPE));
// Swagger UI 접속 후, 딱 한 번만 accessToken을 입력해주면 모든 API에 토큰 인증 작업이 적용됩니다.
return new OpenAPI()
.addSecurityItem(securityRequirement)
.components(components);
}
}
3️⃣ application.yml을 이용해 사용자 지정 경로 및 Swagger UI 설정 추가
springdoc:
swagger-ui:
# swagger-ui 접근 경로. default 값은 /swagger-ui.html이다.
path: /swagger-custom-ui.html
# 각 API의 그룹 표시 순서
# path, query, body, response 순으로 출력
groups-order: DESC
# 태그 정렬 순서.
# alpha: 알파벳 순 정렬
# method: OpenAPI specification file에 원하는 태그 정렬 방식 직접 기재
tags-sorter: alpha
# 컨트롤러 정렬 순서.
# method는 delete - get - patch - post - put 순으로 정렬된다.
# alpha를 사용해 알파벳 순으로 정렬할 수 있다.
operations-sorter: method
# swagger-ui default url인 petstore html의 비활성화 설정
disable-swagger-default-url: true
# swagger-ui에서 try 했을 때 request duration을 알려주는 설정
display-request-duration: true
# openAPI 접근 경로. default 값은 /v3/api-docs 이다.
api-docs:
path: /api-docs
# Spring Actuator의 endpoint까지 보여줄 것인지?
show-actuator: true
# request media type 의 기본 값
default-consumes-media-type: application/json
# response media type 의 기본 값
default-produces-media-type: application/json
# 해당 패턴에 매칭되는 controller만 swagger-ui에 노출한다.
paths-to-match:
- /api/**
각 설정별 의미를 주석으로 추가해두었다. 프로젝트에 필요한 설정만 추가하면 된다.
더 많은 설정은 공식 문서를 참고하자.
4️⃣ Spring Security에 Swagger Endpoint 활성화
저는 Swagger 이외에도 Role 체크 설정이 필요한 endpoint들이 있어, 관리하기 쉽게 AuthenticatedMatchers라는 class로 분리해 사용했습니다.
public class AuthenticatedMatchers {
private AuthenticatedMatchers() {
}
public static final String[] swaggerArray = {
"/api-docs",
"/swagger-ui-custom.html",
"/v3/api-docs/**",
"/swagger-ui/**",
"/api-docs/**",
"/swagger-ui.html"
};
}
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// ---------------------------- Swagger endpoint에 permitAll 추가
.authorizeRequests()
.antMatchers(AuthenticatedMatchers.swaggerArray).permitAll()
// ----------------------------------------------------- 여기까지!
.and()
.addFilterBefore(new JwtFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
}
5️⃣ Spring 프로젝트 실행
프로젝트 실행 후, 3️⃣에서 지정한 springdoc.swagger-ui.path 경로로 접속하면 된다.
로컬 환경에서 9090포트를 이용했다고 가정, 위 코드 기준으로 http://localhost:9090/swagger-custom-ui.html로 접속!
Spring Boot 3에서 Swagger 사용하기
Programming Language : Java 17
Framework : Spring Boot 3
Build Tool : Gradle 7.6
1️⃣ gradle 의존성 추가 ⭐️⭐️⭐️
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
springDoc 공식 문서에 따르면, Spring Boot v3 (Java 17 & Jakarta EE 9)을 사용하기 위해선 위 dependency를 사용해야 한다. Spring Boot 3 지원을 위해 springdoc-openapi v2를 사용해야 하기 때문이다.
low 버전에서 사용하던 org.springdoc:springdoc-openapi-ui가 대체되었다고 보면 된다.
2️⃣ Swagger 설정 파일 추가 (기존과 동일)
없어도 동작하지만, 저는 JWT 를 이용해 accessToken 인증 작업을 추가해야 해서 설정 파일을 작성했습니다.
@OpenAPIDefinition(
info = @Info(
title = "00 프로젝트 API 명세서",
description = "00 프로젝트에 사용되는 API 명세서",
version = "v1"
)
)
@Configuration
public class SwaggerConfig {
private static final String BEARER_TOKEN_PREFIX = "Bearer";
@Bean
// 운영 환경에는 Swagger를 비활성화하기 위해 추가했습니다.
@Profile("!Prod")
public OpenAPI openAPI() {
String jwtSchemeName = JwtTokenProvider.AUTHORIZATION_HEADER;
SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);
Components components = new Components()
.addSecuritySchemes(jwtSchemeName, new SecurityScheme()
.name(jwtSchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme(BEARER_TOKEN_PREFIX)
.bearerFormat(JwtTokenProvider.TYPE));
// Swagger UI 접속 후, 딱 한 번만 accessToken을 입력해주면 모든 API에 토큰 인증 작업이 적용됩니다.
return new OpenAPI()
.addSecurityItem(securityRequirement)
.components(components);
}
}
3️⃣ application.yml을 이용해 사용자 지정 경로 및 Swagger UI 설정 추가 (기존과 동일)
springdoc:
swagger-ui:
# swagger-ui 접근 경로. default 값은 /swagger-ui.html이다.
path: /swagger-custom-ui.html
# 각 API의 그룹 표시 순서
# path, query, body, response 순으로 출력
groups-order: DESC
# 태그 정렬 순서.
# alpha: 알파벳 순 정렬
# method: OpenAPI specification file에 원하는 태그 정렬 방식 직접 기재
tags-sorter: alpha
# 컨트롤러 정렬 순서.
# method는 delete - get - patch - post - put 순으로 정렬된다.
# alpha를 사용해 알파벳 순으로 정렬할 수 있다.
operations-sorter: method
# swagger-ui default url인 petstore html의 비활성화 설정
disable-swagger-default-url: true
# swagger-ui에서 try 했을 때 request duration을 알려주는 설정
display-request-duration: true
# openAPI 접근 경로. default 값은 /v3/api-docs 이다.
api-docs:
path: /api-docs
# Spring Actuator의 endpoint까지 보여줄 것인지?
show-actuator: true
# request media type 의 기본 값
default-consumes-media-type: application/json
# response media type 의 기본 값
default-produces-media-type: application/json
# 해당 패턴에 매칭되는 controller만 swagger-ui에 노출한다.
paths-to-match:
- /api/**
각 설정별 의미를 주석으로 추가해두었다. 프로젝트에 필요한 설정만 추가하면 된다.
더 많은 설정은 공식 문서를 참고하자.
4️⃣ Spring Security에 Swagger Endpoint 활성화 ⭐️⭐️⭐️
여긴 Swagger 설정이 다르다기보단, Spring Boot 설정이 달라서 첨부합니다.
Spring Boot v3으로 업데이트 되면서 WebSecurityConfigurerAdapter가 완전히 지원 종료되었습니다.
따라서 SecurityConfig를 구현하던 것과 다른 방식으로, SecurityFilterChain을 이용해 구현해주시면 됩니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private static final String[] AUTH_WHITELIST = {
"/api/**", "/graphiql", "/graphql",
"/swagger-ui/**", "/api-docs", "/swagger-ui-custom.html",
"/v3/api-docs/**", "/api-docs/**", "/swagger-ui.html"
};
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(
authorize -> authorize
.shouldFilterAllDispatcherTypes(false)
.requestMatchers(AUTH_WHITELIST)
.permitAll()
.anyRequest()
.authenticated()
)
.httpBasic().disable()
.formLogin().disable()
.cors().disable()
.csrf().disable()
.build();
}
}
5️⃣ Spring 프로젝트 실행 (기존과 동일)
프로젝트 실행 후, 3️⃣에서 지정한 springdoc.swagger-ui.path 경로로 접속하면 된다.
로컬 환경에서 9090포트를 이용했다고 가정, 위 코드 기준으로 http://localhost:9090/swagger-custom-ui.html로 접속!
사실 Gradle dependency 추가해주는 것과 WebSecurityConfigurerAdapter를 사용하지 않는 것 말고는 기존 방식과 완전히 동일하다.
예제 코드
1️⃣ Controller에 전체에 Tag 추가
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/members")
@Validated
@Tag(name = "Member", description = "Member API")
public class MemberController {
private final MemberService memberService;
private final MemberMapper memberMapper;
}
@Tag(name = "Member", description = "Member API")
를 이용해 Controller 단위에 Tag를 지정해준다.
그림 이렇게 API들이 Swagger UI에서 Tag 단위로 그룹핑된다! 가독성을 높여주는 설정이다.
2️⃣ 각 Controller에 API 메타 데이터 정보 명세
@GetMapping("/{memberId}")
@Operation(summary = "Get member profile", description = "특정 멤버의 상세 정보를 조회한다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공",
content = {@Content(schema = @Schema(implementation = MemberProfileRes.class))}),
@ApiResponse(responseCode = "404", description = "해당 ID의 유저가 존재하지 않습니다."),
})
public MemberProfileRes getMemberProfile(
@PathVariable
@Positive(message = "유저 ID는 양수입니다.")
@Schema(description = "Member ID", example = "1")
Long memberId,
// TODO: Replace with member ID from JWT or that from any other authentication method
@Parameter(name = "loginId", description = "로그인 유저 ID 값", example = "3", required = true)
@Positive(message = "유저 ID는 양수입니다.") @RequestParam final Long loginId,
@RequestBody @Valid MemberProfileUpdateReq request
) {
return memberMapper.toResponse(
memberService.findProfileByMemberId(memberId, loginId)
);
}
@Operation(summary = "Get member profile", description = "특정 멤버의 상세 정보를 조회한다.")
@Operation를 이용해 해당 API가 어떤 리소스를 나타내는지 간략한 설명을 추가할 수 있다.
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공",
content = {@Content(schema = @Schema(implementation = MemberProfileRes.class))}),
@ApiResponse(responseCode = "404", description = "해당 ID의 유저가 존재하지 않습니다."),
})
@ApiResponses를 이용해 해당 API의 Response 정보들을 나타낼 수 있다.
나는 성공한 경우와 실패한 경우의 Response를 분리했기 때문에, @ApiResponses를 이용해 여러 response들을 묶어주었다.
@ApiResponse는 단일 Response에 대한 정보를 나타낸다.
옵션은 단어 그대로의 의미다.
- responseCode : HTTP status code
- description : 이 response의 의미
- content : response 데이터 포맷. void이거나 response field에 대한 설명을 추가하지 않을거라면 생략하면 된다.
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공",
content = {
@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = MemberRes.class)))
})
})
만약 Response가 List<> 형태라면, array 옵션을 이용하면 된다.
@PathVariable
@Positive(message = "유저 ID는 양수입니다.")
@Schema(description = "Member ID", example = "1")
Long memberId,
// TODO: Replace with member ID from JWT or that from any other authentication method
@Parameter(name = "loginId", description = "로그인 유저 ID 값", example = "3", required = true)
@Positive(message = "유저 ID는 양수입니다.") @RequestParam final Long loginId,
@RequestBody @Valid MemberProfileUpdateReq request
기본적으로 Spring validation 어노테이션을 인식하기 때문에 크게 설정이 필요하진 않다.
나는 swagger가 제공하는 @Schema와 @Parameter 어노테이션을 이용해 각 필드에 대한 설명, 예시, 필수값 여부를 추가해주었다.
3️⃣ Request Body, Response 각 Schema 정보 명세
Swagger Schemas 기능을 이용해 request body, response value를 확인 / 입력 할 수 있다.
@Getter
@AllArgsConstructor
@Schema(description = "Member profile update request")
public class MemberProfileUpdateReq {
@NotBlank(message = "사용자 이름을 입력해주세요.")
@Length(max = 20, message = "사용자 이름은 20글자 이하로 입력해야 합니다.")
@Schema(description = "member name", example = "John Doe")
private String name;
@NotBlank(message = "사용자 닉네임을 입력해주세요.")
@Length(max = 20, message = "사용자 닉네임은 20글자 이하로 입력해야 합니다.")
@Schema(description = "member nickname", example = "johndoe")
private String nickname;
@NotBlank
@Schema(description = "member profile emoji", example = "👨🏻💻")
private String profileEmoji;
}
@Schema(description = "Member profile update request")
class 상단에 @Schema 어노테이션을 이용해, 해당 클래스가 어떤 클래스인지 설명을 적어줄 수 있다.
@NotBlank(message = "사용자 이름을 입력해주세요.")
@Length(max = 20, message = "사용자 이름은 20글자 이하로 입력해야 합니다.")
@Schema(description = "member name", example = "John Doe")
private String name;
request param, path variable과 마찬가지로 Spring validation를 인식한다.
나는 swagger가 제공하는 @Schema 어노테이션을 이용해 각 필드에 대한 설명, 예시, 필수값 여부를 추가해주었다.
@Schema(description = "친구 정보", implementation = FriendRes.class)
private FriendRes friendInfo;
만약 특정 객체를 멤버 변수로 갖는다면 implementation 옵션을 사용해주면 된다.
Schema는 자세히 적어줄 수록 소통하기 편하기 때문에, 되도록 꼼꼼하게 적어주자!
또, 이렇게 자세하게 쓴 Schema는 프론트엔드에서 타입을 만들 때 활용할 수도 있다. 어떻게?
프론트에서 Swagger 활용하기
Swagger 설정할 때, application.yml에서 springdoc.swagger-ui.api-docs.path 를 이용해 openAPI 경로를 등록했다.
해당 경로로 이동하면 아래와 같은 Json이 출력된다.
이걸 기반으로 Swagger UI가 만들어지는건데, 이 친구를 다른 방식으로도 활용할 수 있다.
프론트엔드(Typescript)는 API를 사용하기 위해, request body type이나 response type를 만든다.
그리고 요 타입에는 변수 이름과 변수 타입이 필요하다. 어라? 저 위의 JSON 파일에 다 나와있는 정보잖아?
여러분이 하는 그 생각을 똑~같이 하고, 라이브러리를 만들어 npm에 배포해 둔 선배님이 계신다.
swagger-typescript-api를 이용해 Swagger에서 만들어진 Json을 Typescript type / interface 로 뽑아낼 수 있다.
그냥 보고 쳐도 되잖아요! 라고 생각할 수도 있지만, API가 업데이트 될 때마다, API가 새로 생길 때마다 항상 타입을 직접 만들어주는건 상당한 노가다 작업이다 (사실 전 프론트를 잘 모르지만... 주변 프론트 분들 말씀으로는 그렇더군요 👀)
(요건 사이드프로젝트에서 swagger-typescript-api를 적용한 예시!)
명령어 하나만 쳐주면 이렇게 파일이 자동 생성이 된다 🤩
다시 한 번 되새기자. 3번 이상 반복되는 업무는 자동화할 수 있다. 프로젝트에서 swagger를 쓰고 있다면, 한번 시험 삼아 적용해보는 걸 추천한다!
마무리
이렇게 오늘은 Swagger의 개념과 프로젝트 적용 방법에 대해 알아봤다.
Swagger는 대부분의 프로젝트에서 디폴트 옵션마냥 쓰이고 있고, 나도 많이 써 봤지만, 오늘처럼 각 잡고 정리한 적은 없었다.
기술을 원리와 목적을 이해하고 사용할 때 비로소 내 것이 되는 것 같다 😎
앞으로도 뭔가를 사용할 때 깊게 고민해보고 써야겠다.
질문이나 피드백은 언제나 환영합니다~ 🙇♀️
'Develop > Spring' 카테고리의 다른 글
[Spring Boot] 자바 스프링에서 처리율 제한 기능을 구현하는 4가지 방법 (2) | 2023.06.21 |
---|---|
[Spring Boot] Jsoup으로 OG태그 메타 데이터 크롤링하기 (1) | 2023.06.14 |
[Spring Boot] count를 구현하는 5가지 방법 (2) | 2023.05.24 |
[Spring Boot] ConstraintValidator를 이용해 나만의 validator annotation 만들기 (1) | 2023.05.15 |
[Spring Boot] 테스트 컨테이너로 테스트하기 (3) | 2023.04.17 |