기억의 실마리
2025. 11. 13. 23:59

* 본 포스팅 내용은 2024.09에 실적용한 기술이며, 도입 과정에서 작성해둔 내용을 기반으로 서술하였습니다.

 

✏️ 기술을 도입하게 된 계기

사내 첫 in-house service 예정 프로젝트를 개발하는 과정에서 생긴 문제로, MVP개발 단계이기 때문에 잦은 수정으로 프론트/백엔드 개발자 간 소통이 잦았으며, 매우 중요하기도 했습니다.

 

둘 중 한 포지션에서 실수를 하거나 변경사항을 전달하지 못한다면 오히려 소통의 비용이 더 늘어나고 작업기간 지속 지연되는 문제가 있었습니다.

 

문제점 요약

  • Swagger 문서와 실제 API 타입 간 불일치
  • 백엔드 스펙 변경 시 프론트엔드 타입을 직접 수정해야 하는 번거로움
  • 매번 axios 호출 함수를 수동 작성 → 반복 작업 증가
  • 실수로 인한 타입 오류 및 런타임 에러 발생

결국, API 변경 시마다 "이게 body에 들어가는 필드 맞나요?" 혹은 "string이 아니고 number인가요?" 등의 소통이 반복되었고, 개발 효율이 점점 떨어졌습니다.

 

 

DX / UX를 지향하는 저는 다시금 스스로에게 질문을 던졌습니다.

"이러한 문제를 어떻게 해결할 수 있을까?"

 

 

취업준비 기간에 만든 풀스택 프로젝트인 🔗ZETE - AI 메모 서비스를 개발할 때 그런 생각을 한적이 있습니다.

api를 만들고 Swagger에 spec을 명시하는데 이걸 다시 프론트엔드로 끌고와서 자동화 할 순 없을까?

 

병아리 개발자인 내가 이렇게 생각하는 기능이라면 분명히 아득히 먼 선배 개발자들이 모두 만들어 두었을거라는 가정을 가지고 탐색해 보았고 첫번째 검색에서 바로 찾을 수 있었습니다.

 

그리고 OpenAPI와 openapi-generator-cli 에 대해 알게 되었고 즉시 공식문서를 확인하며 긴 노력 끝에 React + Nest.js를 사용한 프로젝트에 적용시켜 이 후에는 월등히 빠른 속도로 풀스택 개발을 진행할 수 있었습니다.

 

그렇게 활용했던 openapi-generator-cli 를 도입하면 많은 것이 해결될 것 같다는 생각이 들어 동료과 논의 후 대규모 리팩터링인 만큼 기술건의의 주체인 만큼 책임감을 가지고 업무 외 시간(아무도 개발을 하지 않는 시간)을 활용하여 직접 구축하여 통합 적용했습니다.

 

🪜 도입과정

가장 먼저 openapi-generator-cli를 설치했습니다.

npm i @openapitools/openapi-generator-cli

 

🥄 꺼내기 ( Eject )

eject한 jar파일 경로

 

기존에 찾아내어 적용한 방식대로 설치 후 node_modules에서 templates 파일을 eject하기 위해 jar파일을 찾아내어 복사 후 압축을 풀고 mustache파일을 꺼냈습니다.

 

🪄 설정파일 & 명령어 추가

openapi-generator-cli 생성 명령어가 워낙 길기도 하고 auth / api 두가지 카테고리로 서버가 분류되어 있기에 작업하기 좋은 환경 즉, DX향상을 위해서 OpenAPI-CLI를 실행시킬 js파일을 아래와 같은 형식으로 구성했습니다.

require("dotenv").config({
  path: ".env.production",
});

const { execSync } = require("child_process");

const genObject = {
  auth: { url: process.env.SWAGGER_AUTH_URL, path: "./src/libs/openapi/auth" },
  api: { url: process.env.SWAGGER_API_URL, path: "./src/libs/openapi/api" },
  test: "test",
};

const current = genObject[process.env.GENERATE_TYPE];

// 제너레이트타입이 존재하지 않는 경우
if (!current) {
  console.error("GENERATE_TYPE 환경 변수가 설정되지 않았습니다.");
  process.exit(1);
}

// 명령어 실행 함수
execSync(
  `openapi-generator-cli generate -i ${current.url} -g typescript-fetch -o ${current.path} -c ./openapi.json --skip-validate-spec -t ./src/libs/openapi/templates/${process.env.GENERATE_TYPE}`,
  { stdio: "inherit" },
);

 

추가적으로 openapi.json, openapitools.json 두가지의 별도 설정파일이 더 있지만 생략했습니다.

 

명령어를 구성할 때 사내 배포환경이 [ dev, qa, prod ] 총 세가지 환경으로 분기되어 있어 환경마다 각기 다른 script 명령어가 필요했습니다.

 

다양하게 테스트 하고 실무에 바로 적용하기 위해

 

