[Spring] Swagger ์„ค์ • ๋ฐ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
Back-end/Spring

[Spring] Swagger ์„ค์ • ๋ฐ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

728x90

๐Ÿ“‘ ๊ฐœ์š”

Swagger๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API ๋ช…์„ธ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

 

๐Ÿ“‘ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ

SpringBoot : 3.3.5
JDK : 17
build Tools : gradle
Editor : InteliJ

 

๐Ÿ“‘ ์˜์กด์„ฑ ์ถ”๊ฐ€

Spring Boot 3.x ๋ฒ„์ „์—์„œ์˜ Swagger ์ ์šฉ์„ ์œ„ํ•œ ์˜์กด์„ฑ ์ถ”๊ฐ€

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'

 

๐Ÿ“‘ Swagger Config ์ž‘์„ฑ

package withbeetravel.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // ์„ค์ • ํŒŒ์ผ์„ ์ฝ๊ธฐ ์œ„ํ•œ annotation
public class SwaggerConfig {

    @Bean
    public OpenAPI openAPI() {

        // Swagger์—์„œ ๋ณด์—ฌ์ค„ API ์ •๋ณด ์„ค์ •
        Info info = new Info()
                .version("1.0.0") // ์„œ๋น„์Šค ๋ฒ„์ „
                .title("WithBee Travel") // ํƒ€์ดํ‹€
                .description("WithBee Travel๐Ÿ๐Ÿ›ซ Project API"); // ์„ค๋ช…

        return new OpenAPI().info(info);
    }
}

 

๐Ÿ“‘ Swagger ์ ‘์† ํ™•์ธ

๊ธฐ๋ณธ์ ์ธ swagger ์ ‘์† URL์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

{์„œ๋น„์Šค ๊ธฐ๋ณธ URL}/swagger-ui/index.html

ํ˜„์žฌ ๋กœ์ปฌ์—์„œ ์ ‘์† ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณผ ๊ฒƒ์ด๊ณ ,

์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์—์„  ๋”ฐ๋กœ ์ง€์ •ํ•ด์ค€ context-path๊ฐ€ ์—†์œผ๋‹ˆ ๋‹ค์Œ URL๋กœ ์ ‘์†ํ•œ๋‹ค.

http://localhost:8080/swagger-ui/index.html

๊ทธ๋Ÿผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด swagger์— ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ“‘ Swagger ์ž‘์„ฑ์„ ์œ„ํ•œ Annotation

๐Ÿ‘‰ @Tag

API ๊ทธ๋ฃน์„ ์ •์˜ํ•˜๊ณ  ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜์—ฌ, API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ธฐ๋Šฅ๋ณ„๋กœ ๋ฌถ์–ด ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋„์™€์ค€๋‹ค.

 

@Tag๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ์ ์šฉ๋œ๋‹ค.

๊ฐ @RestController ํด๋ž˜์Šค๋‚˜ ํŠน์ • API ๊ทธ๋ฃน์„ ๋Œ€ํ‘œํ•˜๋Š” ํด๋ž˜์Šค์— ์ถ”๊ฐ€ํ•˜์—ฌ ํ•ด๋‹น ๊ทธ๋ฃน์˜ ์„ค๋ช…์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

