Utility API 만들기(Creating Utility APIs)
이 섹션에서는 Utility API를 처음부터 만드는 방법, 또는 기존 것에 구성 가능성(configurability)과 입력(input)을 추가하는 방법을 설명해요. 기존 Utility API를 이전 프런트엔드 시스템에서 마이그레이션하는 데 관심이 있다면 API 마이그레이션 섹션을 확인하세요.
출처: 문서
본문
이 섹션에서는 Utility API를 처음부터 만드는 방법, 또는 기존 것에 구성 가능성(configurability)과 입력(input)을 추가하는 방법을 설명해요. 기존 Utility API를 이전 프런트엔드 시스템에서 마이그레이션하는 데 관심이 있다면 API 마이그레이션 섹션을 확인하세요.
Utility API 계약(contract) 만들기
utility API를 노출하는 첫 단계는 그것의 TypeScript 계약과, 소비자가 구현에 접근하기 위해 쓰는 API 참조를 정의하는 것이에요. API를 다른 플러그인이 접근할 수 있게 하려면 여러분 플러그인의 -react 패키지에서 이 작업을 해야 해요. 그래야 별도로 임포트할 수 있거든요. 자신의 플러그인 안에서만 API를 쓰고 싶다면 정의를 플러그인 자체에 두어도 괜찮아요. 이 예시에서는 어떤 유형의 작업을 수행하기 위한 utility API를 노출하려는 Example 플러그인이 있다고 가정해요.
@internal/plugin-example-react에서:
import { createApiRef } from '@backstage/frontend-plugin-api';
/**
* The work interface for the Example plugin.
* @public
*/
export interface WorkApi {
/**
* Performs some work.
*/
doWork(): Promise<void>;
}
/**
* API Reference for {@link WorkApi}.
* @public
*/
export const workApiRef = createApiRef<WorkApi>({
id: 'plugin.example.work',
});
이 둘은 패키지에서 공개적으로 제대로 내보내져서 소비자가 그것들에 닿을 수 있어요.
프런트엔드 시스템은 ApiRef id에서 API의 소유 플러그인을 추론하므로, plugin.<plugin-id>.* 패턴을 사용해 소유권을 명시적으로 만들고, 다른 플러그인이 여러분의 API를 실수로 오버라이드할 수 없도록 하세요.
플러그인을 통해 확장 제공하기
이제 플러그인 자체가 이 API와 그 기본 구현을 API 확장 형태로 제공하고 싶어해요. 그렇게 하면 사용자가 Example 플러그인을 설치할 때 Work utility API의 인스턴스도 앱에서 자동으로 사용 가능해져요. Example 플러그인 자신뿐 아니라 다른 플러그인도 말이죠. 이 작업은 -react 패키지가 아니라 주요 플러그인 패키지에서 해요.
@internal/plugin-example에서:
import {
ApiBlueprint,
createFrontendPlugin,
storageApiRef,
StorageApi,
} from '@backstage/frontend-plugin-api';
import { WorkApi, workApiRef } from '@internal/plugin-example-react';
class WorkImpl implements WorkApi {
constructor(options: { storageApiRef: StorageApi }) {
/* TODO */
}
async doWork() {
/* TODO */
}
}
const workApi = ApiBlueprint.make({
name: 'work',
params: defineParams =>
defineParams({
api: workApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => {
return new WorkImpl({ storageApi });
},
}),
});
/**
* The Example plugin.
* @public
*/
export default createFrontendPlugin({
pluginId: 'example',
extensions: [exampleWorkApi],
});
설명을 위해 뼈대 구현 클래스와 그것을 위한 API 확장 및 factory를 같은 파일에 만들었어요. 이것들은 플러그인 패키지의 공개 표면으로 내보내지지 않아요. 기본 export인 플러그인만 내보내져요. 플러그인을 설치한 사용자는 이제 utility API도 자동으로 얻게 돼요.
이 코드는 또한 API factory가 다른 utility API, 즉 이 경우에는 핵심 storage API에 대한 의존성을 선언하는 방법을 보여줘요. 그 utility API의 인스턴스가 factory 함수에 제공돼요.
work API의 확장 ID는 kind인 api: 뒤에 namespace로서의 플러그인 ID, / 구분자, 마지막으로 확장에 사용한 이름이 붙는 형태가 돼요. 이 경우 api:example/work가 돼요. 이게 어떻게 동작하는지에 대한 자세한 내용은 명명 패턴 문서를 확인하세요. 이제 이 ID를 사용해 app-config와 다른 곳에서 API를 참조할 수 있어요. 플러그인 기능의 중심이 되는 API가 하나뿐인 경우(대부분 API 클라이언트)에는 확장 이름을 생략해 api:<pluginId>로 끝낼 수도 있어요.
구성 가능성 추가하기
여기서는 utility API에 app-config가 구동하는 확장 구성(extension config)을 가질 수 있는 기능을 덧붙이는 방법을 설명할게요. API 확장 factory 함수에 확장 구성 스키마를 주면 돼요. 위 예시를 구성도 받아들이도록 리팩터링해 볼게요. 그러려면 blueprint의 override 메서드를 사용해야 해요.
@internal/plugin-example에서:
import { z } from 'zod';
const exampleWorkApi = ApiBlueprint.makeWithOverrides({
configSchema: {
goSlow: z.boolean().default(false),
},
factory(originalFactory, { config }) {
return originalFactory({
factory: createApiFactory({
api: workApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => {
return new WorkImpl({
storageApi,
goSlow: config.goSlow,
});
},
}),
});
},
});
API 인스턴스에 대해 goSlow 확장 구성 매개변수를 설정할 수 있게 하고 싶었고, 그것을 새 구성 스키마에 선언했어요. 실제 확장 구성 값은 타입 안전한 방식으로 blueprint factory에 전달되고, 거기서 그것을 사용해 API factory를 만들고 blueprint 매개변수로 넘길 수 있어요.
여기서 쓰인 "확장 구성(extension config)"이라는 표현은 전체 app-config에 접근할 수 있게 해 주는 configApi와는 다른 것임을 알아두세요. 여기서 논의하는 확장 구성은 여러분의 utility API 인스턴스에 주어지는 특정 구성 설정을 말해요. 이에 대해서는 구성하기(Configuring) 섹션에서 더 논의해요.
또한 확장 구성 스키마에 goSlow 필드의 기본값이 들어 있었다는 점을 알아두세요. 이것은 중요한 고려 사항이에요. API 사용자가 구성 방법을 깊이 파지 않고도 최대한의 가치를 얻을 수 있게 하고 싶어요. 그래서 일반적으로 가능한 한 많은 합리적인 기본값을 제공하고, 사용자는 드물지만 목적을 가지고, 필요할 때만 오버라이드하게 하려는 거예요. 기본값이 없는 확장 구성 스키마가 있으면, 사용자가 그 값들을 명시적으로 구성하지 않는 한 프레임워크가 시작 시 utility API 인스턴스화를 거부해요. 기본값이 있었으므로 TypeScript 코드와 인터페이스도 방어적으로 undefined를 허용하지 않아도 돼요. 확장 구성 데이터를 소비하기 시작할 때 그것이 기본값이거나 오버라이드된 값을 가질 것임을 알기 때문이에요.
입력(inputs) 추가하기
입력은 Utility API에 다른 확장 blueprint와 같은 방식으로 추가돼요.
-
.makeWithOverrides를 사용하고 확장을 위한inputs집합을 선언하세요. -
필요하면 그 입력에 사용할 커스텀 확장 데이터 유형을 만드세요.
-
필요하면 특정 attachment 유형을 만드는 확장 blueprint를 만들고 내보내세요.
이것은 파워 사용 사례라서 흔히 쓰이지 않아요.
다음 단계
이 새 utility API를 다양한 방식으로 소비하는 방법은 소비하기(Consuming) 섹션을 확인하세요. 그것을 구성하고 입력을 추가하고 싶다면 구성하기(Configuring) 섹션을 확인하세요.