Files
backstage/plugins/events-backend

events-backend

Welcome to the events-backend backend plugin!

This plugin provides the wiring of all extension points for managing events as defined by plugin-events-node including backend plugin EventsPlugin and EventsBackend.

Additionally, it uses a simple in-process implementation for the EventBroker by default which you can replace with a more sophisticated implementation of your choice as you need (e.g., via module).

Some of these (non-exhaustive) may provide added persistence, or use external systems like AWS EventBridge, AWS SNS, Kafka, etc.

By default, the plugin ships with support to receive events via HTTP endpoints POST /api/events/http/{topic} and will publish these to the used event broker.

Installation

# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-events-backend

Event Broker

First you will need to add and implementation of the EventBroker interface to the backend plugin environment. This will allow event broker instance any backend plugins to publish and subscribe to events in order to communicate between them.

Add the following to makeCreateEnv

// packages/backend/src/index.ts
+   const eventBroker = new DefaultEventBroker(root.child({ type: 'plugin' }));

Then update plugin environment to include the event broker.

// packages/backend/src/types.ts
+  eventBroker: EventBroker;

Publishing and Subscribing to events with the broker

Backend plugins are passed the event broker in the plugin environment at startup of the application. The plugin can make use of this to communicate between parts of the application.

Here is an example of a plugin publishing a payload to a topic.

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  env.eventBroker.publish({
    topic: 'publish.example',
    eventPayload: { message: 'Hello, World!' },
    metadata: {},
  });
}

Here is an example of a plugin subscribing to a topic.

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  env.eventBroker.subscribe([
    {
      supportsEventTopics: ['publish.example'],
      onEvent: async (params: EventParams) => {
        env.logger.info(`receieved ${params.topic} event`);
      },
    },
  ]);
}

Implementing an EventSubscriber class

More complex solutions might need the creation of a class that implements the EventSubscriber interface. e.g.

import { EventSubscriber } from "./EventSubscriber";

class ExampleSubscriber implements EventSubscriber {
  ...

  supportsEventTopics() {
      return ['publish.example']
  }

  async onEvent(params: EventParams) {
    env.logger.info(`receieved ${params.topic} event`)
  }
}

Events Backend

The events backend plugin provides a router to handler http events and publish the http requests onto the event broker.

To configure it add a file packages/backend/src/plugins/events.ts to your Backstage project.

Additionally, add the events plugin to your backend.

// packages/backend/src/index.ts
// [...]
+import events from './plugins/events';
// [...]
+  const eventsEnv = useHotMemoize(module, () => createEnv('events'));
// [...]
+  apiRouter.use('/events', await events(eventsEnv));
// [...]

Configuration

In order to create HTTP endpoints to receive events for a certain topic, you need to add them at your configuration:

events:
  http:
    topics:
      - bitbucketCloud
      - github
      - whatever

Only those topics added to the configuration will result in available endpoints.

The example above would result in the following endpoints:

POST /api/events/http/bitbucketCloud
POST /api/events/http/github
POST /api/events/http/whatever

You may want to use these for webhooks by SCM providers in combination with suitable event subscribers.

However, it is not limited to these use cases.

Event-based Entity Providers

You can implement the EventSubscriber interface on an EntityProviders to allow it to handle events from other plugins e.g. the event backend plugin mentioned above.

Assuming you have configured the eventBroker into the PluginEnvironment you can pass the broker to the entity provider for it to subscribe.

// packages/backend/src/plugins/catalog.ts
 import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
+import { DemoEventBasedEntityProvider } from './DemoEventBasedEntityProvider';
 import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
 import { Router } from 'express';
 import { PluginEnvironment } from '../types';

 export default async function createPlugin(
   env: PluginEnvironment,
 ): Promise<Router> {
   const builder = await CatalogBuilder.create(env);
   builder.addProcessor(new ScaffolderEntitiesProcessor());
+  const demoProvider = new DemoEventBasedEntityProvider({ logger: env.logger, topics: ['example'], eventBroker: env.eventBroker });
+  builder.addEntityProvider(demoProvider);
   const { processingEngine, router } = await builder.build();
   await processingEngine.start();
   return router;
 }

Use Cases

Custom Event Broker

Example using the EventsBackend:

new EventsBackend(env.logger)
  .setEventBroker(yourEventBroker)
  // [...]
  .start();

Example using a module:

import { eventsExtensionPoint } from '@backstage/plugin-events-node';

// [...]

export const yourModuleEventsModule = createBackendModule({
  pluginId: 'events',
  moduleId: 'yourModule',
  register(env) {
    // [...]
    env.registerInit({
      deps: {
        // [...]
        events: eventsExtensionPoint,
        // [...]
      },
      async init({ /* ... */ events /*, ... */ }) {
        // [...]
        const yourEventBroker = new YourEventBroker();
        // [...]
        events.setEventBroker(yourEventBroker);
      },
    });
  },
});

Request Validator

Example using the EventsBackend:

const http = HttpPostIngressEventPublisher.fromConfig({
  config: env.config,
  ingresses: {
    yourTopic: {
      validator: yourValidator,
    },
  },
  logger: env.logger,
});
http.bind(router);

await new EventsBackend(env.logger)
  .addPublishers(http)
  // [...]
  .start();

Example using a module:

import { eventsExtensionPoint } from '@backstage/plugin-events-node';

// [...]

export const eventsModuleYourFeature = createBackendModule({
  pluginId: 'events',
  moduleId: 'yourFeature',
  register(env) {
    // [...]
    env.registerInit({
      deps: {
        // [...]
        events: eventsExtensionPoint,
        // [...]
      },
      async init({ /* ... */ events /*, ... */ }) {
        // [...]
        events.addHttpPostIngress({
          topic: 'your-topic',
          validator: yourValidator,
        });
      },
    });
  },
});