티스토리 뷰

이번 사이드 프로젝트에서 서버 개발자분들이 OpenAPI Generator 를 사용해보자고 제안해주셨다.
 
..그게 뭔가요 ?!
 

OAS (Open API Specification)

Restful API 를 정의하기 위한 규격 이라고 생각하면 될 것 같다. 
각 요청의 엔드포인트, 인증 등등 API 명세 를 위한 규격 이다. 
우리가 서버 개발자에게 받는 스웨거 가 OAS 를 기반으로 만들어진다고 하더라.
 
OAS. 라는 규격이 있고, json 혹은 yaml 형식으로 문서가 만들어지고 스웨거가 만들어진다.
그렇다면 서버와 클라이언트에서 각각 통신하는 코드도 자동화 할 수 있겠다.
 
그래서 Open API Generator 가 있다.
 

Open API Generator

우리가 서버와 통신하는 코드를 만드는 것은 반복적인 작업이다.
클라이언트에서도 서버에서도 각 엔드포인트를 만들어주고, DTO를 만들고
사람이 직접 하다보니 실수도 생긴다.
json 파싱할 때 null값 들어와 decoding error 안겪어봤으면 거짓말.
 
그리고 수정이 되면 DTO를 한땀한땀 바꿔줘야 하기도 하고,
명세를 보며 에러를 정의하기도 한다.
 
하지만, Open API Generator 를 이용해 코드를 자동생성 하면
서버와 클라이언트 모두 일관된 형식으로 request / response 를 받을 수 있으며,
개발자도 반복적인 코드 작성을 줄일 수 있다.
 
이렇게 좋아보이는 것을 한번도 안써봤는데, typescript 로 꽤 사용되고 있어 보인다.
swift의 경우.. 1.0.0 버전이 23년 12월 13일에 릴리즈된 따끈따끈한 녀석이다.
그래서인지 자료가 많이 없었다.

 
현재는 1.3.0 버전까지 나왔다.
 

사용하기

기본적으로 별도의 패키지 런타임 플러그인을 통해 코드가 생성된다.
고민해보면 진행중인 프로젝트와 통합하는 방법도 있긴 할 것 같다.

1. swift 패키지 생성

Swift 패키지를 생성한다. 일반적으로 SPM package 를 만드는 것과 동일하게 만들면 된다.
Xcode - File - New - Package 로 빈 패키지를 생성한다.
혹은 커맨드라인으로도 가능하다.

$ swift package init --type executable

 

2. 패키지 수정

// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription


let package = Package(
    name: "api-generator",
    platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)],
    dependencies: [
        .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"),
    ],
    targets: [
        .executableTarget(
            name: "api-generator",
            dependencies: [
                .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
                .product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
            ],
            plugins: [
                .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator"),
            ]
        )
    ]
)

패키지 파일을 위 처럼 수정해줬다.

 

3. 파일 준비

코드 생성을 위해서는 두가지의 파일이 필요하다.
1. openapi.yaml
  - 생성할 네트워크 통신 코드의 명세가 담긴 문서
2. openapi-genererator-config.yaml
  - 코드 생성시 필요한 configuration
 
튜토리얼에 따라 다음 파일을 만들어 준비한다.

// openapi.yaml

openapi: '3.1.0'
info:
  title: GreetingService
  version: 1.0.0
servers:
  - url: https://example.com/api
    description: Example service deployment.
  - url: http://127.0.0.1:8080/api
    description: Localhost deployment.
paths:
  /greet:
    get:
      operationId: getGreeting
      parameters:
        - name: name
          required: false
          in: query
          description: The name used in the returned greeting.
          schema:
            type: string
      responses:
        '200':
          description: A success response with a greeting.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Greeting'
components:
  schemas:
    Greeting:
      type: object
      properties:
        message:
          type: string
      required:
        - message

api 문서의 형식은 스윽 보면 알 수 있을 것 같다.
 

// openapi-generator-config.yaml

generate:
  - types
  - client
accessModifier: public

config 파일에는 생성할 옵션들이 들어가는데, 공식문서에 옵션들이 잘 나와있다.
- types: 명세에 따른 DTO 를 생성
- client: URL Session을 이용한 통신 코드를 생성
특히, 타 모듈에서의 접근을 위해 접근제한자를 public 으로 두었다.
 
이렇게 준비한 파일을 다음과 같은 파일 구조로 만든다.

 

4. 코드 생성하기

이제 준비는 되었고, 해당 패키지에 접근한 뒤 다음 커맨드를 실행한다.

$ swift run

코드를 실행하면 
OpenAPI document path 도 잘 찾았고, config path 도 찾았고,
output directory 에 잘 생성이 되었다는 결과를 볼 수 있다.
그리고 output path를 찾아가보면 코드가 만들어져 있음을 볼 수 있다.