카테고리 개별 생성 ( import-auth / import-api ),

전체 생성과 삭제 ( create-api / remove-api ),

전체 삭제 후 전체 생성 ( reset-api )

 

3가지 스크립트 로직으로 구분짓고 엮어서 자동화 프로세스를 완성시켰습니다.

 

    "import-auth:dev": "dotenv -e .env.development -- cross-env GENERATE_TYPE=auth node openapi-generate.js",
    "import-auth:qa": "dotenv -e .env.qa -- cross-env GENERATE_TYPE=auth node openapi-generate.js",
    "import-auth:prod": "dotenv -e .env.production -- cross-env GENERATE_TYPE=auth node openapi-generate.js",
    "import-api:dev": "dotenv -e .env.development -- cross-env GENERATE_TYPE=api node openapi-generate.js",
    "import-api:qa": "dotenv -e .env.qa -- cross-env GENERATE_TYPE=api node openapi-generate.js",
    "import-api:prod": "dotenv -e .env.production -- cross-env GENERATE_TYPE=api node openapi-generate.js",
    "create-api:dev": "dotenv -e .env.development -- cross-env pnpm run import-auth:dev && pnpm run import-api:dev",
    "create-api:qa": "dotenv -e .env.qa -- cross-env pnpm run import-auth:qa && pnpm run import-api:qa",
    "create-api:prod": "dotenv -e .env.production -- cross-env pnpm run import-auth:prod && pnpm run import-api:prod",
    "remove-api": "rimraf ./src/libs/openapi/api ./src/libs/openapi/auth",
    "reset-api:dev": "pnpm run remove-api && pnpm run create-api:dev",
    "reset-api:qa": "pnpm run remove-api && pnpm run create-api:qa",
    "reset-api:prod": "pnpm run remove-api && pnpm run create-api:prod",

 

꽤나 장황해 보입니다... dotenv와 각 환경 변수를 활용하여 작성하였습니다.

당시 저의 최선이었지만 분명 더 좋은 방법이 있을 것이라고 생각합니다🥲

 

🥸 API를 생성하는 템플릿 Mustache 파일 커스텀

첫인상이 폭력적인 mustache파일...

 

이게 뭐지 감도 안잡힌다 느낌이었지만 자세히 보면서 하나하나 주석을 남기며 어떤 부분이 어떤 역할을 하는지, 어떤 구문 어떤 코드를 생성해내는지 등 분석하여 기존 사내 프로세스와 커스텀 펫처 등이 정상작동하도록 templates를 커스텀 했습니다.

당시에는 ai를 제대로 활용하지 못했고 cursor - ai도 사용하지 않았기 때문에 주석을 남기며 [ 생성 / 검토 / 수정 ]을 반복하며 작업했기 때문에 좀 더 고된 경험이었습니다...🥹

 

고된 경험1: 주석을 남긴다.
고된 경험2: 생성하고 확인한다.

 

이렇게 openapi-generator-cli 를 활용한 api 생성 자동화를 적용하였습니다.

 

🚩결과

결과적으로는 3가지 이점을 얻을 수 있었습니다.


첫 번째로 불필요한 커뮤니케이션 제거되었습니다.

login api - body에 들어가는 필드 중 validateFlag가 number -> string으로 바뀌었어요!

➔ login api 변경사항 있어 재배포 합니다!

 

구체적인 설명이 필요 없이 특정 엔드포인트의 재배포 라는 단어만으로 프론트엔드는 npm run reset-api:dev 등의 명령어로 간단하게 수정사항이 반영된 api를 받아 빠르게 직접 확인할 수 있게 되어 커뮤니케이션 리소스가 감소했습니다.


두 번째로 유지보수 편의성이 향상되었습니다.

 

예시 변경 사항:

login api에서 env: "dev" | "qa" | "prod" 와 같이 req body 필드에 추가하고

response를 env에 따라 level: "noob" | "normal" | "super" 과 같이 내려주어야 하는 추가 기능이 생겼다.

 

[ Before ]

1. 변경되어야 하는 req의 필드 키 네임과 값의 타입 확인

2. req body에 정의된 interface 수정

3. api의 req body 필드를 변경사항과 동일한 필드로 수정

4. reponse에 정의된 interface 수정

 

[ After ]

1. npm run reset-api:dev

2. api의 req body 필드를 변경사항과 동일한 필드로 수정

 

하나하나 수정해 줄 필요 없이 명령어로 리세팅 후 req body만 변경해주면 되기 때문에 편의성이 크게 향상되었습니다.


세 번째로 코드 품질과 일관성이 향상되었습니다.

협업 과정에서 개발자마다 서로 다른 코드 스타일이 혼합되면 일관성이 떨어질 가능성이 높습니다.

그러나 openapi-generator-cli 기반 자동 생성 방식을 도입하면서, 검증된 템플릿을 기반으로 일관성 있는 api 생성이 가능해졌습니다.