๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๊ฐœ๋ฐœ/DND

Spring REST Docs + Stoplight๋กœ ํ…Œ์ŠคํŠธ ๊ธฐ๋ฐ˜ API ๋ฌธ์„œ ๊ตฌ์„ฑํ•˜๊ธฐ

์ด์ „ ๊ธ€์—์„œ Swagger UI์˜ ๊ฐ€๋…์„ฑ ๋ฌธ์ œ๋ฅผ ๊ฒช๊ณ  Stoplight Elements๋กœ ์ „ํ™˜ํ–ˆ์—ˆ๋‹ค. ๋‹น์‹œ ๋งˆ๋ฌด๋ฆฌ์—์„œ "์ถ”ํ›„ Spring REST Docs + RestDocs-Api-Spec ์กฐํ•ฉ์œผ๋กœ ๊ตฌ์„ฑํ•  ์˜ˆ์ •" ์ด๋ผ๊ณ  ๋‚จ๊ฒผ๋Š”๋ฐ, ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ์‹ค์ œ๋กœ ํ•ด๋ดค๋‹ค.

๊ธฐ์กด ๋ฐฉ์‹์˜ ๋ฌธ์ œ

์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๋ฌธ์„œํ™”๋Š” ์ฝ”๋“œ์™€ ๋ฌธ์„œ๊ฐ€ ๋ถ„๋ฆฌ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋А๊ปด์ง€์ง€๋งŒ, ์‚ฌ์‹ค์ƒ ๋”ฐ๋กœ ๋…ผ๋‹ค.

ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— @Operation, @Schema ๊ฐ™์€ ๋ฌธ์„œ์šฉ ์–ด๋…ธํ…Œ์ด์…˜์ด ๊ฐ€๋“ ๋ถ™๊ณ , ์‹ค์ œ๋กœ API๊ฐ€ ๊ทธ ์„ค๋ช…๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€๋Š” ๊ฒ€์ฆ๋˜์ง€ ์•Š๋Š”๋‹ค. ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์ด ๋ฐ”๋€Œ์–ด๋„, ์‘๋‹ต ๊ตฌ์กฐ๊ฐ€ ๋‹ฌ๋ผ์ ธ๋„ ๋ฌธ์„œ๋Š” ๋ชจ๋ฅธ๋‹ค.

๊ทธ๋ž˜์„œ ๋งค๋ฒˆ API์˜ ์š”์ฒญ/์‘๋‹ต ๊ตฌ์กฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋‚ด์šฉ๋„ ํ•จ๊ป˜ ๋ณ€๊ฒฝํ•ด์ค˜์•ผ ํ•˜๋Š” ์ปจํ…์ŠคํŠธ๋ฅผ ๊ธฐ์–ตํ•ด์•ผ ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ด๋Ÿฌํ•œ ์ปจํ…์ŠคํŠธ์˜ ์ฆ๊ฐ€๋Š” ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ข‹์ง€ ์•Š์€ ์‹ ํ˜ธ์ด๊ธฐ์— ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•ด์•ผ๋งŒ ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๊ตฌ์กฐ๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ํŒ€์›์—๊ฒŒ ์ด๋Ÿฌํ•œ ์˜๊ฒฌ์„ ์ „๋‹ฌํ•˜์˜€๊ณ , ํŒ€์›๋„ ๊ณต๊ฐํ•˜์—ฌ Spring REST docs + Restdocs-api-spec ์กฐํ•ฉ์œผ๋กœ ๋ฌธ์„œํ™” ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์„ฑํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์•„์ง ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž๋กœ๋ถ€ํ„ฐ ์‹ค์งˆ์ ์ธ ๊ฐ€๋…์„ฑ ๋ฌธ์ œ์— ๋Œ€ํ•œ ์–˜๊ธฐ๊ฐ€ ์—†์—ˆ์œผ๋ฏ€๋กœ swagger-ui๋ฅผ ํ†ตํ•ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ๋ฌธ์„œ ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ์˜ํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋“ค

