TIL

사내 코드에 openapi generator를 적용해보자.

KANG_G1 2025. 2. 3. 20:53

1️⃣ openapi generator를 사용하려는 이유

서버 측에서 소통 없이 API 수정을 진행하면 프론트엔드에서는 문제 발생 이전까진 변경점을 파악할 수 없다는 점과

프론트엔드 팀에서 협업하며 서버 response에 대한 각각의 type 설정으로 인한 사이드 이펙트가 존재했습니다.

 

프론트엔드에서 각자 타입을 설정해 사용하던 것을 서버 response의 타입이라고 착각해 벌어진 상황도 벌어졌는데요.

이와 비슷한 상황이 꽤나 잦았기에 대책을 찾던 중, openapi generator를 알게 되었습니다.

 

➡️ openapi generator 적용 전 알고 가면 좋을 것들

더보기

일단 openapi generator를 사용하려면 java가 로컬 환경에 설치되어있어야 합니다.

Java 설치를 완료한다면 프로젝트에서 openapi generator를 설치해 봅시다.

설치는 여기를 참고하시면 됩니다.

 

추가로 infcon 2024에서 진행한 세션이 있는데요, 쭉 돌려보며 필요한 부분을 정리해 봤습니다.

(OpenAPI Generator 실전편: 효율적인 코드를 작성하는 법 │ 인프콘2024)

 

1. Openapi generator 라이브러리 설정할 때, —additional-properties=modelPropertyNaming=original을 사용해 기능을 끄기! Union type과 함께할 때 문제 발생이 높기 때문에 기능을 꺼주는 것이 좋다.

 

2. —remove-operation-id-prefix를 사용하면 코드 길이를 줄여준다.

중복된 모델은 문제 발생 가능성이 있지만, 가독성을 크게 향상해 준다.

 

3. Enum을 Array로 만들어두면 편하다.(ex. 선택화면)

 

서버와 버전관리

코드리뷰의 용이성 및 레포지토리에서 관심사를 분리하기 위해 API 코드는 라이브러리로 분리.

Fetch 코드를 당장 사용하기 부담스럽다면 Model 생성부터 차근차근하는 것을 권장.


2️⃣ openapitools.json 설정과 package.json 설정

openapi generator를 설치했다면 추가 설정을 해주는 것이 좋습니다.

그런데 openapitools.json 설정이 저마다 다른데요.

typescript-axios, typescript-fetch 등 선택한 방식에 따라 설정이 달라지기 때문입니다.

 

저의 경우엔 팀에서는 fetch와 비슷한 ky를 사용하고 있기 때문에 typescript-fetch를 선택했고

typescript-fetch 관련 설정을 진행해 줬습니다.

 

‼️ typescript-axios와 typescript-fetch는 다른 설정이 필요

처음엔 typescript-axios의 openapitools.json을 작성했는데요.

typescript-fetch와 typescript-axios마다 다르게 설정이 필요하다는 점을 주의하셔야 합니다.

 

아래의 예시는 간단하게 작성한 typescript-axios의 openapitools.json입니다.

 //openapitools.json
 {
   "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
   "spaces": 2,
   "generator-cli": {
     "version": "7.10.0"
   }
 }

 

typescript-fetch의 openapitools.json의 기본 설정은 아래와 같습니다.

// openapitools.json(typescript-fetch version)
{
  "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
  "spaces": 2,
  "generator-cli": {
    "version": "7.10.0"
  }
}

 

+ 선택사항)

openapitools를 간단하게 작성하면 package.json에서 명령어 구문을 길게 작성해줘야 합니다.

// package.json
"scripts": {
    "dev": "next dev",
    "build": "next build",
	   ...
    "generate": "openapi-generator-cli generate -i ./src/yaml/your-porject.yaml -g typescript-fetch -o ./src/generate --skip-validate-spec --remove-operation-id-prefix --additional-properties=modelPropertyNaming=original,withInterfaces=true,supportsES6=true,apiPackage=api,enumPropertyNaming=original",
  },

 

openapitools.json을 상세하게 작성했을 경우에는 아래와 같습니다.

//openapitools.json
{
  "spaces": 2,
  "modelPackage": "src/generate/model",
  "apiPackage": "src/generate/api",
  "supportsES6": true,
  "withNodeImports": false,
  "useSingleRequestParameter": true,
  "enumNameSuffix": "",
  "withSeparateModelsAndApi": true
}

위처럼 설정해 준다면 package.json에서의 명령어 구문은 줄어들게 돼요.

