⚙️
개발DevOps/도구

OpenAPI Generator로 개발 효율성 극대화하기

2024.10.25

배경

지난 FEConf 2024에서 바퀴 대신 로켓 만들기 발표를 듣고 OpenAPI Code Generator를 실무에 도입하기로 결정했습니다.

기존에는 백엔드에서 Swagger 명세를 받으면 프론트엔드에서 API 함수와 React Query 코드를 수동으로 작성해야 했는데, 이 과정을 자동화하여 팀 리소스를 크게 절약할 수 있을 것으로 판단했습니다.

도구 선택: Hey API

다양한 코드 제너레이터를 검토한 결과, Hey API를 선택했습니다.

선택 이유:

  • TypeScript에 특화된 현대적이고 깔끔한 코드 생성
  • React Query 지원
  • 활발한 업데이트와 우수한 문서화
  • fetch 기반의 안정적인 동작

구현 과정

1. 환경 설정

먼저 Java를 설치하고 OpenAPI Generator CLI를 사용할 수 있도록 환경을 구성했습니다.

2. API 스펙 다운로드 스크립트

API 문서가 업데이트될 때마다 수동으로 작업하지 않도록 환경변수 기반의 자동화 스크립트를 작성했습니다:

#!/bin/bash

# OpenAPI 스펙 파일 (로컬 파일 또는 URL)
OPENAPI_SPEC="<https://api-dev.stock.dropshot.io/docs/json>"

# Authorization
AUTHORIZATION="Bearer your-token-here"

# 결과가 저장될 경로
OUTPUT_DIR="./openapi/stock/web"

# OpenAPI Generator CLI로 변환 작업 실행
openapi-generator-cli generate \\
 -i "$OPENAPI_SPEC": 입력할 OpenAPI 스펙 파일 경로 \\
 -g openapi: 생성할 코드 타입 (여기서는 openapi 클라이언트) \\
 -a "$AUTHORIZATION": API 인증 정보 \\
 -o "$OUTPUT_DIR": 생성된 코드를 저장할 출력 디렉토리 \\
 --skip-validate-spec: 스펙 파일 유효성 검사 건너뛰기

3. 태그별 API 분리

팀 컨벤션에 맞게 태그별로 API를 분리하는 스크립트를 개발했습니다.

// splitByTag.mjs
import fs from 'fs';
import path from 'path';
import { cloneDeep, camelCase } from 'es-toolkit';

// OpenAPI 스펙을 태그별로 분리하는 함수
const splitByTags = () => {
  const tags = {};

  // paths를 순회하며 태그별로 분류
  Object.keys(openApiSpec.paths).forEach((path) => {
    Object.keys(openApiSpec.paths[path]).forEach((method) => {
      const operation = openApiSpec.paths[path][method];
      if (operation.tags) {
        operation.tags.forEach((tag) => {
          // 태그별 스펙 파일 생성 및 관련 스키마 추가
          // ... 구현 세부사항 생략
        });
      }
    });
  });

  // 태그별 파일 저장
  Object.keys(tags).forEach((tag) => {
    const outputFilePath = path.join(outputDir, `${camelCase(tag)}Api.json`);
    fs.writeFileSync(outputFilePath, JSON.stringify(tags[tag], null, 2));
  });
};

4. Package.json 스크립트 구성

{
  "scripts": {
    "split:stock-web": "node utils/splitByTag.mjs openapi/stock/web/openapi.json openapi/stock/web/api",
    "openapi:stock-web": "sh stock-web-generator.sh",
    "generate:stock-web": "node stock-web-config.mjs",
    "inject-client:stock-web": "node utils/injectClient.mjs \"src/stock/web/**/services.gen.ts\" ../../../../core/stock/web/client"
  }
}

5. 생성 결과물

Service 파일

// services.gen.ts (일부 발췌)
import { type Options } from '@hey-api/client-fetch';
import { client } from '../../../../core/stock/web/client';

/**
 * 유저 정보를 조회합니다.
 */
export const userControllerGetOneUser = <ThrowOnError extends boolean = false>(
  options: Options<UserControllerGetOneUserData, ThrowOnError>
) => {
  return (options?.client ?? client).get
    UserControllerGetOneUserResponse,
    UserControllerGetOneUserError,
    ThrowOnError
  >({
    ...options,
    url: '/v1/user/{id}',
    responseTransformer: UserControllerGetOneUserResponseTransformer
  });
};

Type 파일

// types.gen.ts (일부 발췌)
export type GetOneUserResponseDto = {
  externalId: string;
  name: string;
  email: string;
  signUpType: string;
  locale: string;
  country: string | null;
  isCreator: boolean;
  createdAt: Date;
  betaTester: GetOneBetaTesterResponse;
};

export type UserControllerGetOneUserData = {
  path: {
    id: string;
  };
};

Tanstack Query 훅

// react-query.gen.ts (일부 발췌)
import { queryOptions, type UseMutationOptions } from '@tanstack/react-query';

export const userControllerGetOneUserOptions = (
  options: Options<UserControllerGetOneUserData>
) => {
  return queryOptions({
    queryFn: async ({ queryKey }) => {
      const { data } = await userControllerGetOneUser({
        ...options,
        ...queryKey[0],
        throwOnError: true
      });
      return data;
    },
    queryKey: userControllerGetOneUserQueryKey(options)
  });
};

성과와 영향

개발 시간 단축

  • API 함수 수동 작성 시간 100% 절약
  • React Query 훅 작성 시간 100% 절약
  • 타입 정의 시간 100% 절약

코드 품질 향상

  • API 스펙과 클라이언트 코드 간 완벽한 동기화
  • 타입 안전성 보장
  • 일관된 코드 스타일 유지

유지보수성 개선

  • API 변경 시 자동으로 클라이언트 코드 업데이트
  • 수동 작업으로 인한 휴먼 에러 방지

향후 계획

현재는 사이드 프로젝트로 진행한 단계이며, 다음과 같은 개선 사항을 계획하고 있습니다:

  1. 모든 프로젝트 적용: 매치/스톡 서비스의 모든 클라이언트에 적용
  2. HTTP Client 통합: 프로젝트별로 다른 HTTP Client를 완전히 호환되도록 개선
  3. CI/CD 파이프라인 통합: API 스펙 변경 시 자동으로 코드 생성 및 배포

결론

OpenAPI Generator 도입으로 프론트엔드 개발 효율성을 크게 향상시킬 수 있었습니다. 특히 반복적인 코드 작성 작업을 자동화함으로써 개발자가 더 중요한 비즈니스 로직에 집중할 수 있게 되었습니다.

이러한 자동화 도구의 활용은 팀의 생산성을 높이고, 더 나은 사용자 경험을 제공하는 핵심 기능 개발에 리소스를 집중할 수 있게 해주는 중요한 투자라고 생각합니다.