ํ”„๋ก ํŠธ๊ฐ€ ํ˜ผ๋ž€์— ๋น ์ง€์ง€ ์•Š๋Š” ์„œ๋ฒ„์™€ API ๋ฌธ์„œ ๋งŒ๋“ค๊ธฐ

2024. 2. 25. 19:37ยท ๐Ÿค– Backend/SpringBoot
๋ชฉ์ฐจ
  1. Swagger
  2.  
  3. RestDocs
  4.  
  5. epages, OpenAPI Specification(OAS) ๊ธฐ๋ฐ˜ API ๋ฌธ์„œํ™”
  6. restdocs-api-spec ์˜คํ”ˆ์†Œ์Šค
  7. epages ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…
  8. ๋ถ€์กฑํ•œ ์ฐธ๊ณ ์ž๋ฃŒ
  9. @ModelAttribute, @RequestParts ๋Œ€์‘
  10. Swagger์™€ RestDocs๊ฐ„์˜ ํƒ€์ž… ์ง€์ • ํ˜•์‹์˜ ๋ถˆ์ผ์น˜
  11. ๊ฐ•์ œ๋˜์ง€ ์•Š๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ธํ•œ ๋ถ€์กฑํ•œ ์‹ ๋ขฐ๋„
  12. ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์„ž์ž„์œผ๋กœ ์ธํ•œ ๊ฐ€๋…์„ฑ ๋ฌธ์ œ
  13. ์ž์„ธํ•˜๊ณ  ์ •๋ณด๋Ÿ‰์ด ๋งŽ์€ ๋ฌธ์„œ
  14. ์‹ค์ œ ์ฝ”๋“œ์™€ ๋ฌธ์„œ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ
  15. OAS ๋นŒ๋“œ ๋ฐ Swagger ๋ฐ˜์˜ ์ž๋™ํ™” ๊ทธ๋ ˆ์ด๋“ค
  16. ์ฐธ๊ณ ์ž๋ฃŒ

์ด์ „ ๋…ธ์…˜ ๋ธ”๋กœ๊ทธ์˜ Transaction ์ด๋ž€? (2023.08.11)๋กœ๋ถ€ํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋œ ๊ธ€์ž…๋‹ˆ๋‹ค.

 

 

์ €๋Š” ๋Š˜ Swagger์˜ ์ž๋™ํ™”์™€ ๊ฐ„๋‹จํ•œ ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๋น ๋ฅธ API ๋ฌธ์„œ ๋ฐฐํฌ๋งŒ ํ•ด๋ดค์Šต๋‹ˆ๋‹ค. ๋น ๋ฅด๊ฒŒ ์ž‘์—…๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์žฅ์ ์€ ์žˆ์—ˆ์ง€๋งŒ, ๋ชจ๋“  ์š”์ฒญ์„ Postman์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ณผ์ •์—์„œ ์‹ค์ˆ˜๊ฐ€ ์žˆ์„ ์ˆ˜ ๋ฐ–์— ์—†์—ˆ๊ณ  Swagger์˜ ์ž๋™ ์ƒ์„ฑ ๋ฌธ์„œ๊ฐ€ ์นœ์ ˆํ•œ ํŽธ์€ ์•„๋‹ˆ๊ธฐ์— ์ฝ๋Š” ์‚ฌ๋žŒ๋งˆ๋‹ค ๋‹ค๋ฅด๊ฒŒ ์ดํ•ด๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ”„๋ก ํŠธ์—์„œ API๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ๊ณผ์ •์—์„œ ์‹ค์ˆ˜๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์ €์—๊ฒŒ ์ง์ ‘ ๋ฌผ์–ด๋ณด๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜์Šต๋‹ˆ๋‹ค. API ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•œ ์˜์˜๊ฐ€ ์‚ฌ๋ผ์ ธ๋ฒ„๋ฆฌ๋Š” ์ผ์ด ๋งŽ์•˜์Šต๋‹ˆ๋‹คโ€ฆ ๐Ÿ˜”

์ด๋ฒˆ ๋ฆฌ๋ทฐ๋ฉ”์ดํŠธ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ œํ•œ๋œ ์‹œ๊ฐ„ ๋‚ด์— FE์™€ AI ๋ชจ๋‘๊ฐ€ ์‚ฌ์šฉํ•  ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋ฏ€๋กœ, ์‹ ๋ขฐ์„ฑ์ด ๋†’๊ณ  ๋ช…๋ฃŒํ•œ API ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๊ณต๋ถ€ํ•˜๊ณ  ๊ณ ๋ฏผํ•œ ๊ณผ์ •์„ ์ •๋ฆฌํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

 


API ๋ฌธ์„œํ™” ๋„๊ตฌ ๋น„๊ต

Spring ๊ธฐ๋ฐ˜ ์„œ๋ฒ„ ํ”„๋กœ์ ํŠธ์—์„œ API ๋ฌธ์„œํ™”๋Š” ๋Œ€๊ฒŒ Swagger์™€ Spring RestDocs๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋‘ ๊ฐœ์˜ ์žฅ์ ๋งŒ ๊ฐ€์ ธ๊ฐ€๊ธฐ ์œ„ํ•œ OpenAPI Specification(OAS)๊ธฐ๋ฐ˜ Swagger๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. (OAS๊ธฐ๋ฐ˜ Swagger๋Š” epages๋ผ๋Š” ํšŒ์‚ฌ๊ฐ€ ๋งŒ๋“  OAS ์ถ”์ถœ ๋ฐ Swagger ์ ์šฉ ์˜คํ”ˆ์†Œ์Šค๋ฅผ ํ†ตํ•ด ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, ์ดํ•˜ epages๋ผ๊ณ  ๋ถ€๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค)