// package.json
"scripts": {
    "dev": "next dev",
    "build": "next build",
	   ...
    "generate": "openapi-generator-cli generate -i ./src/yaml/your-porject.yaml -g typescript-fetch -o ./src/generate",
  },

 

저는 속성 설정 내용이 아직 적어 터미널에 속성을 추가해주는 것을 선택했습니다.


3️⃣ mustache를 통해 생성된 모듈 개선해 보기

앗 그런데 순수하게 generate 명령어를 통해 생성해 주니 기본적으로 내장되어 있는 템플릿으로

모듈이 생성되는데, 기본 제공 모듈의 코드의 맥락 파악이 너무 어렵더라고요.

보기 쉬운 코드를 위한 커스텀이 필요했는데, 기본 설정된 template이 아니라 custom template을

사용하기 위해 retrieving templates 내용을 확인해 봤습니다.

 

공식 문서를 보니 mustaches를 사용한 custom template를 사용할 수 있다고 해서 적용해 보았는데요.

다른 사람들의 custom template를 살펴봤을 때, 대부분 Enum Type을 Union Type로 수정해 줬는데

저는 typescript-fetch를 사용해서 그런지 뭔지 몰라도 해당 부분에 대한 설정이 필요 없었어요.

 

또한 서버에서 전달되는 Snake case field name을 Camel case field name으로 변환하는 작업도

필요가 없었습니다!


4️⃣ typescript-fetch로 생성된 기본 코드를 ky 입맛에 맞춰보기(는 실패)

다음으로 진행할 작업은 openapi generator와 ky의 장점을 둘 다 살려보기 위해

typescript-fetch로 생성된 기본 코드를 ky에 맞게 수정하는 것이었는데요.

파일 생성에 관여하는 runtime.ts의 수정이 필요했는데, 코드 최상단에 코드 수정을 하지 말라고

명시되어 있어 쫄았지만 수정을 진행했습니다.

 

2시간 삽질 결과, 기본 api 코드로 제공해 주는 부분을 ky로 변경하는 것은 포기했습니다.

기존 코드의 의존성이 너무 강하기도 하고, 사실 원본 코드 파악이 되질 않았기 때문이었는데요.

무리해서 코드를 수정했더니 코드 동작도 하지 않았고 어디서 문제가 발생하는지를 모르겠더라고요.

infcon 세션에서 말한 조언 중 하나였던 생성된 model만 사용하는 것으로 1차 마무리를 지었습니다.


5️⃣ 생성된 모델 네이밍 변경을 통해 가독성을 개선시키기

생성된 모델을 사용해 보니 불편한 점이 발생했는데요.

자동으로 생성된 타입명과 API 함수명이 너무 성의 없이 설정되었던 것이었습니다.

명령어로 generator가 실행되기 전에 추가 설정을 통해 문제를 해결해보고자 했지만 실패했어요.

(25/02/03 기준, 타입 앞에 I를 붙인다거나 하는 것은 가능하지만 아예 다른 네이밍으로 생성하는 건 불가.)

 

알고 보니 생성되는 타입명은 yaml이나 json에 명시된 정보를 기준으로 생성되는 것이고

yaml을 확인해 보니 백엔드 측에서 네이밍 한 것이 그대로 반영된 것이었습니다.

 

대안으로, 생성된 모델들의 네이밍을 import 할 때, as 키워드로 기존에 프론트엔드에서 사용하던

네이밍으로 변경하여 가독성 문제를 해결했습니다.


6️⃣ 번들링 사이즈 개선을 위해 불필요한 파일을 삭제해 봅시다

생성된 generate 관련 파일과 mustaches 관련 파일로 인한 웹팩 번들링 속도가 떨어지지 않을까?

불필요한 코드가 다수 존재하지 않을까?라고 문득 생각이 들었습니다. 

트리쉐이킹 진행이 필요했는데요.

 

기본 설정은 어지간하면 건들지 말라고 맨 위에 명시되어 있어 마구잡이로 삭제할 수는 없었는데요,

간단한 질문을 통해 삭제할 파일을 선정했습니다.

먼저 API Call이나 Object를 JSON 형태로 바꿔주는 코드들을 관리해 보자.
 -> 지금 당장 사용하는가? -> 아니다. apis에 설정한 ky 기반의 api call 로직을 사용할 것이다.
-> mustaches로 관리 가능한가? -> 어느 정도 가능한지 가늠이 안됨. 하지만 불필요한 mustach는 삭제가 필요하다.

 

1차 작업 시점에서 apis 폴더 내 생성된 api 관련 파일은 모두 삭제했고, 불필요한 mustache 파일을 삭제 및 수정하기로 결정했습니다.