ํ…Œ์ŠคํŠธ์— ์ ์šฉํ•œ ๋‚ด์šฉ์ด ๋ฐ˜์˜๋˜์ง€ ์•Š์•„์š”!

๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•˜๊ณ  ๋ฌธ์„œ ํ…Œ์ŠคํŠธ๋„ ์ž˜ ์ž‘์„ฑํ•˜๊ณ  ๋‚œ ๋’ค, ๋งŒ๋“ค์–ด์ง„ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด์„œ ์ด์ƒํ•œ ๋ถ€๋ถ„๋“ค์ด ๋ชฉ๊ฒฉ๋˜์—ˆ๋‹ค. ๋ถ„๋ช… ๋ฌธ์„œ ํ…Œ์ŠคํŠธ์—๋Š” .summary(), .description()์„ ํ•œ๊ธ€๋กœ ์ž˜ ์ž‘์„ฑํ–ˆ๋Š”๋ฐ ๋ฌธ์„œ์—์„œ๋Š” ์ด์ƒํ•˜๊ฒŒ ์˜์–ด๋กœ ํ‘œ์‹œ๋˜๊ฑฐ๋‚˜ ๋ฌด์‹œ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  contentType์ด๋‚˜ ๊ธฐํƒ€ ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ ํ•ญ๋ชฉ๋“ค์ด ์ œ๋Œ€๋กœ ๋จนํžˆ์ง€ ์•Š๋Š” ์ด์Šˆ๊ฐ€ ์žˆ์—ˆ๋‹ค.

 
๊ทธ๋ž˜์„œ ์™œ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ์›์ธ์„ ์กฐ์‚ฌํ•ด๋ดค๋‹ค.

๋ฌธ์ œ์˜ ์›์ธ์€ springdoc์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋Ÿฐํƒ€์ž„์— ํด๋ž˜์Šค๋ฅผ ์Šค์บ”ํ•ด์„œ ์ž์ฒด ์ŠคํŽ™์„ /v3/api-docs๋กœ ์ž๋™ ์ƒ์„ฑํ•œ๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๋ณ„๋„ ์„ค์ •์„ ํ•˜์ง€ ์•Š์€ ํ˜„์žฌ ์ƒํ™ฉ์—์„œ ๊ธฐ๋ณธ URL์„ ๋ฐ”๋ผ๋ณด๋‹ˆ ํ…Œ์ŠคํŠธ์—์„œ ์ž‘์„ฑ๋œ ๋‚ด์šฉ์ด ์•„๋‹Œ ๋Ÿฐํƒ€์ž„์— ์ƒ์„ฑ๋œ openapi3.yaml์„ ์ฝ๊ณ  ์žˆ๋˜ ๊ฒƒ์ด๋‹ค.

๊ทธ๋ž˜์„œ application.yml์— ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ•ด๊ฒฐํ–ˆ๋‹ค. [๊ด€๋ จ PR]

springdoc:
    swagger-ui:
      url: /api-docs/openapi3.yaml

 
 

Swagger-ui์˜ ๊ฐ€๋…์„ฑ ๋ฌธ์ œ