아름답게 생성된 코드들

 

5. 코드 사용하기

import OpenAPIURLSession

// A 방법
public struct GreetingClient {

    public init() {}

    public func getGreeting(name: String?) async throws -> String {
        let client = Client(
            serverURL: try Servers.server2(),
            transport: URLSessionTransport()
        )
        let response = try await client.getGreeting(query: .init(name: name))
        return try response.ok.body.json.message
    }
}

// B 방법
public struct GreetingClient {

    public init() {}

    public func getGreeting(name: String?) async throws -> String {
        let client = Client(
            serverURL: try Servers.server2(),
            transport: URLSessionTransport()
        )
        let response = try await client.getGreeting(query: .init(name: name))
        switch response {
        case .ok(let okResponse):
            switch okResponse.body {
            case .json(let greeting):
                return greeting.message
            }
        case .undocumented(statusCode: let statusCode, _):
            return "🙉 \(statusCode)"
        }
    }
}

// 실제 사용
let greeting = try await GreetingClient().getGreeting(name: "App")

(OpenAPIUrlSession 라이브러리 임포트 필요!)
튜토리얼에서는 두가지 방법을 제시하고 있다. 한번 감싸서 사용하라는 듯
return try response.ok.body.json.message 이 부분이 뭐지?! 싶었는데
들어가보니 이렇게도 쓸 수 있구나 싶었다. 오호!

(대충 연산프로퍼티로 200인 ok을 꺼낼껀데 ok가 아니면 에러를 던질거라는 코드)
 
일단, 리스폰스의 케이스들대로 enum을 만들어준다. 
문서에 정의된대로 ok 케이스들, error 케이스들을 모두 만들어주니 편하게 쓸 수 있을 것 같다.
 
나는 프로젝트에 client.swift, type.swift 를 포함해 별도의 모듈로 만들었고, Tuist가 해당 파일들을 가지고 모듈을 생성하도록 했다.
output path 등을 별도로 지정해 makefile 을 만들어 make api 명령어로 파일이 자동생성 되도록 했다.
 

6. 어떻게 적용할까?

코드적으로 편해지긴 했으나, 
프로젝트에 어떻게 적용하는게 좋을지 Process 에 대한 것은 솔직히 아직 막막하긴 하다. 자료도 많이 없다.
- 자동생성되는 client.swift, type.swift 파일들을 git에 올리는것이 맞을지? CI/CD 에서 생성되게 해야하나?
- 명세가 수정되어 DTO가 바뀐다면 컴파일이 깨질 수 있는데 그럼 CI/CD 에는 어떻게 matching 시킬지? (버전관리?)
 
일단, 우리팀은 openapi.swift 파일을 별도의 레포로 관리하여 iOS 프로젝트 레포에 서브모듈로 넣어두었다.
이제 어떻게 문서를 어떻게 관리해야 효율적이고 생산적일지는 고민을 좀 해봐야 할 것 같다.
 

트러블슈팅

서버, AOS 진영에서는 openapi.yaml 을 여러개의 파일로 나눠서 생성하는 것이 가능하다고 했는데, 
iOS 에서는 그런 기능이 없는 것 같았다. 파일명을 바꾸는 건 되지만 여러개의 파일을 가지고 생성하는 건 안되는 것 같던데, 
github 을 싹 뒤졌지만 자료가 없었다. 그래서 하나의 파일로 관리하기로 했다..
 
 

참고자료

https://github.com/apple/swift-openapi-generator?tab=readme-ov-file

GitHub - apple/swift-openapi-generator: Generate Swift client and server code from an OpenAPI document.

Generate Swift client and server code from an OpenAPI document. - apple/swift-openapi-generator

github.com

https://developer.apple.com/videos/play/wwdc2023/10171/

Meet Swift OpenAPI Generator - WWDC23 - Videos - Apple Developer

Discover how Swift OpenAPI Generator can help you work with HTTP server APIs whether you're extending an iOS app or writing a server in...

developer.apple.com

https://dokit.tistory.com/69

[iOS] Swift로 Open API Generator 사용하기

" data-og-host="openapi-generator.tech" data-og-source-url="https://openapi-generator.tech" data-og-url="https://openapi-generator.tech/" data-og-image="https://scrap.kakaocdn.net/dn/ScnJN/hyWKH4joGZ/clJnDygrjpaTSktbrTdbQK/img.png?width=256&height=256&face

dokit.tistory.com

https://swiftpackageindex.com/apple/swift-openapi-generator/1.3.0/documentation/swift-openapi-generator

swift-openapi-generator Documentation – Swift Package Index

swiftpackageindex.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함