Swagger RestDocs epages
์žฅ์  ๋‹จ์  ์žฅ์  ๋‹จ์  ์žฅ์  ๋‹จ์ 
๊น”๋”ํ•˜๊ณ  ์˜ˆ์œ
๋ฌธ์„œ
ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ฐ•์ œํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ๋†’์ง€ ์•Š์€ ์‹ ๋ขฐ๋„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ๊ฐ•์ œ๋กœ ์ธํ•œ ๋†’์€ ์‹ ๋ขฐ๋„  ์šฐ์•„ํ•˜์ง€ ๋ชปํ•œ ๋ฌธ์„œ Swagger์˜ ์šฐ์•„ํ•œ ๋ฌธ์„œ
(๊น”๋”ํ•˜๊ณ  ์˜ˆ์˜๋ฉฐ, API Test ๊ธฐ๋Šฅ)
๋„ํ๋จผํŠธ๊ฐ€ ์—†๊ณ , ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์„ ์œ„ํ•œ ์ฐธ๊ณ ์ž๋ฃŒ๋„ ์ ์Œ
API Test ๊ธฐ๋Šฅ
์ง€์›
Swagger ์–ด๋…ธํ…Œ์ด์…˜์ด
๋น„์ฆˆ๋‹ˆ์Šค ์ฝ”๋“œ์™€ ์„ž์ž„
๋น„์ฆˆ๋‹ˆ์Šค ์ฝ”๋“œ์™€ ๋ฌธ์„œํ™” ์ฝ”๋“œ๊ฐ€ ๋ถ„๋ฆฌ๋จ API Test ๊ธฐ๋Šฅ ๋ฏธ์ง€์› ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ
์ž‘์„ฑ๋˜๋ฏ€๋กœ ๋†’์€ ์‹ ๋ขฐ๋„
Swagger์™€์˜ RestDocs ์ฐจ์ด๋กœ ์ธํ•ด ์ž˜๋ชป ์ž‘์„ฑ๋˜๋Š” ๋ฌธ์„œ

 

Swagger

Swagger๋Š” ๊ต‰์žฅํžˆ ์šฐ์•„ํ•œ ๋ฌธ์„œ๊ฐ€ ํŠน์ง•์ž…๋‹ˆ๋‹ค. ๋ฌธ์„œ๊ฐ€ ์˜ˆ์˜๋ฉฐ, ์ œ๊ณตํ•˜๋Š” ์ •๋ณด๋Ÿ‰ ๋˜ํ•œ ๋งŽ์Šต๋‹ˆ๋‹ค. ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ข…๋ฅ˜์™€ ์˜ˆ์‹œ, response, ๋ฆฌํ„ด ํ˜น์€ ์ฒจ๋ถ€๋˜๋Š” DTO์˜ ์Šคํ‚ค๋งˆ ๋“ฑ๋“ฑ์ด ์ œ๊ณต๋˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ„๋„๋กœ ์ด๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด ์„œ๋ฒ„์˜ ์ฝ”๋“œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ž‘์„ฑ๋˜๋Š” ํŠน์ง• ๋•๋ถ„์— ๋‹จ์ˆœ ๋…ธ๋™์— ๊ฐ€๊นŒ์šด ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ๊ฐ„์„ ๋‹จ์ถ•์‹œ์ผœ์ค๋‹ˆ๋‹ค.

์„ค๋ช…๋งŒ ๋“ค์œผ๋ฉด ๋ณ„๊ฒƒ ์•„๋‹Œ ๊ฒƒ ๊ฐ™์€ ์žฅ์ ๊ฐ™์ง€๋งŒ, RestDocs๋ฅผ ์จ๋ณด๊ณ  ๋‚˜๋ฉด ๊ทธ ์žฅ์ ์„ ์ ˆ์‹คํžˆ ๋А๋ผ๊ฒŒ ๋ฉ๋‹ˆ๋‹คโ€ฆ

ํ•˜์ง€๋งŒ ์ด ์žฅ์ ์„ ์ƒ์‡„์‹œํ‚ฌ ์ •๋„๋กœ ์น˜๋ช…์ ์ธ ๋‹จ์ ์ด๋ผ๋ฉด, API ๋ฌธ์„œ์— ๋‚ด์šฉ๋“ค ์ถ”๊ฐ€ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์–ด๋…ธํ…Œ์ด์…˜๋“ค์ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ์„ž์ธ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. DTO์˜ ๊ฒฝ์šฐ์—๋Š” ์–‘ํ˜ธํ•œ ํŽธ์ด์ง€๋งŒ, Controller๋Š” ์™„์ „ํžˆ ํ˜ผ๋ž€์Šค๋Ÿฌ์›Œ์ง‘๋‹ˆ๋‹ค.

 

RestDocs

์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ API ๋ฌธ์„œ๊ณผ ์ž‘๋™๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์‹ ๋ขฐ์„ฑ ๋†’์€ ์š”์ฒญ๋งŒ ๋ฌธ์„œ์— ๊ธฐ์žฌ๋˜๋Š” ํŠน์ง•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด์ฐŒ๋ณด๋ฉด ๋ฐ˜์ž๋™ํ™”๋ผ๊ณ  ๋ณผ ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. RestDocs๋Š” Asciidoctor ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด API ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ, ์ด API ๋ฌธ์„œ๊ฐ€ ์šฐ์•„ํ•˜์ง€ ๋ชปํ•˜๋‹ค๋Š” ์ ์ด ํฐ ๋‹จ์ ์ž…๋‹ˆ๋‹ค. Swagger์— ์ต์ˆ™ํ•œ ์ €์—๊ฒ ์—ญ์ฒด๊ฐ์ด ์‹ฌํ•ด์„œ ๊ทธ๋ ‡๊ฒŒ ๋А๋ผ๋Š” ๊ฒƒ์ผ์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ์˜ˆ์˜์ง€ ๋ชปํ•œ ๋ฌธ์„œ๋Š” ๊ฐ€๋…์„ฑ๋งˆ์ € ํ—ค์นฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฌธ์„œ์—์„œ ์ œ๊ณต๋˜๋Š” ์ •๋ณด๋Ÿ‰๋„ Swagger ๋Œ€๋น„ ์ ์Šต๋‹ˆ๋‹ค. ํŠน์œ ์˜ ๋ฌธ๋ฒ•์œผ๋กœ ์ธํ•œ ๋‹จ์ ์€ epages๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋ฏ€๋กœ, ํ›„์ˆ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

