플러그인 내부에서 Backstage 프록시 사용
플러그인 내부에서 Backstage 프록시 사용 (Using the Backstage Proxy from Within a Plugin)
이 가이드는 Backstage 외부에 배포된 기존 API에 대한 간단한 프록시를 설정하고, Backstage 프런트엔드 플러그인 내부에서 그 API로 요청을 보내는 방법을 안내합니다.
출처: 문서
본문
이 가이드는 Backstage 외부에 배포된 기존 API에 대한 간단한 프록시를 설정하고, Backstage 프런트엔드 플러그인 내부에서 그 API로 요청을 보내는 방법을 안내합니다.
플러그인이 API에 접근해야 한다면 Backstage는 3가지 옵션을 제공합니다.
-
API에 직접 접근할 수 있습니다.
-
프런트엔드 플러그인과 함께 API를 구현한다면 백엔드 플러그인을 만들 수 있습니다.
-
이미 존재하는 API로 프록시하도록 Backstage를 구성할 수 있습니다.
Table of Contents
-
Setting up the backstage proxy
-
Calling an API using the backstage proxy
-
Option 1: Calling the proxy directly from the frontend plugin
-
Option 2: Defining the API client interface
-
Defining the API client interface
-
Creating the API client
-
Bundling your ApiRef with your plugin
-
Using the API in your components
Backstage 프록시 설정 (Setting up the backstage proxy)
플러그인의 API가 https://api.myawesomeservice.com/v1 에 호스팅되어 있고, backstage 내부에서는 /api/proxy/<your-proxy-uri>에서 접근할 수 있기를 원하며, X-Custom-Source라는 기본 헤더를 추가하고 싶다고 가정해 봅시다. app-config.yaml에 다음을 추가해야 합니다.
proxy: endpoints: '/<your-proxy-uri>': target: https://api.myawesomeservice.com/v1 headers: X-Custom-Source: backstage
프록시 구성 옵션에 대한 더 자세한 내용은 프록시 섹션에서 찾을 수 있습니다.
Backstage 프록시를 사용한 API 호출 (Calling an API using the backstage proxy)
이전 단계를 따랐다면 이제 ${backend-url}/api/proxy/<your-proxy-uri>를 호출해 API에 접근할 수 있어야 합니다. backend-url을 참조하는 이유는 backstage 백엔드가 프록시를 만들고 실행하기 때문입니다. Backstage는 backstage 프런트엔드를 백엔드와 독립적으로 실행할 수 있는 방식으로 구성되어 있습니다. 따라서 API를 호출할 때 HTTP 호출 앞에 백엔드 URL을 붙여야 합니다.
서비스 호출에는 두 가지 권장 패턴이 있습니다. 프런트엔드 플러그인에서 discoveryApi와 fetchApi를 직접 사용하거나, 호출을 Utility API로 감싸는 것입니다.
옵션 1: 프런트엔드 플러그인에서 프록시 직접 호출 (Option 1: Calling the proxy directly from the frontend plugin)
프런트엔드 플러그인에서 fetchApi와 discoveryApi를 사용해 올바른 프록시 엔드포인트를 호출하세요.
plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx
import { useApi, discoveryApiRef, fetchApiRef,} from '@backstage/core-plugin-api';import { Progress, Alert } from '@backstage/core-components';import { useAsync, useMountEffect } from '@react-hookz/web';import { myAwesomeApiRef } from '../../api';export const AwesomeUsersTable = () => { const fetchApi = useApi(fetchApiRef); const discoveryApi = useApi(discoveryApiRef); const [{ status, result, error }, { execute }] = useAsync(async () => { const baseUrl = await discoveryApi.getBaseUrl('proxy'); // As configured previously for the backend proxy const resp = await fetchApi.fetch(`${baseUrl}/<your-proxy-uri>`); if (!resp.ok) throw new Error(resp.statusText); return resp.json(); }); useMountEffect(execute); // ...};
옵션 2: API 클라이언트 인터페이스 정의 (Option 2: Defining the API client interface)
이 섹션은 API 클라이언트를 Utility API로 감싸는 단계를 설명합니다.
-
createApiRef를 사용해 새ApiRef를 만듭니다. -
createApiFactory를 사용해 플러그인에ApiFactory를 등록합니다. 그러면 API 구현이 감싸지고,ApiRef가 구현과 연결되며, Backstage가 이를 어떻게 인스턴스화할지 알게 됩니다. -
마지막으로
useApi를 호출해 컴포넌트에서 API를 사용할 수 있습니다.
API 클라이언트 인터페이스 정의 (Defining the API client interface)
이전 예제를 이어서, https://api.myawesomeservice.com/v1 에 다음과 같은 엔드포인트가 있다고 가정해 봅시다.
| | Method | Description | GET /users | Returns a list of users | GET /users/{userId} | Returns a single user | DELETE /users/{userId} | Deletes a user
다음은 backstage의 apiRef 스타일을 따른 이 API의 예제 정의입니다.
/* src/api.ts */import { createApiRef } from '@backstage/core-plugin-api';export interface User { name: string; email: string;}export interface MyAwesomeApi { url: string; listUsers: () => Promise<List<User>>; getUser: (userId: string) => Promise<User>; deleteUser: (userId: string) => Promise<boolean>;}export const myAwesomeApiRef = createApiRef<MyAwesomeApi>({ id: 'plugin.my-awesome-api.service',});
API 클라이언트 생성 (Creating the API client)
myAwesomeApiRef는 플러그인에서 API 클라이언트를 참조할 때 backstage 내부에서 사용하는 것입니다. API ref 자체는 인스턴스화된 API를 참조할 수 있게 해주는 전역 싱글턴 객체입니다. 실제 구현은 대략 다음과 같습니다.
/* src/api.ts *//* ... */import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';export class MyAwesomeApiClient implements MyAwesomeApi { discoveryApi: DiscoveryApi; fetchApi: FetchApi; constructor({discoveryApi, fetchApi}: {discoveryApi: DiscoveryApi, fetchApi: FetchApi}) { this.discoveryApi = discoveryApi; this.fetchApi = FetchApi; } private async fetch<T = any>(input: string, init?: RequestInit): Promise<T> { // As configured previously for the backend proxy const proxyUri = `${await this.discoveryApi.getBaseUrl('proxy')}/<your-proxy-uri>`; const resp = await this.fetchApi.fetch(`${proxyUri}${input}`, init); if (!resp.ok) throw new Error(resp.statusText); return await resp.json(); } async listUsers(): Promise<List<User>> { return await this.fetch<List<User>>('/users'); } async getUser(userId: string): Promise<User> { return await this.fetch<User>(`/users/${userId}`); } async deleteUser(userId: string): Promise<boolean> { return await this.fetch<boolean>( `/users/${userId}`, { method: 'DELETE' } ); }
DiscoveryApi나 FetchApi에 대한 자세한 내용은 문서를 확인하세요.
ApiRef를 플러그인과 묶기 (Bundling your ApiRef with your plugin)
퍼즐의 마지막 조각은 myAwesomeApiRef를 MyAwesomeApiClient 객체 팩토리와 묶는 것입니다. 이는 보통 플러그인 src 디렉터리의 plugin.ts 파일에서 수행됩니다. 이전 코드를 api.ts라는 파일에 추가했다고 가정할 때의 예는 다음과 같습니다.
/* src/plugin.ts */import { myAwesomeApiRef, MyAwesomeApiClient } from './api';import { createPlugin, createRouteRef, createApiFactory, createRoutableExtension, createComponentExtension, discoveryApiRef, fetchApiRef,} from '@backstage/core-plugin-api';//...export const myCustomPlugin = createPlugin({ id: '<your-plugin-name>', // Configure a factory for myAwesomeApiRef apis: [ createApiFactory({ api: myAwesomeApiRef, deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef, }, factory: ({ discoveryApi, fetchApi }) => new MyAwesomeApiClient({ discoveryApi, fetchApi }), }), ],});
컴포넌트에서 API 사용 (Using the API in your components)
이제 플러그인 코드 내부에서 backstage 훅 useApi를 사용해 API에 접근할 수 있어야 합니다.
plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx
import { useApi } from '@backstage/core-plugin-api';import { myAwesomeApiRef } from '../../api';import { useAsync, useMountEffect } from '@react-hookz/web';export const AwesomeUsersTable = () => { const apiClient = useApi(myAwesomeApiRef); const [{ status, result, error }, { execute }] = useAsync(async () => { const users = await apiClient.listUsers(); return users; }, [apiClient]); useMountEffect(execute); // ...};