๋ฌธ์„œ ํ…Œ์ŠคํŠธ๋„ ์ž˜ ์ž‘์„ฑํ•˜๊ณ , ์„ค๋ช…๋„ ์ถฉ์‹คํžˆ ํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ณ  ์žˆ์—ˆ์ง€๋งŒ ์ง€์†์ ์œผ๋กœ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž ๋ถ„๋“ค์—๊ฒŒ ์˜ค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ์—ˆ๋‹ค. ๊ทธ๊ฒŒ ๋ฐ”๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€์ด๋‹ค.

 
์ด๊ฑด ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์ธ '๋Ÿฐ์„ธ๊ถŒ'์—์„œ๋„ ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ์ธ๋ฐ, ์„ค๋ช…์„ ์ž˜ ์ž‘์„ฑํ•ด๋†“์•„๋„ swagger-ui ํŠน์„ฑ์ƒ ์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด id์— ๋Œ€ํ•œ ์„ค๋ช…์ด ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ์•„๋ž˜ ์‚ฌ์ง„์ฒ˜๋Ÿผ Schema๋ฅผ ์„ ํƒํ•˜๊ณ  ๊ฐ ํ•ญ๋ชฉ๋“ค์˜ ํ† ๊ธ€์„ ๋ˆŒ๋Ÿฌ์•ผ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค. 
 

 
ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ๋ถ€ํ„ฐ ์ง€๊ธˆ๊นŒ์ง€ ์ง€์†์ ์œผ๋กœ ์ด๋Ÿฌํ•œ ์ด์•ผ๊ธฐ๊ฐ€ ๋‚˜์˜ค๋Š” ๊ฒƒ์— swagger-ui ๋ณด๋Š” ๋ฒ•์„ ๊ฐ€๋ฅด์ณ์ฃผ๋Š” ๊ฒƒ๋ณด๋‹ค ์ถ”ํ›„ ์ƒˆ๋กœ์šด ํŒ€์› ํ˜น์€ ๋ณด๋Š” ๋ฐฉ๋ฒ•์„ ๊นŒ๋จน์—ˆ์„ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด์„œ ์•„์˜ˆ ์ƒˆ๋กœ์šด ํˆด์ธ 'Stoplight'๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

Stoplight๋ฅผ ์„ ํƒํ•œ ์ด์œ ๋Š” ์ด์ „ ๊ธ€์—์„œ ์„ ํƒํ•œ ์ด์œ ์™€ ๋™์ผํ•˜๋ฉฐ, ํ˜„์žฌ ์ต์ˆ™ํ•œ ํˆด์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ตฌ์„ฑํ•œ ํŒŒ์ดํ”„๋ผ์ธ

docsTest (RestDocs ํƒœ๊ทธ ํ…Œ์ŠคํŠธ)
  → openapi3 (์Šค๋‹ˆํŽซ → OpenAPI YAML ์ƒ์„ฑ)
    → copyOpenApiToStatic (YAML์„ ์„œ๋น™ ๊ฒฝ๋กœ๋กœ ๋ณต์‚ฌ)
      → bootJar / bootRun


๊ฐ ๋‹จ๊ณ„๋ฅผ Gradle ํƒœ์Šคํฌ๋กœ ์—ฐ๊ฒฐํ–ˆ๋‹ค. docsTest๋Š” @Tag("restdocs")๊ฐ€ ๋ถ™์€ ํ…Œ์ŠคํŠธ๋งŒ ๊ณจ๋ผ ์‹คํ–‰ํ•˜๊ณ , ์Šค๋‹ˆํŽซ์„ build/generated-snippets์— ์Œ“๋Š”๋‹ค. openapi3 ํƒœ์Šคํฌ(epages ํ”Œ๋Ÿฌ๊ทธ์ธ)๊ฐ€ ์Šค๋‹ˆํŽซ์„ ์ฝ์–ด YAML๋กœ ๋งŒ๋“ ๋‹ค.

Stoplight Elements๋Š” ๋ณ„๋„ ์„œ๋ฒ„๊ฐ€ ํ•„์š” ์—†๋‹ค. HTML ํŒŒ์ผ ํ•˜๋‚˜๊ฐ€ ์ „๋ถ€๋‹ค.

<elements-api
    apiDescriptionUrl="/api-docs/openapi3.yaml"
    router="hash"
    layout="sidebar"
  />


springdoc ์˜์กด์„ฑ๋„ ์ œ๊ฑฐํ–ˆ๋‹ค. Stoplight HTML์ด ์ง์ ‘ YAML์„ ์ฝ๊ธฐ ๋•Œ๋ฌธ์— springdoc์ด ํ•  ์ผ์ด ์—†์–ด์กŒ๋‹ค.

