티스토리 뷰
이번 사이드 프로젝트에서 서버 개발자분들이 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일에 릴리즈된 따끈따끈한 녀석이다.
그래서인지 자료가 많이 없었다.
![](https://blog.kakaocdn.net/dn/Epblv/btsJpez1wUA/CBGW6mpQcY1Es26lprO2Gk/img.png)
현재는 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"),
]
)
]
)
패키지 파일을 위 처럼 수정해줬다.
![](https://blog.kakaocdn.net/dn/66Ket/btsJoNv6QKP/ov7AMY5cEdKgKXRu3W7qo1/img.png)
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 으로 두었다.
이렇게 준비한 파일을 다음과 같은 파일 구조로 만든다.
![](https://blog.kakaocdn.net/dn/bFIiim/btsJqIU7Tq4/NWpN2C8IHZKGUMLqTO11wk/img.png)
4. 코드 생성하기
이제 준비는 되었고, 해당 패키지에 접근한 뒤 다음 커맨드를 실행한다.
$ swift run
![](https://blog.kakaocdn.net/dn/uaOip/btsJpdWBcgv/qJkn2FiCfMyEyxXNcwQs81/img.png)
코드를 실행하면
OpenAPI document path 도 잘 찾았고, config path 도 찾았고,
output directory 에 잘 생성이 되었다는 결과를 볼 수 있다.
그리고 output path를 찾아가보면 코드가 만들어져 있음을 볼 수 있다.
![](https://blog.kakaocdn.net/dn/m3BFY/btsJptENW1X/gFVI6q4vOyfr6niB1eSRBK/img.png)
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 이 부분이 뭐지?! 싶었는데
들어가보니 이렇게도 쓸 수 있구나 싶었다. 오호!
![](https://blog.kakaocdn.net/dn/bX9ArK/btsJpeH1jiY/qBRJAoh4nSLPUTi3Q351PK/img.png)
(대충 연산프로퍼티로 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
[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
swift-openapi-generator Documentation – Swift Package Index
swiftpackageindex.com
'iOS' 카테고리의 다른 글
[iOS] Open API Generator 로 네트워크 코드 자동생성(2) - middleware로 토큰 관리, interceptor (0) | 2024.10.29 |
---|---|
[iOS] demical keyboard type 에서 소수점이 comma로 나온다 (소수점 표기법) (0) | 2024.10.03 |
[iOS] Github Action 과 Tuist test 로 테스트 자동화 구축 (0) | 2024.08.20 |
[iOS] Dateformatter 가 고장나서 이상했던 경험 (0) | 2024.08.07 |
[iOS] 단일타겟 프로젝트를 멀티모듈로 바꾸었다. (1) | 2024.05.16 |
- Total
- Today
- Yesterday
- 애플워치
- 소수점
- OAS
- KVO
- 회고
- auth
- swift날짜
- openapi-generator
- retry
- Xcode15
- SwiftUI
- 애플워치 데이터 전송
- easy cue
- flo
- Xcode
- DateFormatter
- AVFoundation
- musicplayer
- Swift
- TextField
- locale
- avplayer
- 토큰
- 2024년
- demical
- watchOS
- open-api-generator
- keyboardtype
- IOS
- watch connectivity
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |