API 명세 및 구현

SummerToday·2025년 1월 26일
0
post-thumbnail

OAS로 API 설계

OAS란?

  • OAS(Open API Specification)는 REST API 명세 및 설명을 위한 표준으로, API 설계의 설계 우선 접근 방식(design-first)을 지원한다.

  • YAML(YAML Ain't Markuup Language) 또는 JSON(JavaScript Object Notation ) 형식으로 REST API를 정의하며, OAS 3.0이 많이 사용된다.


OAS의 필요성

  • API 설계를 코드 작성 전에 명확히 정의하여 수정, 관리, 확장성 문제를 줄인다.

  • 설계가 명확하면 개발 중 혼란을 줄이고 협업과 문서화를 쉽게 만든다.


OAS 설계 도구

OAS 기반 API 설계를 위한 대표적인 도구들은 다음과 같다.

  • Swagger Editor
    웹 기반 도구로, YAML 형식으로 API 설계를 작성하고 즉시 미리보기가 가능하다.
    OAS 3.0 설정을 지원하며, 초보자에게 적합하다.

  • Swagger Codegen
    설계한 API 명세를 기반으로 코드(JAVA, Python 등)를 자동 생성한다.
    개발 시간을 줄이고, 표준화된 API 구현을 돕는다.

  • Swagger UI
    작성한 OAS 문서를 시각화하여 API 인터페이스를 확인하거나 테스트 가능.
    클라이언트와 협업 시 API 이해를 돕는 데 유용하다.


OAS의 기본 구조 이해

OpenAPI 명세는 다음과 같은 주요 키워드와 계층 구조로 구성된다.

  • openapi
    OpenAPI 명세의 버전을 정의 . (예: 3.0.0)

  • info
    API의 메타데이터를 포함하며, API의 제목, 설명, 버전 등의 정보를 정의한다.

  • externalDocs
    API에 대한 추가 문서 링크를 제공한다.

    위 세가지 항목은 API의 메타데이터를 정의하는 데 사용된다.

  • servers
    API가 배포된 서버의 정보를 정의한다. (예: https://api.example.com)

  • tags
    API를 주제별로 구분하기 위해 사용. 태그를 통해 관련된 API를 그룹화한다.

  • paths:
    API의 경로(URI)와 각 경로에서 지원하는 메서드(예: GET, POST)를 정의한다.

  • components
    공통적으로 사용되는 스키마, 응답, 매개변수, 보안 정의 등을 포함한다.


OAS의 메타데이터 절

openapi: 3.0.3
info:
  title: Sample Ecommerce App
  description: >
    'This is a ***sample ecommerce app API***.  You can find out more about Swagger at [swagger.io](http://swagger.io).
    Description supports markdown markup. For example, you can use the `inline code` using back ticks.'
  termsOfService: https://github.com/PacktPublishing/Modern-API-Development-with-Spring-6-and-Spring-Boot-3/blob/main/LICENSE
  contact:
    name: Packt Support
    url: https://www.packt.com
    email: support@packtpub.com
  license:
    name: MIT
    url: https://github.com/PacktPublishing/Modern-API-Development-with-Spring-6-and-Spring-Boot-3/blob/main/LICENSE
  version: 1.0.0
externalDocs:
  description: Any document link you want to generate along with API.
  url: http://swagger.io
  • openapi
    OpenAPI 명세의 버전을 정의한다.

  • info
    API에 대한 메타데이터를 제공하는 섹션으로, 주로 제목, 설명, 서비스 약관, 연락처, 라이선스 정보를 포함한다. info에는 다음 필드가 포함되며, title과 version만 필수 필드이고 나머지는 선택 필드이다.

    • title
      API의 이름 또는 제목이다.

    • description
      API의 세부 설명이다. Markdown 문법을 지원하므로, 텍스트 서식, 링크, 코드 블록 등을 추가할 수 있다.

    • termsOfService
      API 사용 시 적용되는 서비스 약관의 링크이다.

    • contact
      API와 관련된 지원 팀 또는 담당자의 연락처 정보를 의미한다.

      • name
        담당 팀 또는 사람의 이름.

      • url
        연락처 페이지 URL.

      • email
        이메일 주소.

    • license
      API에 적용된 라이선스 정보이다.

      • name
        라이선스 이름(예: MIT).

      • url
        라이선스의 자세한 내용을 확인할 수 있는 링크이다.

    • version
      API의 현재 버전을 나타낸다.

  • externalDocs
    API와 관련된 추가 외부 문서 링크를 제공.

    • description
      문서의 간단한 설명을 의미한다.

    • url
      문서의 링크이다.


OAS의 servers와 tags 절

servers:
  - url: https://ecommerce.swagger.io/v2
tags:
  - name: cart
    description: Everything about cart
    externalDocs:
      description: Find out more (extra document link)
      url: http://swagger.io
  - name: order
    description: Operation about orders
  - name: user
    description: Operations about users
  • servers
    API거 배포된 서버 정보를 정의한다.

    • url
      API 요청을 보낼 기본 서버의 URL이다.
  • tags
    API의 엔드포인트를 주제별로 그룹화하는 데 사용한다. 각 태그는 특정 API 관련 작업을 설명하며, Swagger UI에서 카테고리로 나타낸다.

    • name
      태그의 이름을 의미한다.

    • description
      태그의 설명을 나타낸다.

    • externalDocs
      태그와 관련된 추가 문서 링크를 제공한다.

      • description
        문서의 설명이다.

      • url
        추가 문서의 URL이다.


OAS의 컴포넌트(components) 절

components:
  schemas:
    Cart:
      description: Shopping Cart of the user
      type: object
      properties:
        customerId:
          description: Id of the customer who possesses the cart
          type: string
        items:
          description: Collection of items in cart.
          type: array
          items:
            $ref: '#/components/schemas/Item'
  • components
    OpenAPI의 공통 재사용 요소를 정의하는 섹션이다. 스키마, 보안 설정, 응답, 매개변수 등을 포함하며, 여러 경로에서 참조($ref) 가능하다.

  • schemas
    데이터 모델을 정의하는 부분으로, API에서 사용되는 객체와 속성의 구조를 정의한다.

  • $ref는 재사용 가능한 스키마를 참조하기 위한 경로를 나타낸다.

    • #/components/schemas/Item은 OpenAPI 명세의 components.schemas 섹션에서 정의된 Item 스키마를 참조한다.

    • Cart 모델의 items 필드는 배열로, 배열의 각 요소는 Item 스키마를 참조한다.

    $ref는 쉽게 말해, "Item은 여기저기서 쓰이니, 정의를 재사용하자"는 뜻으로, 반복 없이 깔끔하게 작성하도록 도와주는 참조 기능이다.


OAS는 다음 6가지 기본 데이터 타입을 지원한다.

  • type: string

    • format: date
      부동 소수점 숫자를 포함한다.

    • format: byte
      Base64로 인코딩한 값을 포함한다.

    • format: binary
      이진 데이터를 포함한다.(파일에 사용 가능)

  • type: number

    • format: float
      부동 소수점 숫자를 포함한다.

    • format: double
      배정밀도(double precision)의 부동 소수점 숫자를 포함한다.

  • type: integer

    • format: int32
      int 타입(부호 있는 32비트 정수)을 포함한다.

    • format: int64
      long 타입(부호 있는 64비트 정수)을 포함한다.

  • type: boolean

  • type: object

  • type: array

cf. components 절에서 requestBodies(요청 페이로드)와 responses을 정의할 수도 있다. 이는 공통 요청 본문과 응답이 있는 경우에 유용하다.


OAS의 경로(path) 절

path 절에서는 URI를 구성하고 HTTP 메서드를 작성한다.

paths:
  /api/v1/carts/{customerId}:
    get:
      tags:
        - cart
      summary: Returns the shopping cart
      description: Returns the shopping cart of given customer
      operationId: getCartByCustomerId
      parameters:
        - name: customerId
          in: path
          description: Customer Identifier
          required: true
          schema:
            type: string
      responses:
        200:
          description: successful operation
          content:
            application/xml:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Cart'
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Cart'
        404:
          description: Given customer ID doesn't exist
          content: {}
  • paths
    API 경로(/api/v1/carts/{customerId})와 관련된 엔드포인트 정의를 나타낸다.

    • tags
      해당 엔드포인트의 카테고리를 나타낸다.

    • summary
      API의 간단한 설명을 나타낸다.

    • description
      API의 상세 설명을 나타낸다.

    • operationId
      API 메서드의 고유 식별자를 나타낸다. 쉽게 말해, 해당 API가 호출하는 메서드를 의미한다.

    • parameters
      경로 매개변수를 의미한다.

      • in: path
        해당 매개변수가 경로에 포함된다는 뜻이다. ex. /api/v1/carts/12345

      • required: true
        반드시 제공되어야 한다.

      • schema
        데이터타입은 string이다.


  • 응답 정의(responses)
    모든 API 오퍼레이션의 필수 필드이다. 기본 필드로 HTTP 상태코드가 포함된다.

    • 200

      • description (응답 설명)
        성공적으로 쇼핑 카트를 반환했음을 나타낸다.

      • content
        반환 데이터 형식은 XML과 JSON 모두 지원한다.
        두 형식의 데이터는 배열(type: array)이며, 각 배열 항목은 Cart 스키마($ref: '#/components/schemas/Cart')를 따른다.

    • 404

      • description
        해당 고객 ID가 존재하지 않을 경우를 설명한다.
      • content
        데이터가 반환되지 않음을 나타내는 빈 객체({})로 정의한다.

OAS를 스프링 코드로 전환

  • OpenAPI 명세 파일(OAS, openapi.yaml)을 기반으로 API 모델 클래스 및 인터페이스 코드를 자동 생성한다.

  • 수동으로 API 코드를 작성하는 대신, Swagger Codegen과 같은 도구를 사용하여 빠르고 일관성 있게 코드 생성할 수 있다.


0. openAPI 지원에 필요한 의존성을 build.gradle에 추가한다.

dependencies {
    // OpenAPI Starts
    swaggerCodegen 'org.openapitools:openapi-generator-cli:6.2.1'
    compileOnly 'io.swagger:swagger-annotations:1.6.4'
    compileOnly 'org.springframework.boot:spring-boot-starter-validation'
    compileOnly 'org.openapitools:jackson-databind-nullable:0.2.3'
    implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    // required for schema in swagger generated code
    implementation 'io.springfox:springfox-oas:3.0.0'
    // OpenAPI Ends
}
  • swaggerCodegen 'org.openapitools:openapi-generator-cli:6.2.1'

    • OpenAPI Generator CLI를 사용하기 위한 의존성이다.

    • OpenAPI 명세(openapi.yaml)를 기반으로 코드 생성 도구를 제공한다.

    • 이 라이브러리를 통해 모델 클래스, 컨트롤러 인터페이스 등을 자동으로 생성한다.

    • 사용 버전: 6.2.1.


  • compileOnly 'io.swagger:swagger-annotations:1.6.4'

    • Swagger를 위한 어노테이션 지원 라이브러리이다.

    • 코드에 Swagger 어노테이션을 추가해 API 문서를 명확하게 정의할 수 있다.

    • 주로 사용하는 어노테이션

      • @Api
        클래스에 대한 설명을 추가한다.

      • @ApiOperation
        메서드 설명을 추가한다.

      • @ApiParam
        요청 매개변수를 설명한다.

      • @ApiResponse
        응답에 대한 설명이다.


  • compileOnly 'org.springframework.boot:spring-boot-starter-validation'

    • Spring Boot Validation 라이브러리이다.

    • 요청 데이터의 유효성 검사를 지원한다.

    • Javax Bean Validation API(@Valid, @NotNull, @Size 등)와 연동되어 데이터 검증을 수행한다.


  • compileOnly 'org.openapitools:jackson-databind-nullable:0.2.3'

    • OpenAPI에서 nullable 속성을 처리하기 위한 Jackson 모듈이다.

    • OpenAPI 명세에서 nullable: true가 설정된 필드를 처리할 때 사용한다.


  • implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'

    • Jackson에서 XML, JSON 데이터를 읽고 쓰기 위한 데이터 포맷 라이브러리이다.

    • JSON뿐 아니라 XML 포맷을 직렬화/역직렬화할 때 사용한다.

    • 주로 XML 요청/응답이 필요한 API에서 활용한다.


  • implementation 'org.springframework.boot:spring-boot-starter-hateoas'

    • HATEOAS(Hypermedia As The Engine Of Application State) 지원 라이브러리이다.

    • REST API에 링크 기반 내비게이션을 추가할 수 있다.

    • OpenAPI에서 HATEOAS를 활용하는 경우 필수이다.


  • implementation 'io.springfox:springfox-oas:3.0.0'

    • Springfox OAS는 Spring 프로젝트에서 OpenAPI 명세를 자동으로 생성해주는 도구이다.

    • 컨트롤러 메서드와 엔드포인트를 기반으로 OpenAPI 3.0 문서를 생성한다.

    • Swagger UI와 연동되어 시각화된 API 문서를 제공한다.

    • ex.

      • @EnableOpenApi 어노테이션을 통해 OpenAPI를 활성화한다.

      • 컨트롤러 메서드와 엔드포인트가 OpenAPI 명세로 자동 변환된다.

의존성기능
swaggerCodegenOpenAPI 명세를 기반으로 코드 생성
swagger-annotationsSwagger 문서화를 위한 어노테이션 지원
spring-boot-starter-validation요청 데이터 유효성 검사 지원
jackson-databind-nullableOpenAPI nullable 필드 처리
jackson-dataformat-xmlJSON 외에 XML 데이터를 처리
spring-boot-starter-hateoasHATEOAS를 통해 API 링크 지원
springfox-oasSpring 프로젝트에서 OpenAPI 3.0 명세 자동 생성 및 Swagger UI 연동

1. gradle 플러그인 추가

Swagger Codegen을 사용하려면 Gradle에 플러그인을 추가한다.

plugins {
    id 'org.hidetake.swagger.generator' version '2.19.2'
}

2. OpenAPI 설정 파일 정의

코드 생성을 위한 설정 파일(config.json)을 정의한다. 이 파일은 생성될 코드의 패키지 구조, 사용할 라이브러리 등을 지정한다.

{
    "library": "spring-boot",
    "dateLibrary": "java8",
    "hideGenerationTimestamp": true,
    "modelPackage": "com.packt.modern.api.model",
    "apiPackage": "com.packt.modern.api",
    "invokerPackage": "com.packt.modern.api",
    "serializableModel": true,
    "useTags": true,
    "useBeanValidation": true,
    "hateoas": true,
    "unhandledException": true,
    "useSpringBoot3": true,
    "useSwaggerUI": true,
    "importMapping": {
        "ResourceSupport": "org.springframework.hateoas.RepresentationModel",
        "Link": "org.springframework.hateoas.Link"
    }
}
설명값의 의미
library생성할 코드의 기본 라이브러리를 설정한다."spring-boot": Spring Boot와 호환되는 코드가 생성됨.
dateLibrary날짜/시간 처리를 위한 라이브러리 설정."java8": Java 8의 LocalDate, LocalDateTime 등을 사용.
hideGenerationTimestamp코드 생성 시 타임스탬프 포함 여부.true: 생성된 코드 주석에 타임스탬프를 포함하지 않음.
modelPackage생성된 모델 클래스가 포함될 패키지 경로."com.packt.modern.api.model": 모델 클래스(User, Order 등)가 이 경로에 생성됨.
apiPackage생성된 API 인터페이스가 포함될 패키지 경로."com.packt.modern.api": API 인터페이스(UserApi, OrderApi)가 이 경로에 생성됨.
invokerPackageInvoker 클래스가 포함될 패키지 경로."com.packt.modern.api": Invoker 클래스(필요 시)가 이 경로에 생성됨.
serializableModel모델 클래스의 직렬화 가능 여부 설정.true: 모델 클래스가 Serializable을 구현해 파일 저장이나 전송 가능.
useTagsOpenAPI 명세의 태그를 기반으로 코드 그룹화.true: 태그별로 관련 API 메서드가 그룹화됨.
useBeanValidation유효성 검사를 위한 Java Bean Validation 어노테이션 추가.true: @NotNull, @Size 등의 유효성 검증 어노테이션이 추가됨.
hateoas하이퍼미디어 링크(HATEOAS) 지원 여부 설정.true: HATEOAS 관련 클래스(RepresentationModel, Link)를 활용한 링크 생성 가능.
unhandledException처리되지 않은 예외를 위한 기본 예외 처리 코드 생성 여부.true: 기본 예외 응답 로직을 생성.
useSpringBoot3Spring Boot 3.x 호환 코드 생성 여부.true: Spring Boot 3.x와 호환되는 코드가 생성됨.
useSwaggerUISwagger UI 활성화 여부.true: Swagger UI를 통해 API 테스트 및 문서 시각화 가능.
importMapping특정 클래스나 타입을 Spring HATEOAS 클래스로 매핑."ResourceSupport": "RepresentationModel", "Link": "org.springframework.hateoas.Link": 매핑.

cf. importMapping 내부 설정 값은 openAPI Generator CLI가 Spring HATEOAS의 최신 표준을 따르기 위해 설정한 것이다.


3. 생성에서 제외할 파일 정의

.openapi-generator-ignore 파일을 사용하여 생성에서 제외할 파일을 지정할 수 있다.

**/Controller.java

cf. OpenAPI Generator는 표준화된 스펙을 기반으로 모델, 인터페이스, DTO 등을 생성하는데 주로 사용하고, 컨트롤러처럼 프로젝트 고유의 비즈니스 로직이나 동작 방식을 포함해야 하는 코드는 자동 생성에서 제외되는 경우가 많다.


4. openapi.yaml 파일 생성

src/main/resources/api에 openapi.yaml 파일을 작성해준다.

https://github.com/SummerToday/modernAPI.git 참고


5. Gradle 빌드 파일 설정

build.gradle 파일에서 swaggerSources 태스크를 정의하여 코드 생성을 구성한다.

swaggerSources {
	def typeMappings = 'URI=URI'
	def importMappings = 'URI=java.net.URI'
	eStore {
		def apiYaml = "${rootDir}/src/main/resources/api/openapi.yaml"
		def configJson = "${rootDir}/src/main/resources/api/config.json"
		inputFile = file(apiYaml)
		def ignoreFile = file("${rootDir}/src/main/resources/api/.openapi-generator-ignore")
		code {
			language = 'spring'
			configFile = file(configJson)
			rawOptions = ['--ignore-file-override', ignoreFile, '--type-mappings',
						  typeMappings, '--import-mappings', importMappings] as List<String>
			components = [models: true, apis: true, supportingFiles: 'ApiUtil.java']
			dependsOn validation
		}
	}
}
  • typeMapping
    OpenAPI 명세의 타입(URI)을 Java의 특정 타입(URI)으로 매핑한다.

  • importMappings
    typeMappings에서 매핑된 Java 타입(URI)에 대해 정확한 import 경로(java.net.URI)를 명시한다.

    cf. 자주 사용되는 타입 매핑
    format: uri → java.net.URI
    format: date → java.time.LocalDate
    format: date-time → java.time.LocalDateTime
    type: number, format: double → java.math.BigDecimal

  • eStore
    OpenAPI 명세 파일, 설정 파일, 생성 코드의 구성 등을 정의하는 작업 블록이다.

    • inputFile
      OpenAPI 스펙 파일(openapi.yaml)을 지정합니다.

    • configFile
      설정 파일(config.json)을 지정합니다.

    • rawOptions
      OpenAPI Generator 실행 시 명령줄 옵션을 전달하는 역할을 한다.

      • --ignore-file-override
        OpenAPI Generator가 무시할 파일/디렉토리를 정의하는 .openapi-generator-ignore 파일을 사용하도록 설정한다.

      • --type-mappings
        OpenAPI 명세의 타입을 typeMappings 변수에 정의된 매핑 값으로 매핑한다.

      • --import-mappings
        OpenAPI 명세의 특정 타입에 대해 import 경로를 명시적으로 지정한다.

    • components
      생성할 코드의 종류(모델, API 인터페이스 등)를 지정합니다.

      cf. Open API 생성기를 통해서 클라이언트 또는 테스트 파일과 같은 다른 파일도 생성할 수 있다.

      • ApiUtil.java는 OpenAPI Generator에서 생성되는 코드의 일부로, API 호출이나 응답 처리를 도와주는 유틸리티 파일이다.

6. Gradle에서 swaggerSources와 compileJava 작업 연결

build.gradle에 다음 문장을 추가해준다.

compileJava.dependsOn swaggerSources.eStore.code
  • compileJava 작업이 실행되기 전에, OpenAPI Generator를 통해 코드 생성 작업(swaggerSources.eStore.code)이 먼저 실행되도록 설정한다.

    • OpenAPI Generator를 사용해 생성된 코드(모델, API 인터페이스 등)가 프로젝트의 빌드와 컴파일에 포함되기 위해, 해당 코드를 먼저 생성해야하기 때문이다.

cf. gradle build 과정
clean(기존 빌드 결과 삭제) -> compilejava(소스 코드를 컴파일하여 .class 파일 생성) -> processResource(리소스 파일을 복사하여 출력 디렉토리에 저장) -> classes(compileJava와 processResouces의 결과물을 결합하여 실행 가능한 클래스 생성) -> test(테스트 코드를 실행하고 결과를 출력) -> jar(컴파일된 클래스와 리소스 파일을 묶어 JAR 파일 생성) -> build(모든 작업을 마치고 최종 빌드 결과를 제공)

또한 processResources와 generateSwaggerCode를 연결해줘야 한다.

processResources {
	dependsOn(generateSwaggerCode)
}
  • processResources 작업이 실행되기 전에, Swagger Codegen을 통해 소스 코드 생성 작업(generateSwaggerCode)이 실행되도록 설정한다.

7. 생성된 소스 코드와 리소스를 Gradle sourceSets에 추가

sourceSets.main.java.srcDir "${swaggerSources.eStore.code.outputDir}/src/main/java"
sourceSets.main.resources.srcDir "${swaggerSources.eStore.code.outputDir}/src/main/resources"

sourceSets는 Gradle에서 소스 코드와 리소스 파일의 경로를 정의하는 설정이다.
기본적으로 Gradle은 프로젝트의 소스 코드와 리소스 파일을 다음 경로에서 찾는다.
Java 코드: src/main/java/
리소스 파일: src/main/resources/
해당 경로 외에 다른 위치에 있는 소스 코드나 리소스 파일은 기본적으로 Gradle 빌드에 포함되지 않는다.
OpenAPI Generator가 생성한 파일들은 Gradle의 기본 소스 디렉토리가 아닌 경로(예: /build/swagger-code-eStore/)에 저장된다.
해당 파일들을 Gradle 빌드에 포함하지 않으면, 빌드 도중 "파일을 찾을 수 없음" 같은 컴파일 에러가 발생하게 된다.

cf. swaggerSources.eStore.code.outputDir는 Swagger Codegen이 생성한 코드가 저장될 디렉토리 경로를 정의하는 설정이다.


8. Swagger 명세(OpenAPI 명세)를 기반으로 소스 파일을 생성하기 위한 빌드

여태 설정한 명세를 기반으로 소스 파일을 생성하기 위해 빌드를 해준다.

gradlew clean build

빌드 후 다음과 같이 소스 파일(API 인터페이스)들이 생성된 것을 확인할 수 있다.

다음은 해당 코드에 적힌 주요 어노테이션들이다.

@Tag

@Tag(name = "Cart", description = "Everything about cart")

API 그룹을 지정한다.

  • name
    API 그룹 이름이다.
  • description
    그룹에 대한 설명이다.

@Operation

@Operation(
    operationId = "addCartItemsByCustomerId",
    summary = "Adds an item in shopping cart",
    tags = { "cart" },
    responses = {
        @ApiResponse(responseCode = "201", description = "Item added successfully"),
        @ApiResponse(responseCode = "404", description = "Given customer ID doesn't exist")
    }
)

각각의 API 메서드를 설명하는 데 사용된다.

  • operationId
    API 메서드의 고유 식별자이다.

  • summary
    API 메서드의 간단한 설명이다.

  • tags
    API 메서드가 속하는 태그 그룹(위의 @Tag와 연결됨)이다.

  • responses
    응답 상태 코드와 설명을 정의한다.


@ApiResponse

@ApiResponse(responseCode = "201", description = "Item added successfully", content = {
    @Content(mediaType = "application/xml", schema = @Schema(implementation = Item.class)),
    @Content(mediaType = "application/json", schema = @Schema(implementation = Item.class))
}),
@ApiResponse(responseCode = "404", description = "Given customer ID doesn't exist")

API 호출에 대한 응답 코드와 설명을 정의한다.

  • responseCode
    HTTP 상태 코드를 정의한다(예: 201, 404).

  • description
    해당 응답 코드의 의미를 설명한다.

  • content
    응답 데이터의 형식(JSON, XML 등)과 스키마를 지정한다.


@RequestMapping

@RequestMapping(
    method = RequestMethod.POST,
    value = "/api/v1/carts/{customerId}/items",
    produces = { "application/xml", "application/json" },
    consumes = { "application/xml", "application/json" }
)

API의 HTTP 메서드, 경로, 요청/응답 데이터 형식을 정의한다.

  • method
    HTTP 메서드(예: POST, GET, PUT, DELETE)이다.

  • value
    API 경로(예: /api/v1/carts/{customerId}/items)이다.

  • produces
    API가 반환하는 데이터 형식이다(예: application/json, application/xml).

  • consumes
    API가 수락하는 데이터 형식(예: application/json, application/xml)이다.


@Parameter

@Parameter(name = "customerId", description = "Customer Identifier", required = true)
@PathVariable("customerId") String customerId

API의 입력값(파라미터)을 설명한다.

  • name
    파라미터 이름이다.

  • description
    파라미터 설명이다.

  • required
    필수 여부(true/false)이다.


@Schema

@Schema(implementation = Item.class)

API의 요청/응답 데이터 스키마를 정의한다.

  • implementation
    데이터 형식(클래스)을 지정한다.

OAS 코드 인터페이스 구현

개발자는 Swagger Codegen으로 스프링 코드를 작성해 API 인터페이스를 구현하고 그 안에 비즈니스 로직만 작성해주면 된다.

Swagger Codehen은 각 API 태그마다 인터페이스를 생성한다.

import com.packt.modern.api.CartApi;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;
import java.util.Collections;

@RestController
public class CartsController implements CartApi {

    private static final Logger log = LoggerFactory.getLogger(CartsController.class);

    @Override
    public ResponseEntity<List<Item>> addCartItemsByCustomerId(String customerId, @Valid Item item) {
        log.info("고객 ID 요청: {}\nItem: {}", customerId, item);
        return ok(Collections.EMPTY_LIST);
    }

    @Override
    public ResponseEntity<List<Item>> getCartByCustomerId(String customerId){
        throw new RuntimeException("수동 예외 발생 (Manual Exception thrown)");
    }

}
  • addCartItemsByCustomerId
    Item 요청(POST /api/v1/carts/{customerId}/items)을 추가하기 위해 addCartItemsByCustomerId 메서드 내부에서 들어오는 요청 페이로드와 고객 ID를 기록한다.

  • getCartByCustomerId
    단순한 예외를 발생시킨다. 이를 통해 전역 예외 처리기(Global Exception Handler)의 결과를 확인할 수 있다.

    cf. 전역 예외처리기
    Spring Framework에서 제공하는 기능으로, 애플리케이션 전역에서 발생하는 예외를 하나의 클래스에서 처리할 수 있도록 해주는 구성 요소이다.
    이를 통해 각 컨트롤러마다 따로 예외 처리를 작성하지 않고, 중앙에서 모든 예외를 관리할 수 있다.


전역 예외 처리기 추가

  • 컨트롤러에 작성된 각 메서들에서 발생하는 예외들을 처리할 중심 역할이 필요하고, 스프링은 이를 위해 AOP 기능을 제공한다.

    • @Controller가 달린 단일 클래스(Error 객체) 생성 후, @ExceptionHandler를 통해 예외 타입에 맞는 메서드를 정의하여 처리한다.

      • 해당 메서드에는 공통된 에러 메시지를 생성하고 관련된 정보들을 사용자에게 전달한다.
  • Lombok 라이브러리를 사용하면, getter, setter, constructor 등에 대한 코드의 복잡성을 제거할 수 있다.


@Setter
@Getter
public class Error {
    private static final serialVersionUID = 1L;
    private String ErrorCode;
    private String message;
    private Integer status;
    private String url = "Not available";
    private String reqMethod = "Not available";
}
  • serialVersionUID
    직렬화된 객체가 역직렬화될 때 클래스의 버전을 검증하는 데 사용된다.

    cf. 객체의 직렬화와 역직렬화 과정에서 버전 호환성을 보장한다. 클래스가 변경되었더라도 기존 데이터를 정상적으로 복원 가능하다.
    미지정 시 자동으로 값이 생성되는데, 클래스가 조금만 변경돼도 달라지므로, 명시적으로 지정하는 것이 안전하다.

  • errorCode
    HTTP 오류 코드와 다른 애플리케이션 오류 코드이다.

  • message
    문제에 대한 간단하고 사람이 읽을 수 있는 요약이다.

  • status
    문제 발생 시 서버에 의해 설정된 HTTP 상태 코드이다.

  • url
    오류를 발생시킨 요청의 URL이다.

  • reqMethod
    오류를 발생시킨 요청의 메서드이다.

cf. 필요 시 추가적인 필드도 설정 가능하다. Exceptions 패키지에는 사용자 정의 예외 및 전역 예외 처리를 위한 모든 코드가 포함된다.


ErrorCode Enum 정의

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public enum ErrorCode {
    // Internal Errors: 1 to 0999
    GENERIC_ERROR("PACKT-0001", "The system is unable to complete the request. Contact system support."),
    HTTP_MEDIATYPE_NOT_SUPPORTED("PACKT-0002", "Requested media type is not supported. Please use application/json or application/xml as 'Content-Type' header value"),
    HTTP_MESSAGE_NOT_WRITABLE("PACKT-0003", "Missing 'Accept' header. Please add 'Accept' header."),
    HTTP_MEDIA_TYPE_NOT_ACCEPTABLE("PACKT-0004", "Requested 'Accept' header value is not supported. Please use application/json or application/xml as 'Accept' value"),
    JSON_PARSE_ERROR("PACKT-0005", "Make sure request payload should be a valid JSON object."),
    HTTP_MESSAGE_NOT_READABLE("PACKT-0006", "Make sure request payload should be a valid JSON or XML object according to 'Content-Type'.");

    private String errCode;
    private String errMsgKey;

    ErrorCode(final String errCode, final String errMsgKey) {
        this.errCode = errCode;
        this.errMsgKey = errMsgKey;
    }

}

cf. enum 클래스에서는 생성자를 먼저 정의하면 오류가 발생한다.

  • 에러 코드와 에러 메시지를 관리하기 위한 열거형(Enum)을 작성한다.

  • 각 에러 상황에 대한 고유 코드와 메시지를 정의한다.


Error 객체를 생성하는 유틸리티 추가

REST API에서 발생하는 다양한 예외를 처리할 때, 각 예외마다 반환할 에러 객체(Error 클래스)를 생성해야 하는데, 이 작업을 유틸리티 클래스로 분리하면 코드 중복 제거, 일관성 있는 에러 응답 제공, 유지보수 용이, 확장성 같은 장점이 존재한다.

package com.packt.modern.api.exceptions;

public class ErrorUtils {

    private ErrorUtils() {
    } // 객체 생성 방지(유틸리티 클래스)

    public static Error createError(final String errMsgKey, final String errorCode,
                                    final Integer httpStatusCode) {
        Error error = new Error();
        error.setMessage(errMsgKey);
        error.setErrorCode(errorCode);
        error.setStatus(httpStatusCode);
        return error;
    }
}

전역 예외 처리기 구현

@ControllerAdvice
public class RestApiErrorHandler {

    private static final Logger log = LoggerFactory.getLogger(RestApiErrorHandler.class);
    private final MessageSource messageSource;

    @Autowired
    public RestApiErrorHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Error> handleException(HttpServletRequest request, Exception ex,
                                                 Locale locale) {
        ex.printStackTrace(); // TODO: Should be kept only for development
        Error error = ErrorUtils
                .createError(ErrorCode.GENERIC_ERROR.getErrMsgKey(), ErrorCode.GENERIC_ERROR.getErrCode(),
                        HttpStatus.INTERNAL_SERVER_ERROR.value()).setUrl(request.getRequestURL().toString())
                .setReqMethod(request.getMethod());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ResponseEntity<Error> handleHttpMediaTypeNotSupportedException(HttpServletRequest request,
                                                                          HttpMediaTypeNotSupportedException ex,
                                                                          Locale locale) {
        ex.printStackTrace(); // TODO: Should be kept only for development
        Error error = ErrorUtils
                .createError(ErrorCode.HTTP_MEDIATYPE_NOT_SUPPORTED.getErrMsgKey(),
                        ErrorCode.HTTP_MEDIATYPE_NOT_SUPPORTED.getErrCode(),
                        HttpStatus.UNSUPPORTED_MEDIA_TYPE.value()).setUrl(request.getRequestURL().toString())
                .setReqMethod(request.getMethod());
        log.info("HttpMediaTypeNotSupportedException :: request.getMethod(): " + request.getMethod());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

 ...생략...

}
  • log
    log 객체를 사용하면 애플리케이션 실행 중 발생하는 정보, 경고, 오류 등을 로그로 남길 수 있다.

  • @ControllerAdvice
    애플리케이션의 모든 컨트롤러에서 발생하는 예외를 추적하고, @ExceptionHandler를 사용해 예외를 처리할 수 있다.

  • @ExceptionHandler()
    특정 예외를 처리할 메서드에 붙이는 어노테이션이다. 괄호 안에 지정한 예외 클래스와 발생한 예외가 일치하거나, 하위 클래스인 경우 해당 메서드가 호출된다.

    • Exception.class를 사용하면 모든 예외를 처리할 수 있다.
  • ex.printStackTrace();
    예외 발생 시 스택 트레이스를 콘솔에 출력

    cf. 스택 트레이스
    프로그램 실행 중 발생한 오류나 예외 상황에서, 해당 오류가 발생하기까지의 메서드 호출 순서를 기록한 정보.

  • ResponseEntity<응답 본문 타입>(응답 본문, 헤더, 상태코드)
    Spring Framework에서 HTTP 응답을 나타내는 클래스이다. 이를 사용하면 HTTP 상태 코드, 헤더, 본문(body) 등을 포함한 완전한 HTTP 응답을 구성하고 반환할 수 있다,

    • ResponseEntity로 객체를 반환하면, Spring은 해당 객체를 JSON으로 직렬화한다.

      cf. 응답 본문의 객체가 public이 아니거나, 필드에 getter 메서드가 없는 경우 직렬화를 실패한다.

      cf. pom.xml 또는 build.gradle에 Jackson 의존성이 추가되어 있어야 직렬화/역직렬화가 동작한다.

  • log.info()
    Spring Boot에서 로그를 기록하는 Logger 객체의 info 레벨 메서드이다.

    기본적으로 로그는 터미널(콘솔)에만 출력되며, 파일에는 저장되지 않기 때문에 로그 파일을 application.properties에서 따로 설정해줘야한다.
    ex. logging.file.name=logs/application.log


API 구현 테스트

.gradlew clean build

java -jar .\build\libs\modernAPI-0.0.1-SNAPSHOT.jar

위 명령어를 통해 애플리케이션을 빌드하고, 실행시켜준다.

PostMan을 통해 API를 테스트 해준다.

GET http://localhost:8080/api/v1/carts/1 header: Accept: application/xml


GET http://localhost:8080/api/v1/carts/1 header: Accept: application/json


POST http://localhost:8080/api/v1/carts/1/items Accept: application/json, ContentType: application/json

위 내용들과 같이 api들이 정상적으로 동작하는 것을 확인할 수 있다.




해당 글은 다음 도서의 내용을 정리하고 참고한 글임을 밝힙니다. 보다 자세한 내용은 아래 책에서 확인할 수 있습니다. 소라브 샤르마, ⌜스프링 6와 스프링 부트 3로 배우는 모던 API 개발⌟, 위키북스, 2024, 512쪽
profile
IT, 개발 관련 정보들을 기록하는 장소입니다.

0개의 댓글

관련 채용 정보