본문 바로가기
WIKI 기술 지식 베이스

Backstage 플러그인에서 OpenAPI 시작하기

원문 보기 위키 갱신

이 튜토리얼은 OpenAPI 스펙과 플러그인 수명 주기를 더 긴밀하게 연결해 주는 도구들을 소개해요. OpenAPI 도구 프로젝트 영역에서 만든 이 도구들을 이용하면 타입이 지정된 express 라우터, 자동 생성된 클라이언트, 검증·확인 도구까지 만들어 활용할 수 있어요.

출처: 문서

본문

타겟 독자: 플러그인 개발자

난이도: 중급

목표

이 튜토리얼의 목표는 OpenAPI 스펙과 플러그인 수명 주기를 더 긴밀하게 연결해 주는 도구들을 경험해 보는 것이에요. 소개하는 도구들은 OpenAPI 도구 프로젝트 영역에서 만들었으며, 다음을 만들 수 있어요.

  • 입력·출력 값에 대해 개발 중 강력한 안전장치를 제공하는 타입이 지정된 express 라우터. 쿼리·경로 파라미터와 요청 본문을 지원하고, 헤더와 쿠키는 실험적으로 지원해요.
  • 플러그인 백엔드와 상호작용하기 위한 자동 생성 클라이언트. 모든 요청 유형, 파라미터, 본문, 반환 타입을 지원해요. 더 높은 수준의 라이브러리가 더 많이 커스터마이즈할 수 있도록 저수준 인터페이스를 제공해요.
  • API와 스펙이 계속 동기화되도록 하는 검증·확인 도구. 단위 테스트에 대한 테스트도 포함돼요.

사전 준비

기술 지식

이 튜토리얼은 다음을 이미 잘 알고 있다고 가정해요.

  • Backstage 플러그인을 만드는 방법.
  • Express.js와 Typescript
  • OpenAPI 3.1 스키마

OpenAPI 버전 지원

Backstage는 OpenAPI 3.0과 3.1 스펙을 모두 지원해요. 기존 OpenAPI 3.0 스펙이 있다면 3.1로 마이그레이션하는 것을 권장해요. oasdiff upgrade spec.yaml을 사용하면 이 변환을 자동화할 수 있어요. 주요 변경 사항은 다음과 같아요.

  • nullable: true를 type: ['string', 'null']으로 바꾸거나 anyOf/oneOf 사용.
  • 경로 파라미터에서 allowReserved 제거 (3.1에서는 쿼리/쿠키 파라미터에서만 유효).

설정

워크스페이스 루트에 @backstage/repo-tools를 설치하세요. 이 패키지에는 플러그인 관련 모든 OpenAPI 명령이 들어 있으며, 튜토리얼 내내 사용돼요.

중단 변경 감지(package schema openapi diff)를 위해서는 시스템에 oasdiff CLI도 설치해야 해요. oasdiff 설치 안내를 참고하세요.

또한 java 바이너리가 PATH에 있어야 해요.

OpenAPI 스펙 저장

백엔드 플러그인에 src/schema 폴더를 새로 만들어 OpenAPI(및 기타) 스펙을 저장하세요. 예를 들어 catalog 플러그인에 스펙을 추가한다면 plugins/catalog-backend에 src/schema 폴더를 추가해 plugins/catalog-backend/src/schema 디렉터리를 만드는 거예요. 이 디렉터리 안에 openapi.yaml 파일이 있어야 해요.

현재는 .yml이 아니라 .yaml 확장자만 지원돼요.

스펙 검증

openapi.yaml을 작성한 후 플러그인 디렉터리에서 다음 명령을 실행하면 구조적으로 올바른 OpenAPI 3.x 문서인지 검증할 수 있어요.

yarn backstage-repo-tools package schema openapi validate

이 명령은 스펙이 올바르게 파싱되고 OpenAPI 스펙을 따르는지 확인해요. 스펙에서 코드를 생성하기 전에 실행해 두는 것이 좋아요.

스타일·모범 사례 린팅을 위해 다음 명령도 추가로 실행할 수 있어요.

yarn backstage-repo-tools repo schema openapi lint

스펙에서 타입 지정 express 라우터 생성

플러그인이 있는 디렉터리에서 yarn backstage-repo-tools package schema openapi generate --server를 실행하세요. 그러면 src/schema/openapi/generated 디렉터리에 router.ts 파일이 만들어지는데, 여기에는 OpenAPI 스키마와 스키마와 일치하는 타입을 가진 생성된 express 라우터의 팩토리 함수가 포함돼요.

이 명령은 나중에 다시 쓰기 위해 package.json에 추가해 두는 것이 좋아요. 서버 생성과 아래의 클라이언트 생성을 yarn backstage-repo-tools package schema openapi generate --server --client-package <clientPackageDirectory>처럼 합칠 수도 있어요.

router.ts 또는 createRouter.ts 파일을 다음과 같이 갱신해 사용하세요.

+ import { createOpenApiRouter } from '../schema/openapi';- import Router from 'express-promise-router';...export async function createRouter(  options: RouterOptions,): Promise<express.Router> {+ const router = await createOpenApiRouter();- const router = Router();

스펙에서 타입 지정 클라이언트 생성

현재 백엔드 플러그인 디렉터리에서 yarn backstage-repo-tools package schema openapi generate --client-package <plugin-client-directory>를 실행하세요. <plugin-client-directory>는 새로 만들 디렉터리이자 npm 패키지예요. 보통 플러그인의 common 패키지인 plugins/<plugin-name>-common/client에 새 진입점을 추가하는 패턴을 사용해요. 이 명령도 package.json에 추가해 두세요.

생성된 클라이언트에는 src/schema/openapi/generated 디렉터리가 있으며, DefaultApiClient 클래스와 생성된 모든 타입을 내보내요. 클라이언트는 다음과 같이 사용할 수 있어요.

+ import { DefaultApiClient } from '../schema/openapi/generated';export class CatalogClient implements CatalogApi {+ private readonly apiClient: DefaultApiClient;  constructor(options: {    discoveryApi: { getBaseUrl(pluginId: string): Promise<string> };    fetchApi?: { fetch: typeof fetch };  }) {+    this.apiClient = new DefaultApiClient(options);  }  ...

타입의 사용법은 타입 이름에 따라 달라져요.

생성된 DefaultApi.client.ts 파일은 API 요구 사항에 바로 사용할 수 있어야 해요. 완전한 커스터마이즈가 필요하다면 생성된 클라이언트를 감싸는 래퍼를 사용해 클라이언트만의 특성을 조정할 수 있어요.

자세한 내용은 문서를 참고하세요.

테스트 트래픽으로 스펙 검증

createRouter.test.ts 또는 router.test.ts 파일에 다음 줄을 추가하세요.

+ import { wrapServer } from '@backstage/backend-openapi-utils/testUtils';+ import type { Server } from 'node:http';...describe('createRouter', () => {- let app: express.Express;+ let app: Server;...- app = express().use(router);+ app = await wrapServer(express().use(router));

이렇게 하면 모든 요청과 응답을 캡처하고 테스트 중에 OpenAPI 스펙과 대조해 검증하는 프록시가 설정돼요. 스펙과 실제 API 동작 사이의 불일치는 테스트 실패로 보고돼요.

자세한 내용은 문서를 참고하세요.

더 알아보기 (Learn more)