epages, OpenAPI Specification(OAS) ๊ธฐ๋ฐ˜ API ๋ฌธ์„œํ™”

OpenAPI Specification(OAS)๋Š” RESTful API ์ŠคํŽ™ ๋ฌธ์„œ์˜ ํ‘œ์ค€์œผ๋กœ์„œ ํ™œ์šฉ๋˜๊ณ  ์žˆ๋Š” ๊ทœ์•…์ž…๋‹ˆ๋‹ค. Swagger-UI๋Š” OAS๋ฅผ ํ•ด์„ํ•˜์—ฌ API ์ŠคํŽ™์„ ์‹œ๊ฐํ™”ํ•ด์ค๋‹ˆ๋‹ค. ๋˜ํ•œ Postman, Paw๊ฐ™์€ API Client๋“ค๋„ OAS๋ฅผ ์ง€์›ํ•˜๊ณ  ์žˆ์–ด ํ™œ์šฉ๋„๊ฐ€ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค.

restdocs-api-spec ์˜คํ”ˆ์†Œ์Šค

์ด ์›๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ, epages๋Š” ์ œ๊ณตํ•˜๋Š” **RestDocs Wrapper ํด๋ž˜์Šค๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด OAS๋ฅผ ์ถ”์ถœํ•ด์ค๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด static routing์„ ํ†ตํ•ด ์ถ”์ถœ๋œ OAS๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ swagger ํŽ˜์ด์ง€๋ฅผ ํ˜ธ์ŠคํŒ…**ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๋ฌธ์„œ ์ž‘์„ฑ์€ RestDocs, ๋ฌธ์„œ ์ œ๊ณต์€ Swagger๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋‘ ์žฅ์ ๋งŒ ํƒํ•˜๋Š” ๋ฐฉ์‹์ด๋ฏ€๋กœ ์žฅ์ ๋งŒ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„๋ณด์˜€์ง€๋งŒ, ์ œ๊ฐ€ ์ ์šฉํ•˜๋ฉด์„œ ๋งŒ๋‚œ ๋ฌธ์ œ๋“ค์„ ์†Œ๊ฐœํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

 

epages ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

๋ถ€์กฑํ•œ ์ฐธ๊ณ ์ž๋ฃŒ

epages๋Š” 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. RestDocs์˜ ๋ฌธ๋ฒ•๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ, epages ๋…์ž์ ์ธ ResourceSnippetParameters.builder() ํŒจํ„ด์„ ์‚ฌ์šฉํ•œ API ๋ฌธ์„œ ์ž‘์„ฑ
  2. RestDocs์™€ ๋˜‘๊ฐ™์€ ๋ฌธ๋ฒ•์œผ๋กœ ์ž‘์„ฑ๋œ ์ฝ”๋“œ์—, epages๊ฐ€ ์ œ๊ณตํ•˜๋Š” RestDocs Wrapper ํด๋ž˜์Šค๋กœ ๊ต์ฒด

1๋ฒˆ ๋ฐฉ์‹์€ ๋…์ž์ ์ธ ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ด€๋ จ ์ž๋ฃŒ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜์ง€๋งŒ, ์ฐธ๊ณ ์ž๋ฃŒ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ์šฐ์„  OAS๊ธฐ๋ฐ˜ API ๋ฌธ์„œํ™”๋Š” ์ผ๋ฐ˜ ๊ฐœ๋ฐœ ๋ธ”๋กœ๊ทธ์—์„œ๋Š” ์ž˜ ์†Œ๊ฐœ๋˜์ง€ ์•Š๊ณ  ์žˆ๊ณ  ์นด์นด์˜ค ํŽ˜์ด๋‚˜ ์ปฌ๋ฆฌ ๋“ฑ์˜ ์ผ๋ถ€ ํ…Œํฌ ๋ธ”๋กœ๊ทธ์—์„œ๋งŒ ์†Œ๊ฐœ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ ์ ์šฉ์„ ์‹œ๋„ํ•œ ๊ธฐ๋ก์ด๋‚˜ ๊ทธ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•œ ํŠธ๋Ÿฌ๋ธ”์„ ํ•ด๊ฒฐํ•œ ๊ธฐ๋ก์— ๋Œ€ํ•œ ์ž๋ฃŒ๊ฐ€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค.(๋ฌผ๋ก  ์˜์–ด๋กœ ๊ฒ€์ƒ‰ํ•ด๋„ ๋งˆ์ฐฌ๊ฐ€์ง€..) ๊ณต์‹ ๋„ํ๋จผํŠธ ๋˜ํ•œ ์กด์žฌํ•˜์ง€ ์•Š๊ณ , ๋ ˆํผ์ง€ํ† ๋ฆฌ์˜ README์— ๊ฐ„๋‹จํ•œ setup๊ณผ ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•ด์„œ๋งŒ ์†Œ๊ฐœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

ํด๋ž˜์Šค๋ฅผ ๋œฏ์–ด๊ฐ€๋ฉด์„œ ์‚ฌ์šฉํ•ด๊ฐ€๋ ค๊ณ  ํ–ˆ์ง€๋งŒ ๋””๋ฒ„๊น…์— ๋„ˆ๋ฌด ๋งŽ์€ ์‹œ๊ฐ„์ด ์†Œ๋ชจ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, RestDocs ์ž๋ฃŒ๋ฅผ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” 2๋ฒˆ ๋ฐฉ์‹์œผ๋กœ ์‹œ๋„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

@ModelAttribute, @RequestParts ๋Œ€์‘