package withbeetravel.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/travels/{travelId}/payments")
@Tag(name = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ API", description = "์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.")
public class PaymentController {

	...
}
  • name: ํƒœ๊ทธ ์ด๋ฆ„ ์ง€์ •
  • description: ํƒœ๊ทธ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ•ด๋‹น API ๊ทธ๋ฃน์˜ ์—ญํ• ๊ณผ ๊ธฐ๋Šฅ์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์™€ ๊ฐ™์ด @Tag annotation์„ ๋ถ™์—ฌ์ฃผ๋ฉด swagger์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ‘‰ @Operation

OpenAPI ๋ฌธ์„œ์— ํŠน์ • API ์—”๋“œํฌ์ธํŠธ์˜ ์ƒ์„ธํ•œ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

 

@Operation์€ ๋ฉ”์„œ๋“œ ๋ ˆ๋ฒจ์—์„œ ์ ์šฉ๋˜๋ฉฐ

API์˜ ๊ธฐ๋Šฅ, ์„ค๋ช…, ์š”์ฒญ๊ณผ ์‘๋‹ต์˜ ์„ธ๋ถ€์‚ฌํ•ญ ๋“ฑ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

package withbeetravel.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import withbeetravel.dto.ChooseParticipantsRequestDto;
import withbeetravel.dto.SuccessResponseDto;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/travels/{travelId}/payments")
@Tag(name = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ API", description = "์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.")
public class PaymentController {

    @Operation(
            summary = "์ •์‚ฐ ์ธ์› ์„ ํƒ",
            description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ์— ๋Œ€ํ•œ ์ •์‚ฐ ์ธ์›์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
            tags = {"User Management"}
    )
    @PatchMapping("/{sharedPaymentId}/participants")
    public ResponseEntity<String> chooseParticipant(@PathVariable Long travelId,
                                                                @PathVariable Long sharedPaymentId,
                                                                @RequestBody ChooseParticipantsRequestDto requestDto) {

        return null;
    }
}
  • summary:
    API ์—”๋“œํฌ์ธํŠธ์˜ ๊ฐ„๋žตํ•œ ์„ค๋ช…์„ ์ œ๊ณตํ•œ๋‹ค.
    Swagger UI์—์„œ API ์ œ๋ชฉ์œผ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, ์—”๋“œํฌ์ธํŠธ์˜ ์ฃผ์š” ๊ธฐ๋Šฅ์„ ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • description:
    API ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ์ƒ์„ธ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
    ์ด ํ•„๋“œ๋Š” API์˜ ๋™์ž‘ ๋ฐฉ์‹์ด๋‚˜ ์ถ”๊ฐ€์ ์ธ ์„ธ๋ถ€์‚ฌํ•ญ์„ ์ „๋‹ฌํ•˜๋Š” ๋ฐ ์œ ์šฉํ•˜๋‹ค.
  • tags:
    API๋ฅผ ํŠน์ • ํƒœ๊ทธ์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
    ํƒœ๊ทธ๋ฅผ ํ†ตํ•ด ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ธฐ๋Šฅ๋ณ„๋กœ ๊ทธ๋ฃนํ™”ํ•˜์—ฌ Swagger UI์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • parameters:
    API๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.
    URL ๊ฒฝ๋กœ์— ์žˆ๋Š” @PathVariable์ด๋‚˜ @RequestParam ๋“ฑ์— ๋Œ€ํ•ด ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•œ๋‹ค.
    (์•„๋ž˜์— ๋” ์ž์„ธํžˆ ๋‹ค๋ฃจ๊ฒ ๋‹ค.)

์œ„์™€ ๊ฐ™์ด @Operation annotation์„ ๋ถ™์—ฌ์ฃผ๋ฉด swagger์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

User Management ํƒœ๊ทธ๋ฅผ ๋“ฑ๋กํ•œ API๋„ ๋”ฐ๋กœ ๋ชจ์•„๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ‘‰ @Parameter

@Operation์ด๋‚˜ @RequestMapping, @GetMapping, @PostMapping ๋“ฑ์˜ ๋ฉ”์„œ๋“œ ๋ ˆ๋ฒจ annotation๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ

API์˜ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ(์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ, ๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ, ํ—ค๋”, ์š”์ฒญ ๋ณธ๋ฌธ ๋“ฑ)๋ฅผ ์„ค๋ช…ํ•œ๋‹ค.

package withbeetravel.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import withbeetravel.dto.ChooseParticipantsRequestDto;
import withbeetravel.dto.SuccessResponseDto;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/travels/{travelId}/payments")
@Tag(name = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ API", description = "์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.")
public class PaymentController {

    @Operation(
            summary = "์ •์‚ฐ ์ธ์› ์„ ํƒ",
            description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ์— ๋Œ€ํ•œ ์ •์‚ฐ ์ธ์›์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
            tags = {"User Management"},
            parameters = {
                    @Parameter(
                            name = "travelId",
                            description = "์—ฌํ–‰ ID",
                            required = true,
                            in = ParameterIn.PATH,
                            example = "1234"
                    ),
                    @Parameter(
                            name = "sharedPaymentId",
                            description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ ID",
                            required = true,
                            in = ParameterIn.PATH,
                            example = "1234"
                    )
            }
    )
    @PatchMapping("/{sharedPaymentId}/participants")
    public ResponseEntity<String> chooseParticipant(@PathVariable Long travelId,
                                                                @PathVariable Long sharedPaymentId,
                                                                @RequestBody ChooseParticipantsRequestDto requestDto) {

        return null;
    }
}
  • name (ํ•„์ˆ˜):
    ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ด๋ฆ„์„ ์ง€์ •ํ•œ๋‹ค.
    ์ด ์ด๋ฆ„์€ API ์š”์ฒญ ์‹œ ์‚ฌ์šฉ๋˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•œ๋‹ค.
  • description:
    ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์—ญํ• ์ด๋‚˜ ์šฉ๋„๋ฅผ ์„ค๋ช…ํ•˜๋Š” ํ…์ŠคํŠธ๋‹ค.
    Swagger UI์—์„œ ์ด ์„ค๋ช…์ด API ๋ฌธ์„œ์— ํ‘œ์‹œ๋œ๋‹ค.
  • required:
    ํ•„์ˆ˜์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•œ๋‹ค.
    true๋กœ ์„ค์ •ํ•˜๋ฉด ์ด ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ•„์ˆ˜๋กœ ์š”๊ตฌ๋œ๋‹ค.
    ๊ธฐ๋ณธ๊ฐ’์€ false์ด๋‹ค.
  • in:
    ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์œ„์น˜ํ•˜๋Š” ๊ณณ์„ ์ง€์ •ํ•œ๋‹ค.
    ๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” path, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” query, ์š”์ฒญ ํ—ค๋”๋Š” header, ์š”์ฒญ ๋ณธ๋ฌธ์€ default๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
    ๊ธฐ๋ณธ๊ฐ’์€ query์ด๋‹ค.
  • example:
    ํ•ด๋‹น ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์˜ˆ์‹œ ๊ฐ’์„ ์ œ๊ณตํ•œ๋‹ค.
    Swagger UI์—์„œ API ํ…Œ์ŠคํŠธ ์‹œ ์˜ˆ์‹œ ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • allowEmptyValue:
    ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋นˆ ๊ฐ’(ex. ๋นˆ ๋ฌธ์ž์—ด, null)๋„ ํ—ˆ์šฉํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•œ๋‹ค.
    true๋กœ ์„ค์ •ํ•˜๋ฉด ๋นˆ ๊ฐ’๋„ ํ—ˆ์šฉ๋œ๋‹ค.
    ๊ธฐ๋ณธ๊ฐ’์€ false์ด๋‹ค.

์œ„์™€ ๊ฐ™์ด @Parameter annotation์„ ๋ถ™์—ฌ์ฃผ๋ฉด swagger์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ‘‰ @Schema

๋ชจ๋ธ ํด๋ž˜์Šค์™€ ๊ทธ ํ•„๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฃผ๋กœ DTO, ๋ชจ๋ธ ํด๋ž˜์Šค, ๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ํด๋ž˜์Šค์˜ ํ•„๋“œ์— ์ ์šฉํ•˜์—ฌ API ๋ฌธ์„œ์—์„œ ํ•ด๋‹น ๊ฐ์ฒด์˜ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•œ๋‹ค.

package withbeetravel.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;

import java.util.List;

@Getter
@Schema(description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ์— ๋Œ€ํ•œ ์ •์‚ฐ ์ธ์› ๋ฆฌ์ŠคํŠธ DTO")
public class ChooseParticipantsRequestDto {

    @Schema(
            description = "์ •์‚ฐ ์ธ์› ๋ฆฌ์ŠคํŠธ",
            example = "[17, 19, 22, 27]"
    )
    List<Long> travelMembersId;
}
  • description:
    ํ•„๋“œ๋‚˜ ํด๋ž˜์Šค์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ œ๊ณตํ•œ๋‹ค.
    ํ•ด๋‹น ํ•„๋“œ๋‚˜ ํด๋ž˜์Šค์˜ ์˜๋ฏธ๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.
  • defaultValue:
    ํ•„๋“œ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ•œ๋‹ค.
    ํ•ด๋‹น ํ•„๋“œ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํ‘œ์‹œ๋œ๋‹ค.
  • example:
    ํ•„๋“œ์˜ ์˜ˆ์‹œ ๊ฐ’์„ ์ œ๊ณตํ•œ๋‹ค.
    ์˜ˆ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.
  • allowableValues:
    ํ•„๋“œ์˜ ๊ฐ’์ด ํŠน์ • ๊ฐ’๋“ค ์ค‘ ํ•˜๋‚˜๋กœ ์ œํ•œ๋  ๊ฒฝ์šฐ ์‚ฌ์šฉ๋œ๋‹ค.
    enum ํƒ€์ž… ํ•„๋“œ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์™€ ๊ฐ™์ด @Schema annotation์„ ๋ถ™์—ฌ์ฃผ๋ฉด swagger์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ‘‰ @ApiResponse

API ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜ ์‹คํŒจํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์‘๋‹ต ์ฝ”๋“œ์™€ ์„ค๋ช…์„ ์ œ๊ณตํ•˜์—ฌ
API ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋–ค ์ข…๋ฅ˜์˜ ์‘๋‹ต์„ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

package withbeetravel.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import withbeetravel.dto.ChooseParticipantsRequestDto;
import withbeetravel.exception.dto.ErrorResponseDto;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/travels/{travelId}/payments")
@Tag(name = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ API", description = "์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.")
public class PaymentController {

    @Operation(
            summary = "์ •์‚ฐ ์ธ์› ์„ ํƒ",
            description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ์— ๋Œ€ํ•œ ์ •์‚ฐ ์ธ์›์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
            tags = {"User Management"},
            parameters = {
                    @Parameter(
                            name = "travelId",
                            description = "์—ฌํ–‰ ID",
                            required = true,
                            in = ParameterIn.PATH,
                            example = "1234"
                    ),
                    @Parameter(
                            name = "sharedPaymentId",
                            description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ ID",
                            required = true,
                            in = ParameterIn.PATH,
                            example = "1234"
                    )
            }
    )
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "์ •์‚ฐ์ธ์› ๋ณ€๊ฒฝ ์„ฑ๊ณต"),
            @ApiResponse(responseCode = "401", description = "AUTH-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
            @ApiResponse(responseCode = "404", description = "TRAVEL-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
            @ApiResponse(responseCode = "403", description = "TRAVEL-002", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
            @ApiResponse(responseCode = "404", description = "PAYMENT-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
    })
    @PatchMapping("/{sharedPaymentId}/participants")
    public ResponseEntity<String> chooseParticipant(@PathVariable Long travelId,
                                                                @PathVariable Long sharedPaymentId,
                                                                @RequestBody ChooseParticipantsRequestDto requestDto) {


        return ResponseEntity.ok("์ •์‚ฐ์ธ์› ๋ณ€๊ฒฝ ์„ฑ๊ณต");
    }
}
  • responseCode:
    HTTP ์‘๋‹ต ์ฝ”๋“œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ "200", "400", "404", "500" ๋“ฑ๊ณผ ๊ฐ™์€ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•œ๋‹ค.
  • description:
    ํ•ด๋‹น ์‘๋‹ต ์ฝ”๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ž…๋ ฅํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•ด๋‹น ์ƒํƒœ ์ฝ”๋“œ์˜ ์˜๋ฏธ๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
  • content:
    @Content์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์–ด ์‘๋‹ต์˜ ๋‚ด์šฉ ์œ ํ˜•๊ณผ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•œ๋‹ค.
    @Schema๋ฅผ ํ†ตํ•ด ๋ฐ˜ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด์™€ ๊ฐ™์ด @ApiResponse annotation์„ ๋ถ™์—ฌ์ฃผ๋ฉด swagger์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ“‘ Swagger ์ž‘์„ฑ ์ตœ์ ํ™”

ํ˜„์žฌ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, chooseParticipant ๋ฉ”์„œ๋“œ ์•ˆ์— ๊ตฌํ˜„๋ถ€๋Š” ํ•˜๋‚˜๋„ ์—†์Œ์—๋„ swagger ์„ค์ •๋งŒ์œผ๋กœ๋„ ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ๋‹ค.

@Operation(
        summary = "์ •์‚ฐ ์ธ์› ์„ ํƒ",
        description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ์— ๋Œ€ํ•œ ์ •์‚ฐ ์ธ์›์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
        tags = {"User Management"},
        parameters = {
                @Parameter(
                        name = "travelId",
                        description = "์—ฌํ–‰ ID",
                        required = true,
                        in = ParameterIn.PATH,
                        example = "1234"
                ),
                @Parameter(
                        name = "sharedPaymentId",
                        description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ ID",
                        required = true,
                        in = ParameterIn.PATH,
                        example = "1234"
                )
        }
)
@ApiResponses({
        @ApiResponse(responseCode = "200", description = "์ •์‚ฐ์ธ์› ๋ณ€๊ฒฝ ์„ฑ๊ณต"),
        @ApiResponse(responseCode = "401", description = "AUTH-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
        @ApiResponse(responseCode = "404", description = "TRAVEL-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
        @ApiResponse(responseCode = "403", description = "TRAVEL-002", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
        @ApiResponse(responseCode = "404", description = "PAYMENT-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
})
@PatchMapping("/{sharedPaymentId}/participants")
public ResponseEntity<String> chooseParticipant(@PathVariable Long travelId,
                                                            @PathVariable Long sharedPaymentId,
                                                            @RequestBody ChooseParticipantsRequestDto requestDto) {


    return ResponseEntity.ok("์ •์‚ฐ์ธ์› ๋ณ€๊ฒฝ ์„ฑ๊ณต");
}

์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™œ์šฉํ•˜์—ฌ swagger ์„ค์ •์„ ํ•˜๊ณ  ๊ฐ€๋…์„ฑ์„ ๋†’์—ฌ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

swagger docs์šฉ interface๋ฅผ ์ƒ์„ฑํ•˜์—ฌ swagger ๊ด€๋ จ ์„ค์ •๋“ค์€ ๋ชจ๋‘ ์ด ๊ณณ์— ์ ์–ด์ค€๋‹ค.

package withbeetravel.controller.docs;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import withbeetravel.dto.ChooseParticipantsRequestDto;
import withbeetravel.exception.dto.ErrorResponseDto;

@Tag(name = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ API", description = "์— ๋Œ€ํ•œ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.")
public interface PaymentControllerDocs {

    @Operation(
            summary = "์ •์‚ฐ ์ธ์› ์„ ํƒ",
            description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ์— ๋Œ€ํ•œ ์ •์‚ฐ ์ธ์›์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
            tags = {"User Management"},
            parameters = {
                    @Parameter(
                            name = "travelId",
                            description = "์—ฌํ–‰ ID",
                            required = true,
                            in = ParameterIn.PATH,
                            example = "1234"
                    ),
                    @Parameter(
                            name = "sharedPaymentId",
                            description = "๊ณต๋™ ๊ฒฐ์ œ ๋‚ด์—ญ ID",
                            required = true,
                            in = ParameterIn.PATH,
                            example = "1234"
                    )
            }
    )
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "์ •์‚ฐ์ธ์› ๋ณ€๊ฒฝ ์„ฑ๊ณต"),
            @ApiResponse(responseCode = "401", description = "AUTH-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
            @ApiResponse(responseCode = "404", description = "TRAVEL-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
            @ApiResponse(responseCode = "403", description = "TRAVEL-002", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
            @ApiResponse(responseCode = "404", description = "PAYMENT-001", content = @Content(schema = @Schema(implementation = ErrorResponseDto.class))),
    })
    public ResponseEntity<String> chooseParticipant(@PathVariable Long travelId,
                                                    @PathVariable Long sharedPaymentId,
                                                    @RequestBody ChooseParticipantsRequestDto requestDto);
}

๊ทธ๋ฆฌ๊ณ  ์ด interface๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ implements ํ•ด์ฃผ๋ฉด

package withbeetravel.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import withbeetravel.controller.docs.PaymentControllerDocs;
import withbeetravel.dto.ChooseParticipantsRequestDto;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/travels/{travelId}/payments")
public class PaymentController implements PaymentControllerDocs {


    @PatchMapping("/{sharedPaymentId}/participants")
    public ResponseEntity<String> chooseParticipant(@PathVariable Long travelId,
                                                                @PathVariable Long sharedPaymentId,
                                                                @RequestBody ChooseParticipantsRequestDto requestDto) {

        return ResponseEntity.ok("์ •์‚ฐ์ธ์› ๋ณ€๊ฒฝ ์„ฑ๊ณต");
    }
}

์ด์™€ ๊ฐ™์ด ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋ฉฐ swagger ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ“‘ ์ฐธ๊ณ 

https://velog.io/@fever-max/Spring-Boot3-%EC%8A%A4%EC%9B%A8%EA%B1%B0Swagger-%EC%84%A4%EC%A0%95-%EB%B0%8F-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95

https://infinitecode.tistory.com/65

728x90

'Back-end > Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring] AOP๋กœ ๊ถŒํ•œ ์ฒ˜๋ฆฌํ•˜๊ธฐ  (0) 2024.11.14
[Spring] Custome Exception ๋งŒ๋“ค๊ธฐ  (0) 2024.11.07