์ด๋Ÿฌํ•œ ํŒŒ์ดํ”„ ๋ผ์ธ์„ ์ ์šฉํ•˜๋ฉด์„œ๋„ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ถ”๊ฐ€๋กœ ์ ์šฉํ–ˆ๋‹ค.

JWT ๋ณด์•ˆ ์Šคํ‚ค๋งˆ ์ž๋™ ํŒจ์น˜ ์ œ๊ฑฐ

swagger-ui์—์„œ๋Š” ๋ณด์•ˆ ์Šคํ‚ค๋งˆ๋ฅผ ๋ถ™์—ฌ์ฃผ์ง€ ์•Š์œผ๋ฉด, ๊ฐ API ์š”์•ฝ ์˜†์— ์ž๋ฌผ์‡  ์•„์ด์ฝ˜์ด ๋ถ™์ง€ ์•Š๋Š”๋‹ค.


๊ทธ๋ž˜์„œ ํ—ค๋”์—๋งŒ ํ† ํฐ์ด ํ•„์š”ํ•จ์„ ๋ถ™์—ฌ๋†“์œผ๋ฉด ์ด๋ฅผ ํ•œ ๋ˆˆ์— ํŒŒ์•…ํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š์•˜๋‹ค. ํ•˜์ง€๋งŒ, Stoplight๋Š” ๋ณด์•ˆ ์Šคํ‚ค๋งˆ๋ฅผ ์ ์šฉํ•˜์ง€ ์•Š๊ณ , ํ—ค๋”์—๋งŒ ๋…ธ์ถœํ•ด๋„ ํฌ๊ฒŒ ๋ถˆํŽธํ•œ ์ ์ด ์—†๋‹ค.


์‚ฌ์ง„์ฒ˜๋Ÿผ ํ—ค๋”๊ฐ€ ๋ช…์‹œ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์Šคํ‚ค๋งˆ๋ฅผ ์ ์šฉํ•œ๋‹ค๊ณ  ํ•ด๋„ ํ—ค๋” ๋ฐฉ์‹๊ณผ ํฐ ์ฐจ์ด์ ์ด ์—†๋‹ค.
๊ทธ๋ž˜์„œ build.gradle์—์„œ ์ƒ๋‹น์ˆ˜๋ฅผ ์ฐจ์ง€ํ•˜๋˜ jwt ๋ณด์•ˆ ์Šคํ‚ค๋งˆ ์ž๋™ ํŒจ์น˜๋ฅผ ์ œ๊ฑฐํ–ˆ๋‹ค.

์šด์˜ ์„œ๋ฒ„ ๋…ธ์ถœ ๋ฌธ์ œ

๊ตฌ์„ฑ์„ ๋งˆ์น˜๊ณ  ๋ฌธ๋“ "์šด์˜ ์„œ๋ฒ„์—์„œ๋„ ๋ฌธ์„œ๊ฐ€ ๋ณด์ด๋Š” ๊ฑฐ ์•„๋‹ˆ์•ผ?" ๋ผ๋Š” ์งˆ๋ฌธ์ด ์ƒ๊ฐ๋‚ฌ๋‹ค.

์ฒ˜์Œ์—” static/docs/index.html์„ Spring์˜ ์ •์  ๋ฆฌ์†Œ์Šค ๊ฒฝ๋กœ์— ๋’€๋Š”๋ฐ, ์ด ๊ฒฝ๋กœ๋Š” ๋ชจ๋“  ํ™˜๊ฒฝ์˜ JAR์— ํฌํ•จ๋œ๋‹ค. ์ด์ „์— Springdoc์„ ์“ธ ๋•Œ๋Š” application-prod.yml์—์„œ swagger-ui.enabled: false๋กœ ๋ง‰์•˜์ง€๋งŒ, ์ •์  ํŒŒ์ผ์€ ๊ทธ ์„ค์ •์œผ๋กœ ์ œ์–ด๊ฐ€ ์•ˆ ๋œ๋‹ค.