๋ฆฌ๋ทฐ๋ฉ”์ดํŠธ์˜ ๋ฆฌ๋ทฐ ์—…๋กœ๋“œ์™€ ์ƒํ’ˆ ์ถ”๊ฐ€ ์š”์ฒญ์€ DTO์™€ ์‚ฌ์ง„ MultipartFile์„ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋‘ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” Content-type ๋„ ๊ตฌ๋ถ„ํ•ด์„œ ๋ณด๋‚ด์•ผํ•˜๋Š” ๋งŒํผ ๋ฌธ์„œํ™”๊ฐ€ ์ค‘์š”ํ•œ POST ์š”์ฒญ์ž…๋‹ˆ๋‹ค๋งŒ, epages์—์„œ๋Š” ์ œ๋Œ€๋กœ ๋ฌธ์„œํ™”๋˜์ง€๊ฐ€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

โš ๏ธ ์šฐ์„  epages์˜ OpenAPI๋ฅผ 2๋ฒ„์ „์œผ๋กœ ๋‚ฎ์ถฐ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • OpenAPI 2.0 โ‡’ RestDocs 2.0.X
  • OpenAPI 3.0 โ‡’ RestDocs 3.0.X

์™€ ๊ฐ™์ด ๋Œ€์‘๋˜๋Š”๋ฐ, RestDocs 3.0.X ๋ถ€ํ„ฐ๋Š” @ModelAttribute ์™€ @RequestParts ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋Œ€์‘ํ•˜๋Š” requestParts ์ง€์›์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Form parameter์™€ QueryParameter๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด๋ผ๊ณ  ์ ํ˜€์žˆ์ง€๋งŒ, mockMvc์˜ mulipart() ์š”์ฒญ์œผ๋กœ ์ „๋‹ฌ๋˜๋Š” multipartFile ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์–ด๋–ป๊ฒŒ ๋Œ€์‘ํ•ด์•ผํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๋ฌธ์„œ๊ฐ€ ์—†๊ณ , ๊ตฌ๊ธ€๋งํ•ด๋„ ์ฐพ์„ ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

OpenAPI 2.0 ์ด ์ œ๋Œ€๋กœ ๋ฌธ์„œํ™”๋˜์ง€ ์•Š๋Š” ์ ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์ง€๋งŒ, OpenAPI 3.0 ์€ ์ปดํŒŒ์ผ ์กฐ์ฐจ ์•ˆ๋˜๋ฏ€๋กœ, ๋ฒ„์ „์„ ๋‚ฎ์ถฐ์•ผํ•ฉ๋‹ˆ๋‹ค.

@ModelAttribute ์™€ @RequestParts ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋Œ€์‘ํ•˜๋Š” requestParts() ๋Š” ๋ฌธ์„œํ™”๋˜์ง€๊ฐ€ ์•Š์Šต๋‹ˆ๋‹ค.

// ReviewController
@PostMapping("/")
public ResponseEntity<Void> createReview(@Valid @ModelAttribute ReviewCreateRequest reviewCreateRequest) {
    Long reviewId = 1L;

    return ResponseEntity.created(URI.create("/api/v1/review/" + reviewId)).build();
}
// ReviewCreateRequest
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ReviewCreateRequest {

    @NotNull
    private Integer rating;

    @NotBlank
    private String title;

    @NotBlank
    private String content;

    private List<MultipartFile> reviewImages;

    @NotNull
    private Long travelProductId;

    @NotNull
    private Long customerId;
}
@Test
void ๋ฆฌ๋ทฐ๋ฅผ_์ƒ์„ฑํ•œ๋‹ค() throws Exception {

    // when
    ResultActions response = mockMvc.perform(
                    multipart("/api/v1/review/")
                          .file("reviewImages", testImage1.getBytes())
                            .param("rating", "5")
                            .param("title", "๋ฆฌ๋ทฐ ์ œ๋ชฉ")
                            .param("content", "๋ฆฌ๋ทฐ ๋‚ด์šฉ")
                            .param("travelProductId", "1")
                            .param("customerId", "1")
                            .contentType(MediaType.MULTIPART_FORM_DATA)
            )
            .andExpect(status().isCreated())
            .andExpect(header().string(HttpHeaders.LOCATION, "/api/v1/review/1"));

    // then
    response.andDo(
            document("review-createReview",
                    requestHeaders(
                            HeaderDocumentation.headerWithName(HttpHeaders.CONTENT_TYPE).description(MediaType.MULTIPART_FORM_DATA_VALUE)
                    ), requestParts(
                            partWithName("reviewImages").description("๋ฆฌ๋ทฐ ์ด๋ฏธ์ง€ ํŒŒ์ผ")
                    ), requestParameters(
                            RequestDocumentation.parameterWithName("rating").description("๋ณ„์ "),
                            RequestDocumentation.parameterWithName("title").description("๋ฆฌ๋ทฐ ์ œ๋ชฉ"),
                            RequestDocumentation.parameterWithName("content").description("๋ฆฌ๋ทฐ ๋‚ด์šฉ"),
                            RequestDocumentation.parameterWithName("travelProductId").description("์—ฌํ–‰ ์ƒํ’ˆ ID"),
                            RequestDocumentation.parameterWithName("customerId").description("๊ณ ๊ฐ(์—…๋กœ๋”) ID")
                    ))
    );
}

List<MultipartFile> reviewImages &nbsp; ๊ฐ€ ํ‘œํ˜„๋˜์ง€ ์•Š์€ ๋ชจ์Šต

 

