이벤트 기반 업데이트를 Entity Provider와 통합하기
이벤트 기반 업데이트를 Entity Provider와 통합하기 (Integrating Event-Driven Updates with Entity Providers)
이 가이드는 이벤트를 수신할 HTTP 엔드포인트를 설정하고 Events System을 EntityProvider에 통합하여 카탈로그를 즉시 업데이트하는 방법을 안내합니다.
출처: 문서
본문
이 가이드는 이벤트를 수신할 HTTP 엔드포인트를 설정하고 Events System을 EntityProvider에 통합하여 카탈로그를 즉시 업데이트하는 방법을 안내합니다. Events 시스템이 다른 사용 사례도 지원하지만, 이 가이드는 특히 EntityProvider에서 HTTP 기반 이벤트를 사용하는 데 초점을 맞춥니다.
제공된 예시에서는 Frobs라는 외부 가상 서비스에서 엔터티를 수집하는 방법을 보여줍니다.
기본 흐름:
- 외부 서비스(이 예시에서는
Frobs)가@backstage/plugin-events-backend플러그인이 노출하는 HTTP 엔드포인트로 이벤트를 보냅니다. 이 엔드포인트는 정의한 토픽(예:frobs)에 해당합니다. @backstage/plugin-events-backend플러그인을 확장해 사용자 지정 Router를 노출하는 모듈이 이 일반 토픽의 수신 이벤트를 처리하고, 각 이벤트 페이로드의 내용에 따라 더 구체적인 하위 토픽으로 라우팅합니다.EntityProvider가 이러한 특정 하위 토픽 이벤트를 구독하고, 수신 시 카탈로그의 엔터티를 그에 따라 추가/업데이트/삭제하는 작업을 수행합니다.
HTTP 엔드포인트로 이벤트 수신하기 (Receiving Events via HTTP Endpoints)
The @backstage/plugin-events-backend 플러그인은 HTTP 엔드포인트를 통해 이벤트를 수신하기 위한 즉시 사용 가능한(out-of-the-box) 지원을 제공합니다. 이러한 이벤트는 EventsService에 게시됩니다.
특정 토픽에 대한 HTTP 엔드포인트를 만들려면 app-config.yaml에서 구성해야 합니다.
events: http: topics: - frobs
이 구성에 명시적으로 나열된 토픽만 사용 가능한 HTTP 엔드포인트가 됩니다.
위 예시는 다음 엔드포인트를 만듭니다.
POST /api/events/http/frobs
이 URL을 외부 서비스에서 웹훅을 설정할 때 페이로드 URL로 사용할 수 있습니다. 이벤트가 이 엔드포인트로 전송되면 events-backend가 이를 Events Service에 게시하여 구독 중인 모든 엔터티 공급자가 사용할 수 있게 합니다.
일반 토픽을 특정 하위 토픽으로 라우팅하기 (Routing General Topics to Specific Subtopics)
이벤트를 효과적으로 관리하려면 events-backend 플러그인용 모듈을 만들어 일반 토픽을 더 구체적인 하위 토픽으로 라우팅하도록 이벤트 시스템을 확장할 수 있습니다.
예를 들어 POST /api/events/http/frobs 엔드포인트를 사용해 Frobs 서비스용 웹훅을 설정하면 모든 수신 이벤트가 처음에는 일반 frobs 토픽 아래에 게시됩니다. 그러나 페이로드(예: type 필드)를 기반으로 이러한 이벤트를 더 구체적인 하위 토픽 아래에 다시 게시하면 EntityProvider가 더 넓은 frobs 토픽 아래의 모든 이벤트를 처리하는 대신 필요한 관련 하위 토픽만 구독할 수 있게 됩니다.
다음은 일반 frobs 토픽을 구독하고 이벤트 페이로드에 제공된 $.type을 기준으로 더 구체적인 하위 토픽 아래에 이벤트를 게시하는 SubTopicEventRouter의 예시입니다.
import { EventParams, EventsService, SubTopicEventRouter,} from '@backstage/plugin-events-node';/** * Subscribes to the generic `frobs` topic * and publishes the events under the more concrete sub-topic * depending on the `$.type` provided in the event payload. * * @public */export class FrobsEventRouter extends SubTopicEventRouter { constructor(options: { events: EventsService }) { super({ events: options.events, topic: 'frobs', }); } protected getSubscriberId(): string { return 'FrobsEventRouter'; } protected determineSubTopic(params: EventParams): string | undefined { if ('type' in (params.eventPayload as object)) { const payload = params.eventPayload as { type: string }; return payload.type; } return undefined; }}
Events를 Entity Provider에 통합하기 (Integrating Events into an Entity Provider)
엔터티 공급자는 특정 이벤트 토픽을 구독하고 수신 이벤트에 반응할 수 있습니다. 이를 통해 외부 트리거에 기반한 카탈로그의 즉시 업데이트가 가능합니다.
다음은 이벤트 구독을 통합한 EntityProvider의 기본 구조입니다. 아래 번호 표시를 확인해 각 단계를 살펴보세요.
plugins/catalog-backend-module-frobs/src/FrobsProvider.ts
import { Entity } from '@backstage/catalog-model';import { EntityProvider, EntityProviderConnection,} from '@backstage/plugin-catalog-node';import { SchedulerServiceTaskRunner, UrlReaderService,} from '@backstage/backend-plugin-api';import { EventsService, EventParams } from '@backstage/plugin-events-node';/** * Provides entities from the fictional Frobs service. */export class FrobsProvider implements EntityProvider { private readonly env: string; private readonly reader: UrlReaderService; private readonly taskRunner: SchedulerServiceTaskRunner; private readonly events?: EventsService; private connection?: EntityProviderConnection; constructor( env: string, reader: UrlReaderService, taskRunner: SchedulerServiceTaskRunner, /** [1] */ events?: EventsService, ) { this.env = env; this.reader = reader; this.taskRunner = taskRunner; this.events = events; } getProviderName(): string { return `frobs-${this.env}`; } async connect(connection: EntityProviderConnection): Promise<void> { this.connection = connection; /** [2] */ await this.events?.subscribe({ id: this.getProviderName(), topics: ['frobs-add', 'frobs-delete', 'frobs-modify'], /** [3] */ onEvent: async (params: EventParams) => { const id = params.eventPayload.id; const baseUrl = `https://frobs-${id}.example.com/data`; const response = await this.reader.readUrl(baseUrl); const data = JSON.parse((await response.buffer()).toString()); const entities: Entity[] = frobsToEntities(data); if (params.topic === 'frobs-add') { await this.connection!.applyMutation({ type: 'delta', added: entities, removed: [], }); } else if (params.topic === 'frobs-delete') { await this.connection!.applyMutation({ type: 'delta', added: [], removed: entities, }); } else if (params.topic === 'frobs-modify') { ...
이 통합의 핵심 부분을 살펴보겠습니다.
- EventsService를 의존성으로 추가하기: EntityProvider의 생성자에
EventsService를 선택적 의존성으로 포함합니다. 이를 통해 공급자가 이벤트 시스템과 상호작용할 수 있습니다. - connect에서 토픽 구독하기: connect 함수 내에서 플러그인이 반응해야 하는 특정 이벤트 토픽(예: 'frobs-add', 'frobs-delete', 'frobs-modify')을 구독합니다.
onEvent메서드 구현하기:onEvent메서드는 중요합니다. 공급자가 구독한 토픽에 대한 이벤트를 받을 때마다 호출됩니다. 이 메서드 내에서:- 이벤트 페이로드 정보(
params.eventPayload로 접근 가능)를 기반으로 어떤 엔터티를 추가, 삭제, 수정해야 할지 결정하는 로직을 구현합니다. - 델타(delta) 변형을 사용해 엔터티를 명시적으로 upsert하거나 삭제합니다. 이 접근 방식은 전체 카탈로그를 처음부터 업데이트하는 것보다 더 효율적입니다. 변형에 대한 자세한 내용은 "Provider Mutations" 섹션을 참조하세요.
- 이벤트 페이로드 정보(