ํ•˜์ง€๋งŒ ์ž ๊น ์ƒ๊ฐํ•ด๋ณด๋‹ˆ ๊ตณ์ด ๋ณต์žกํ•˜๊ฒŒ ๋ง‰์„ ํ•„์š”๊ฐ€ ์—†์—ˆ๋‹ค.

์šด์˜ ๋นŒ๋“œ ์Šคํฌ๋ฆฝํŠธ๋Š” ./gradlew clean build -x test -x docsTest๋กœ docsTest๋ฅผ ์Šคํ‚ตํ•œ๋‹ค. docsTest๊ฐ€ ์Šคํ‚ต๋˜๋ฉด ์Šค๋‹ˆํŽซ์ด ์ƒ์„ฑ๋˜์ง€ ์•Š๊ณ , ์Šค๋‹ˆํŽซ์ด ์—†์œผ๋ฉด YAML๋„ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค. YAML ์—†์ด bootJar๋ฅผ ์‹คํ–‰ํ•˜๋ฉด JAR์— YAML์ด ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค. HTML ํŽ˜์ด์ง€๊ฐ€ ์—ด๋ ค๋„ YAML์„ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ•ด ๋นˆ ์—๋Ÿฌ ํ™”๋ฉด๋งŒ ๋ณด์ธ๋‹ค.

์˜๋„์ ์œผ๋กœ ๋ง‰์ง€ ์•Š์•„๋„ ๋นŒ๋“œ ๊ตฌ์กฐ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์šด์˜ ํ™˜๊ฒฝ์—์„œ์˜ ๋…ธ์ถœ์„ ๋ฐฉ์ง€ํ•ด์ฃผ๋Š” ์…ˆ์ด๋‹ค.

์ด์— ๋ฐ˜ํ•ด ๊ฐœ๋ฐœ ์„œ๋ฒ„ ๋นŒ๋“œ๋Š” ./gradlew clean build -x test๋กœ docsTest๊ฐ€ ์‹คํ–‰๋˜๊ณ , YAML์ด ์ƒ์„ฑ๋˜์–ด JAR์— ํฌํ•จ๋˜์–ด ๋ฌธ์„œ๊ฐ€ ์ •์ƒ ์„œ๋น™๋œ๋‹ค.

๋งˆ์น˜๋ฉฐ

REST Docs ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜์„œ ๊ฐ€์žฅ ์ข‹์€ ์ ์€ ๋ฌธ์„œ ์‹ ๋ขฐ๋„๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง€๋ฉด YAML๋„ ์ƒ์„ฑ์ด ์•ˆ๋˜๋‹ˆ, ๋ฌธ์„œ์™€ ์‹ค์ œ API๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜ ์—†๋‹ค. ์–ด๋…ธํ…Œ์ด์…˜์„ ์—†์• ๋Š” ๋Œ€์‹  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ๋ฌธ์„œ์˜ ์—ญํ• ์„ ๊ฒธํ•˜๊ฒŒ ๋˜์–ด ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•ด์กŒ๋‹ค.

Stoplight Elements๋Š” ์—ฌ์ „ํžˆ ๋งŒ์กฑ์Šค๋Ÿฝ๋‹ค. CDN ํ•œ ์ค„๋กœ ์™„์„ฑ๋˜๋Š” UI์น˜๊ณ ๋Š” ํ€„๋ฆฌํ‹ฐ๊ฐ€ ์ข‹๋‹ค. [๊ด€๋ จ PR]
 

'๊ฐœ๋ฐœ > DND' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

ํšŒ์›ํƒˆํ‡ด๋ฅผ ์–ด๋–ป๊ฒŒ ์„ค๊ณ„ํ• ๊นŒ  (0) 2026.02.10