@PostMapping("/request-part")
public ResponseEntity<Void> createReview(@Valid @RequestPart("reviewCreateRequest") ReviewCreateRequest reviewCreateRequest,
                                         @RequestPart("reviewImages") List<MultipartFile> reviewImages) {
    Long reviewId = 1L;

    return ResponseEntity.created(URI.create("/api/v1/review/" + reviewId)).build();
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ReviewCreateRequest {

    @NotNull
    private Integer rating;

    @NotBlank
    private String title;

    @NotBlank
    private String content;

    @NotNull
    private Long travelProductId;

    @NotNull
    private Long customerId;
}
@Test
void ๋ฆฌ๋ทฐ๋ฅผ_์ƒ์„ฑํ•œ๋‹ค_request_part() throws Exception {

    String content = objectMapper.writeValueAsString(new ReviewCreateRequest(5, "๋ฆฌ๋ทฐ ์ œ๋ชฉ", "๋ฆฌ๋ทฐ ๋‚ด์šฉ", 1L, 1L));
    MockMultipartFile json = new MockMultipartFile("reviewCreateRequest", "", "application/json", content.getBytes());

    // when
    ResultActions response = mockMvc.perform(
                    multipart("/api/v1/review/request-part")
                            .file("reviewImages", testImage1.getBytes())
                            .file(json)
                            .contentType("multipart/mixed")
                            .accept(MediaType.APPLICATION_JSON)

            )
            .andExpect(status().isCreated())
            .andExpect(header().string(HttpHeaders.LOCATION, "/api/v1/review/1"));

    // then
    response.andDo(
            document("review-createReview-request-part",
                    requestHeaders(
                            HeaderDocumentation.headerWithName(HttpHeaders.CONTENT_TYPE).description(MediaType.MULTIPART_FORM_DATA_VALUE)
                    ), requestParts(
                            partWithName("reviewImages").description("๋ฆฌ๋ทฐ ์ด๋ฏธ์ง€ ํŒŒ์ผ"),
                            partWithName("reviewCreateRequest").description("๋ฆฌ๋ทฐ ์ด๋ฏธ์ง€ ํŒŒ์ผ")
                    ))
    );
}

@RequestPart ํŒŒ๋ผ๋ฏธํ„ฐ์— ํ•ด๋‹นํ•˜๋Š” DTO์™€ Multipart list ๋ชจ๋‘ ํ‘œํ˜„๋˜์ง€ ์•Š๋Š” ๋ชจ์Šต

์ฆ‰, MultipartFile์ด๋‚˜ RequestPart ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์ด ์ œ๋Œ€๋กœ ๋ฌธ์„œํ™”๋˜์ง€ ์•Š๋Š” ๋ชจ์Šต์ด๋ฏ€๋กœ, ์ด ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด์„œ epages์˜ ์žฅ์ ์„ ๊ฐ€์ ธ๊ฐ€๊ณ  ์‹ถ๋‹ค๋ฉด ๊ณ ๋ คํ•ด๋ณผ๋งŒ ํ•œ ๊ฒƒ ๊ฐ™๋‹ค.

 

Swagger์™€ RestDocs๊ฐ„์˜ ํƒ€์ž… ์ง€์ • ํ˜•์‹์˜ ๋ถˆ์ผ์น˜

๋ฐ”๋กœ ์œ„์—์„œ ์†Œ๊ฐœํ•œ @ModelAttribute๋ฅผ ํฌํ•จํ•˜์—ฌ PathVariable ๋“ฑ reqeustParameters() ์— ํ•ด๋‹นํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์€ ํƒ€์ž…์„ ์ง€์ •ํ•  ์ˆ˜ ์—†๊ณ , ์˜ค์ง json ํ˜•์‹ ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค๋งŒ requestFields() ํƒ€์ž…์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ž…๋ ฅ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฐ˜ํ™˜์—์„œ๋„ json ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋งŒ ํƒ€์ž…์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ˆœํžˆ ํƒ€์ž…์„ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ ๋˜ํ•œ ๋‹จ์ ์ด์ง€๋งŒ, ๋ฌธ์ œ๋Š” Swagger์— ํ‘œํ˜„๋  ๋•Œ๊ฐ€ ๋ฌธ์ œ๋‹ค. Swagger๋Š” ๋ชจ๋“  ์ž…๋ ฅ ๋ฐ ๋ฐ˜ํ™˜ ๋ฐ์ดํ„ฐ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ช…์‹œ๋˜๊ธฐ ๋•Œ๋ฌธ์— RestDocs์—์„œ ๋ช…์‹œํ•˜์ง€ ๋ชปํ•œ ๋ฐ์ดํ„ฐ๋“ค์€ ๊ธฐ๋ณธ๊ฐ’์ธ string์œผ๋กœ ์ง€์ •๋œ๋‹ค. ์ด๋Š” API ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ์˜คํ•ด๋ฅผ ๋ถˆ๋Ÿฌ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

 

 


๋‚˜์˜ ์„ ํƒ

๋”ฐ๋ผ์„œ epages๋Š” ์•„์ง ํ”„๋กœ์ ํŠธ์— ๋„์ž…ํ•˜๊ธฐ ์ด๋ฅด๋‹ค๊ณ  ํŒ๋‹จํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์—, Swagger๋ฅผ ๋„์ž…ํ•˜๋˜ ๊ทธ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๋Š” ๋ฐฉํ–ฅ์„ ์„ ํƒํ•˜์˜€๋‹ค.

๊ฐ•์ œ๋˜์ง€ ์•Š๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ธํ•œ ๋ถ€์กฑํ•œ ์‹ ๋ขฐ๋„

mockMvc์™€ andExpect() ๋ฅผ ์ด์šฉํ•˜์—ฌ, ์ปจํŠธ๋กค๋Ÿฌ๋งˆ๋‹ค ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ ์šฉํ–ˆ๋‹ค.

 

์–ด๋…ธํ…Œ์ด์…˜๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์„ž์ž„์œผ๋กœ ์ธํ•œ ๊ฐ€๋…์„ฑ ๋ฌธ์ œ

๊ณต๋ฐฑ์„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๊ณ  Controller๋ฅผ ์„ธ๋ถ„ํ™”ํ•˜์—ฌ, ์–ด๋…ธํ…Œ์ด์…˜์„ ์ฃผ์„์ •๋„๋กœ ๋А๋‚„ ์ˆ˜ ์žˆ๋Š” ์„ ์œผ๋กœ ๊ฐ€๋…์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ–ˆ๋‹ค. ์•„๋ž˜ ์‚ฌ์ง„์€ ReviewController ์ด๊ณ , ๋ฆฌ๋ทฐ์˜ ์ง์ ‘์ ์ธ CRUD์˜ ์š”์ฒญ๋งŒ ๋“ค์–ด์žˆ๊ณ , ๋ฆฌ๋ทฐ ๋ถ„์„๊ฒฐ๊ณผ์— ํ•ด๋‹นํ•˜๋Š” ์š”์ฒญ๋“ค์€ ReviewTagController ๋กœ ๋ถ„๋ฆฌํ•˜์˜€๋‹ค.

์ž์„ธํ•˜๊ณ  ์ •๋ณด๋Ÿ‰์ด ๋งŽ์€ ๋ฌธ์„œ

Swagger๊ฐ€ ์ฝ”๋“œ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ์ž๋™์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์ง€๋งŒ, ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์ถ”๊ฐ€์ ์ธ ์ •๋ณด์™€ ํ•„์š”ํ• ๋งŒํ•œ ์ •๋ณด๋ฅผ ์ตœ๋Œ€ํ•œ ์ž์„ธํžˆ ์ ๊ณ , Swagger์˜ API Test ๊ธฐ๋Šฅ์„ ์ ๊ทน ํ™œ์šฉํ•˜๋„๋ก ์„ธํŒ…ํ•  ์˜ˆ์ •์ด๋‹ค. ๋‹ค์Œ์€ ๊ทธ ๊ณผ์ •๊ณผ ๊ณผ์ • ์ค‘์— ์žˆ๋˜ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์„ ๋ถ„์„ํ•ด๋ณธ ํฌ์ŠคํŒ…์ด๋‹ค.

Swagger์—์„œ MultipartFile๊ณผ DTO ํ•œ ๋ฒˆ์— ๋ฐ›๋Š” @RequestPart ์š”์ฒญ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค๊ธฐ

 

์‹ค์ œ ์ฝ”๋“œ์™€ ๋ฌธ์„œ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ

์ด ๋ฌธ์ œ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ์ž‘์„ฑํ•˜๋Š” ๋„์ค‘ ์‹ค์ˆ˜ํ•˜๊ฑฐ๋‚˜ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋ฆฌํŽ™ํ† ๋งํ•˜๊ณ  ๋‚˜์„œ ๋ฐ˜์˜ํ•˜์ง€ ์•Š์•„์„œ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, Github Copilot๋ฅผ ์ด์šฉํ•˜์—ฌ ๋งŽ์ด ๋ณด์™„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋ฆฌํŒฉํ† ๋งํ•˜๊ณ  ๋‚˜์„œ, ๋ณ€๊ฒฝ๋œ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ง€์› ๋‹ค๊ฐ€ ์ฝ”ํŒŒ์ผ๋Ÿฟ์„ ํ†ตํ•ด ๋‹ค์‹œ ์ž‘์„ฑํ•˜๋ฉด 95%์ •๋„๋Š” ์ •์ƒ์ ์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ๋‹ค. 5%์ •๋„์˜ ์˜ค์ฐจ ์ค‘์— 3%๋Š” ๋‚˜์˜ ์‹ค์ˆ˜ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ ๊ฒƒ์ด์—ˆ๋Š”๋ฐ, ์ฝ”ํŒŒ์ผ๋Ÿฟ์€ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ž๋™์™„์„ฑ์‹œ์ผœ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌํ„ด ๊ฐ’์ด๋ผ๋˜๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋“ฑ์— ์ž˜๋ชป ์ž‘์„ฑํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ์ž๋™์™„์„ฑ๋˜๋Š” ์ฝ”๋“œ ๋˜ํ•œ ์ž˜๋ชป๋œ๋‹ค.

๋งŒ์•ฝ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ API ๋ฌธ์„œ์™€ ์œ ์‚ฌํ•œ ํ˜•ํƒœ๋กœ ์ž๋™์™„์„ฑ์‹œ์ผœ ์ฃผ๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ, IDE์— ์ฐธ๊ณ ํ•  ๋‹ค๋ฅธ ์ฝ”๋“œ๋ฅผ ๋„์–ด๋†“๋Š”๋‹ค๋ฉด ์ฝ”ํŒŒ์ผ๋Ÿฟ์ด ์ด๋ฏธ ์ž‘์„ฑ๋œ ์œ ์‚ฌํ•œ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ž๋™์™„์„ฑ์‹œ์ผœ ์ค€๋‹ค. ์•„๋ž˜๋Š” ์ฐธ๊ณ ํ•  ์ฝ”๋“œ๋ฅผ ์•ˆ๋„์šด ์ž๋™์™„์„ฑ๊ณผ ๋„์šด ์ž๋™์™„์„ฑ์„ ๋น„๊ตํ•œ ๊ฒƒ์ด๋‹ค. ์›๋ž˜๋Š” @APIResponse ์˜ @Header ์–ด๋…ธํ…Œ์ด์…˜์˜ description ๋ถ€๋ถ„์— URL ์ด ์ถ”๊ฐ€ ๋˜์ง€ ์•Š์•˜์ง€๋งŒ, ์ฐธ๊ณ ํ•  ์ฝ”๋“œ๋ฅผ ๋„์šฐ๊ณ  ๋‚˜์„œ๋Š” URL๊นŒ์ง€ ์ถ”๊ฐ€๋˜๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

 


๋งˆ์น˜๋ฉฐ

OAS๊ธฐ๋ฐ˜ Swagger๋ผ๋Š” ์ƒˆ๋กœ์šด ๊ธฐ์ˆ ์„ ๋„์ž…ํ•˜์—ฌ ๊ธฐ์กด์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๊ธด ์‹œ๊ฐ„ ๋…ธ๋ ฅํ•˜์˜€์ง€๋งŒ, ๋‹จ์ ์˜ ๋ฐœ๊ฒฌ์œผ๋กœ ๋‹ค์‹œ ์›๋ž˜๋Œ€๋กœ ๋Œ์•„์˜ค์ง€๋งŒ ๋‹จ์ ์„ ์ตœ๋Œ€ํ•œ ์ƒ์‡„ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๋งˆ๋ฌด๋ฆฌ ๋˜์—ˆ๋‹ค. ์ดํ›„์— ๋‹ค์–‘ํ•œ Swagger ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด API ๋ฌธ์„œ์ž‘์„ฑํ•˜๋Š” ๊ณผ์ •์—์„œ ์ง„ํ–‰ํ•œ ๋‹ค์–‘ํ•œ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์€ ์•„๋ž˜์˜ ๊ฒŒ์‹œ๊ธ€์— ์ด์–ด๊ฐ€ ๋ณผ ์˜ˆ์ •์ด๋‹ค.

Swagger์—์„œ MultipartFile๊ณผ DTO ํ•œ ๋ฒˆ์— ๋ฐ›๋Š” @RequestPart ์š”์ฒญ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค๊ธฐ

OAS ๋นŒ๋“œ ๋ฐ Swagger ๋ฐ˜์˜ ์ž๋™ํ™” ๊ทธ๋ ˆ์ด๋“ค

ํ˜น์‹œ ๋‚˜์˜ ๋‹จ์  ๋ถ„์„์ด ์ž˜๋ชป๋˜์—ˆ๊ฑฐ๋‚˜ ๋‹จ์ ์„ ๊ฐ์•ˆํ•˜๊ณ ๋„ epages๋ฅผ ์“ฐ๊ณ  ์‹ถ๋‹ค๋ฉด, OAS ์ถ”์ถœ ๋ฐ ๋ฐ˜์˜ ์ž๋™ํ™”๋ฅผ ์œ„ํ•ด ์ž‘์„ฑํ•œ ๋‚˜์˜ ๊ทธ๋ ˆ์ด๋“ค ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค. epageSwagger ํ…Œ์Šคํฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ๋ณ€๊ฒฝ๋œ ์ปจํŠธ๋กค๋Ÿฌ์™€ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜์˜ํ•˜์—ฌ ๋นŒ๋“œํ•˜๊ณ , Swagger์— ๋ฐ˜์˜ํ•˜๋Š” ์ž๋™ํ™”์ด๋‹ค. ๊ธฐ์กด์˜ ๋นŒ๋“œํŒŒ์ผ์„ ์ง€์šฐ๊ณ  ๋นŒ๋“œํ•˜์ง€ ์•Š์œผ๋ฉด, ์‚ญ์ œ๋œ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•ด์„œ ๋งŒ๋“ค์—ˆ๋‹ค.

openapi3 {
    servers = [{ url = "https://localhost:8080/" }]
    title = "Review Mate API"
    description = "Review Mate WAS API"
    version = "0.1.0"
    format = "yaml"
}

tasks.named('test') {
    useJUnitPlatform()
}

tasks.register('clearSwagger') {
    mustRunAfter('test')

    doLast {
        delete("build/")
        println "::: 1. Clear 'build/'"

        delete("src/main/resources/static/swagger-ui/openapi3.yaml") // ๊ธฐ์กด OAS ํŒŒ์ผ ์‚ญ์ œ
        println "::: 2. Delete 'rc/main/resources/static/swagger-ui/openapi3.yaml'"
    }
}

tasks.register('runOpenApi3') {
    mustRunAfter('clearSwagger')

    doLast {
        exec {
            commandLine './gradlew', 'openapi3'
        }
        println "::: 3. Run openapi3'"
    }
}

tasks.register('copyOasToSwagger') {
    mustRunAfter('runOpenApi3')

    doLast {
        copy {
            from "$buildDir/api-spec/openapi3.yaml" // ๋ณต์ œํ•  OAS ํŒŒ์ผ ์ง€์ •
            into "src/main/resources/static/swagger-ui/." // ํƒ€์ผ“ ๋””๋ ‰ํ† ๋ฆฌ์— ํŒŒ์ผ ๋ณต์ œ

        }
        println "::: 4. Copy OAS file to swagger-ui"
    }
}

tasks.register('epagesSwagger') {
    doFirst {
        println ":: 0. Run ePages Swagger'"
    }

    dependsOn 'test'
    dependsOn 'clearSwagger'
    dependsOn 'runOpenApi3'
    dependsOn 'copyOasToSwagger'
}

tasks.named('build') {
    dependsOn 'copyOasToSwagger'
}

 

์ฐธ๊ณ ์ž๋ฃŒ

https://tech.kakaopay.com/post/openapi-documentation/#13-swagger-file-์ˆ˜์ •

https://helloworld.kurly.com/blog/spring-rest-docs-guide/

https://luvstudy.tistory.com/186

https://yongc.tistory.com/18

https://github.com/spring-projects/spring-restdocs/wiki/Spring-REST-Docs-3.0-Release-Notes

https://ykh6242.tistory.com/entry/Spring-Web-MVC-Multipart-์š”์ฒญ-๋‹ค๋ฃจ๊ธฐ

https://velog.io/@tmdgh0221/Spring-Rest-Docs-์ ์šฉํ•ด๋ณด๊ธฐ

https://techblog.woowahan.com/2597/

https://discuss.gradle.org/t/what-is-dolast-for/27731

https://kotlinworld.com/322

 

'๐Ÿค– Backend > SpringBoot' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

์Šคํ”„๋ง jpa application.properties ์„ค์ •  (0) 2024.02.25
์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ์— ์†Œ๋‚˜ํ๋ธŒ์™€ Jacoco ๋“ฑ๋กํ•˜๊ณ  ์ฝ”๋“œ ํ’ˆ์งˆ ํ™•์ธํ•˜๊ธฐ  (0) 2024.02.25
Swagger์—์„œ MultipartFile๊ณผ DTO ํ•œ ๋ฒˆ์— ๋ฐ›๋Š” @RequestPart ์š”์ฒญ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค๊ธฐ  (0) 2024.02.25
enum ํ•„๋“œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•˜๊ธฐ  (0) 2024.02.25
์™ธ๋ถ€ ์„œ๋น„์Šค ์š”์ฒญ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋ฆฌ๋ทฐ ์—…๋กœ๋“œ ์š”์ฒญ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๊ฐ•๊ฒฐํ•ฉ ๊ตฌ์กฐ๋ฅผ ๊ฐœ์„ ํ•ด๋ณด์ž.  (0) 2024.02.25
  1. Swagger
  2.  
  3. RestDocs
  4.  
  5. epages, OpenAPI Specification(OAS) ๊ธฐ๋ฐ˜ API ๋ฌธ์„œํ™”
  6. restdocs-api-spec ์˜คํ”ˆ์†Œ์Šค
  7. epages ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…
  8. ๋ถ€์กฑํ•œ ์ฐธ๊ณ ์ž๋ฃŒ
  9. @ModelAttribute, @RequestParts ๋Œ€์‘
  10. Swagger์™€ RestDocs๊ฐ„์˜ ํƒ€์ž… ์ง€์ • ํ˜•์‹์˜ ๋ถˆ์ผ์น˜
  11. ๊ฐ•์ œ๋˜์ง€ ์•Š๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ธํ•œ ๋ถ€์กฑํ•œ ์‹ ๋ขฐ๋„
  12. ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์„ž์ž„์œผ๋กœ ์ธํ•œ ๊ฐ€๋…์„ฑ ๋ฌธ์ œ
  13. ์ž์„ธํ•˜๊ณ  ์ •๋ณด๋Ÿ‰์ด ๋งŽ์€ ๋ฌธ์„œ
  14. ์‹ค์ œ ์ฝ”๋“œ์™€ ๋ฌธ์„œ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ
  15. OAS ๋นŒ๋“œ ๋ฐ Swagger ๋ฐ˜์˜ ์ž๋™ํ™” ๊ทธ๋ ˆ์ด๋“ค
  16. ์ฐธ๊ณ ์ž๋ฃŒ
'๐Ÿค– Backend/SpringBoot' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ์Šคํ”„๋ง jpa application.properties ์„ค์ •
  • ์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ์— ์†Œ๋‚˜ํ๋ธŒ์™€ Jacoco ๋“ฑ๋กํ•˜๊ณ  ์ฝ”๋“œ ํ’ˆ์งˆ ํ™•์ธํ•˜๊ธฐ
  • Swagger์—์„œ MultipartFile๊ณผ DTO ํ•œ ๋ฒˆ์— ๋ฐ›๋Š” @RequestPart ์š”์ฒญ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค๊ธฐ
  • enum ํ•„๋“œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•˜๊ธฐ
sckwon770
sckwon770
sckwon770
sckwon770
sckwon770
์ „์ฒด
์˜ค๋Š˜
์–ด์ œ
  • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (58)
    • ๐Ÿš€ Activity (6)
      • Project (5)
      • Experience (1)
    • ๐Ÿค– Backend (28)
      • Linux (1)
      • SpringBoot (15)
      • Database (7)
      • Web (2)
      • Cloud (2)
      • Test (1)
    • ๐Ÿ›  ๊ฐœ๋ฐœ์ž (1)
      • ํšŒ๊ณ  (0)
      • ๋…์„œ (1)
    • ๐Ÿ”ฅ Algorithm (5)
      • ๋ฐฑ์ค€ (2)
      • ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค (1)
      • PS (1)
    • ๐Ÿ‘พ CS (0)
    • Python (2)
      • Programming (0)
      • PS Skills (0)
    • Csharp (15)

๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

  • ํ™ˆ
  • ํƒœ๊ทธ
  • ๋ฐฉ๋ช…๋ก

๊ณต์ง€์‚ฌํ•ญ

์ธ๊ธฐ ๊ธ€

ํƒœ๊ทธ

  • C#
  • Hibernate
  • mysql
  • JPA
  • jacoco
  • ํ…Œ์ŠคํŠธ
  • ์ธ๋ฑ์Šค
  • java
  • CS
  • ThreadPoolTaskExecutor
  • springboot
  • Python
  • algorithm
  • ULID
  • WPF
  • SSH
  • ๋ฆฌ๋ทฐ๋ฉ”์ดํŠธ
  • TSID
  • SWAGGER
  • ํŒŒ์ด์ฌ

์ตœ๊ทผ ๋Œ“๊ธ€

์ตœ๊ทผ ๊ธ€

hELLO ยท Designed By ์ •์ƒ์šฐ.v4.2.2
sckwon770
ํ”„๋ก ํŠธ๊ฐ€ ํ˜ผ๋ž€์— ๋น ์ง€์ง€ ์•Š๋Š” ์„œ๋ฒ„์™€ API ๋ฌธ์„œ ๋งŒ๋“ค๊ธฐ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”

๋‹จ์ถ•ํ‚ค

๋‚ด ๋ธ”๋กœ๊ทธ

๋‚ด ๋ธ”๋กœ๊ทธ - ๊ด€๋ฆฌ์ž ํ™ˆ ์ „ํ™˜
Q
Q
์ƒˆ ๊ธ€ ์“ฐ๊ธฐ
W
W

๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€

๊ธ€ ์ˆ˜์ • (๊ถŒํ•œ ์žˆ๋Š” ๊ฒฝ์šฐ)
E
E
๋Œ“๊ธ€ ์˜์—ญ์œผ๋กœ ์ด๋™
C
C

๋ชจ๋“  ์˜์—ญ

์ด ํŽ˜์ด์ง€์˜ URL ๋ณต์‚ฌ
S
S
๋งจ ์œ„๋กœ ์ด๋™
T
T
ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ ์ด๋™
H
H
๋‹จ์ถ•ํ‚ค ์•ˆ๋‚ด
Shift + /
โ‡ง + /

* ๋‹จ์ถ•ํ‚ค๋Š” ํ•œ๊ธ€/์˜๋ฌธ ๋Œ€์†Œ๋ฌธ์ž๋กœ ์ด์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ํ‹ฐ์Šคํ† ๋ฆฌ ๊ธฐ๋ณธ ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.