수정하려고 보니 mustache 코드가 제가 평소에 접하던 코드 구조와는 전혀 달랐는데요.

 

➡️ mustache 기본 구조

mustache는 로직 없는 코딩을 지향하는데, 아래의 내용을 기본적으로 알고 있어야 합니다.

{{#variable}} - 섹션 시작 (반복/조건부 렌더링)
{{/variable}} - 섹션 종료
{{^variable}} - 반전 섹션 (값이 false/비어있을 때 렌더링)
{{>partial}} - 부분 템플릿 포함
{{!comment}} - 주석
{{ variable }} - 변수 값 출력

 

자, 기초를 뗐으니 바로 시작해 볼까요?

첫 번째로 프로젝트의 메인 진입점인 models/mustache에서 사용하지 않을 구문을 하나씩 체크해 보며 삭제해 봅시다.

아래는 제가 분류한 내용입니다.

필요한 mustache 파일들:
models.mustache - 모델 생성의 메인 템플릿
modelGeneric.mustache - 일반적인 모델 클래스 생성
modelEnum.mustache - enum 타입 생성
modelOneOf.mustache - union 타입 생성
modelGenericInterfaces.mustache - 인터페이스 정의
modelEnumInterfaces.mustache - enum 인터페이스 정의
modelOneOfInterfaces.mustache - union 타입 인터페이스 정의
models.index.mustache - 모델 인덱스 파일
licenseInfo.mustache - 라이센스 정보 (선택적)

불필요한 mustache 파일들:
records.mustache - Record 타입 생성 관련 (models만 사용한다면 불필요)
recordGeneric.mustache - Record 타입 생성 관련 (models만 사용한다면 불필요)
index.mustache - 전체 API 인덱스 파일 (models만 사용한다면 불필요)

TypeScript 모델 정의만 필요하다면 API 클라이언트나 Record 타입은 사용하지 않음
기본적인 타입 변환 (JSON serialization/deserialization)은 필요
/* tslint:disable */
/* eslint-disable */
{{>licenseInfo}}
{{#models}}
{{#model}}
{{#isEnum}} - delete
{{>modelEnum}} - delete
{{/isEnum}} - delete
{{^isEnum}}
{{#oneOf}} - delete
{{#-first}} - delete
{{>modelOneOf}} - delete
{{/-first}} - delete
{{/oneOf}} - delete
{{^oneOf}}
{{>modelGeneric}}
{{/oneOf}}
{{/isEnum}}
{{/model}}
{{/models}}

7️⃣ script 설정으로 편하게 openapi generator 사용하기

// package.json

"scripts": {
    ...
    "generate:default": "openapi-generator-cli generate -i ./src/openapi-generator/your-porject.yaml -g typescript-fetch -o ./src",
    "generate": "openapi-generator-cli generate -i ./src/openapi-generator/your-porject.yaml -g typescript-fetch -o ./src -t ./src/openapi-generator/mustaches --skip-validate-spec --remove-operation-id-prefix",
    "template": "openapi-generator-cli author template -g typescript-fetch -o ./src/openapi-generator/mustaches"
  },
  "devDependencies": {
  	...
   "@openapitools/openapi-generator-cli": "^2.15.3"
  }

 

프로젝트에 필요한 설정이 어느 정도 완료되었다면 package.json에 script를 추가해

상황에 따라 다르게 파일이 생성되어지도록 처리했습니다.

 

openapi generator를 사용하기 위한 순서

  1. src 폴더 내 generate 폴더 생성
  2. root에 mustaches 폴더 생성
  3. pnpm template & pnpm generate로 필요 파일 생성

8️⃣ openapi generator 적용 후 찾아온 변화

1)자유로워진 타입 관리

각 팀원별로 선언해 사용하던 서버 데이터의 type이나 interface로 인한 사이드 이펙트 걱정이 사라지며

DX를 개선, 다른 개발 업무에 집중할 수 있게 되었습니다.

 

2)swagger의 최신화 요청 필요

프론트엔드에서 api 통신과 연관된 타입을 신경쓰지 않아도 되지만

swagger 기반의 yaml을 사용해 작업을 진행해야 하기에 백엔드 분들에게 작업 변경 내역이 있다면

업데이트를 진행해 달라고 리마인드를 시켜주셔야 합니다.

 


Reference

openapi generator 공식 문서

npm openapitools/openapi-generator-cli

mustache github

 

blog)

openapi generator 개념과 적용 방법 정리 01

openapi generator 개념과 적용 방법 정리 02

주인장이 가장 많이 참고한 openapi generator 개념과 적용 방법 정리

typescript-axios 관련 세팅 정보