新Backend Systemでお手軽になる!? BackstageのPlugin導入 - APC 技術ブログ

APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

新Backend Systemでお手軽になる!? BackstageのPlugin導入

Backstageとは

なにかと話題にしていますBackstageですが、ご存知無い方のためにあらためて。
BackstageはCNCFのプロジェクトの1つで、拡張性の高い開発者ポータルのOSSです。機能拡張などはPluginを追加導入することで実現します。
具体的な機能などは、これまでにも何回か本ブログでも紹介しているので、そちらをご一読いただくのがよいかと思います。

techblog.ap-com.co.jp

techblog.ap-com.co.jp

背景

Backstageの導入はちょっと面倒だった

これまでにも何度か取り上げているテーマが「Backstageの導入は面倒な部分が多い」ということです。 いろいろな機能を追加できるBackstageですが、いざ「始めよう」とか、「Pluginを追加しよう」と思ったとき、実はこれまでは面倒な作業がいろいろありました。

そのあたりを少しでも簡単にしようと「ちょこっとBackstage」でした。(どれくらい面倒かも含めてサイトでご紹介しております)

techblog.ap-com.co.jp

めんどくささの解消に向けて

その「面倒くささ」というのはBackstage OSSコミュニティでも認知しており、2023年〜2024年のBackstageの主要なテーマの1つが、『導入を簡単に』というものです。それの最終形が2024年初頭に登場してくるであろう「Backstage Quickstart」です。

techblog.ap-com.co.jp

実はこうした機能の前提として、アーキテクチャの変更があります。それが以下の2つです。

Backendは少し前から公開されていましたが、Frontendも先日リリースされた v1.21 で公開されました。

本題

ここからが本題です。
どのくらい楽になったのかについて、今回はBackend Systemについて、実例を交えてご紹介したいと思います。

従来バージョン

これまでのBackend SystemではBackstageのPluginを導入するためには、それぞれのPluginにあったコードを追加する必要がありました。

たとえば標準的な機能だけを利用するにあたっても以下のフォルダのように、導入するPluginごとにコードを用意しなければなりません。この例では7個のPluginを導入しようとしています。そして一部のPluginはさらに機能拡張のコードも含まれています。

github.com

この中のコードを2つほどご紹介すると、以下のようなものです。

scaffolder.ts。こちらは比較的シンプルな内容です。

import { CatalogClient } from '@backstage/catalog-client';
import { createRouter } from '@backstage/plugin-scaffolder-backend';
import { Router } from 'express';
import type { PluginEnvironment } from '../types';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  const catalogClient = new CatalogClient({
    discoveryApi: env.discovery,
  });

  return await createRouter({
    logger: env.logger,
    config: env.config,
    database: env.database,
    reader: env.reader,
    catalogClient,
    identity: env.identity,
    permissions: env.permissions,
  });
}

search.ts。こちらは内部に機能拡張用のコードを多数含んでいます。

import { useHotCleanup } from '@backstage/backend-common';
import { createRouter } from '@backstage/plugin-search-backend';
import {
  IndexBuilder,
  LunrSearchEngine,
} from '@backstage/plugin-search-backend-node';
import { PluginEnvironment } from '../types';
import { DefaultCatalogCollatorFactory } from '@backstage/plugin-search-backend-module-catalog';
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs';
import { Router } from 'express';
import { PgSearchEngine } from '@backstage/plugin-search-backend-module-pg';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  // Initialize a connection to a search engine.
  const searchEngine = (await PgSearchEngine.supported(env.database))
    ? await PgSearchEngine.fromConfig(env.config, { database: env.database })
    : new LunrSearchEngine({ logger: env.logger });
  const indexBuilder = new IndexBuilder({
    logger: env.logger,
    searchEngine,
  });

  const schedule = env.scheduler.createScheduledTaskRunner({
    frequency: { minutes: 10 },
    timeout: { minutes: 15 },
    // A 3 second delay gives the backend server a chance to initialize before
    // any collators are executed, which may attempt requests against the API.
    initialDelay: { seconds: 3 },
  });

  // Collators are responsible for gathering documents known to plugins. This
  // collator gathers entities from the software catalog.
  indexBuilder.addCollator({
    schedule,
    factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
      discovery: env.discovery,
      tokenManager: env.tokenManager,
    }),
  });

  // collator gathers entities from techdocs.
  indexBuilder.addCollator({
    schedule,
    factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
      discovery: env.discovery,
      logger: env.logger,
      tokenManager: env.tokenManager,
    }),
  });

  // The scheduler controls when documents are gathered from collators and sent
  // to the search engine for indexing.
  const { scheduler } = await indexBuilder.build();
  scheduler.start();

  useHotCleanup(module, () => scheduler.stop());

  return await createRouter({
    engine: indexBuilder.getSearchEngine(),
    types: indexBuilder.getDocumentTypes(),
    permissions: env.permissions,
    config: env.config,
    logger: env.logger,
  });
}

標準的なPluginに関しては初期に自動生成してくれるのでよいのですが、自分で追加する際にはこうしたコードの導入は面倒以外の何物でもありませんよね。

新バージョン

新Backend Systemではこうしたコードを書く必要がなくなりました。つまり、pluginsフォルダが一切不要になります。 代わりに記述すべきは以下のような内容です。

import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();

backend.add(import('@backstage/plugin-app-backend/alpha'));

backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('@backstage/plugin-auth-backend-module-github-provider'))

backend.add(import('@backstage/plugin-catalog-backend/alpha'));
backend.add(import('@backstage/plugin-catalog-backend-module-github/alpha'));
backend.add(import('@backstage/plugin-catalog-backend-module-github-org'));
backend.add(import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'));

backend.add(import('@backstage/plugin-proxy-backend/alpha'));

backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));

backend.add(import('@backstage/plugin-search-backend/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
// backend.add(import('@backstage/plugin-search-backend-module-pg/alpha'));

backend.add(import('@backstage/plugin-techdocs-backend/alpha'));

backend.start();

コードはこれが全てです。使いたいPluginをimportしてadd するだけ。従来バージョンのものと比較すると非常に簡単ですね!! あとは細かい指定を数行コンフィグレーションに必要がありますが、これまで記述していたコードに比べたらほんの数%の作業量です。

ここまで楽になれば、本当に簡単にはじめられるようになるのでは?? (ちょこっとBackstageが不要になるかも??)

残る(またはあらたに発生する)課題

さてこれだけ楽になったBackend SystemへのPluginの導入ですが、若干課題があります。 といってもおそらく通常みなさんが利用になられる際にはほとんど問題にはならないはず。

課題となるというのは以下のようなパターンです。

さきほどのsearch.tsの例では以下のような部分があります。

  const searchEngine = (await PgSearchEngine.supported(env.database))
    ? await PgSearchEngine.fromConfig(env.config, { database: env.database })
    : new LunrSearchEngine({ logger: env.logger });
  const indexBuilder = new IndexBuilder({
    logger: env.logger,
    searchEngine,
  });

これは PgSearchEngine.supported(env.database)) のところでコンフィグレーションを読み込み、PostgreSQLへ接続する記述があれば、PostgreSQLを、そうでなければインメモリデータベースを検索で利用する、という切り替えを行っています。

実は新Backend Systemではこうしたコンフィグレーションの読み込みがPlugin側で行われるように隠蔽されてしまったため、上記のようなコード上での切り替えが簡単には実現できません。

さきほどの例では以下のような部分がありました。

// backend.add(import('@backstage/plugin-search-backend-module-pg/alpha'));

まさにこれがこの課題を反映したものです。ローカル環境で実行するときはインメモリデータベースを利用するためにpostgresql用拡張のimportをコメントアウトしています。

2023年12月22日時点ではこれを解消する手段などは公開されていないようです。

ケースとしてはあまり一般的ではないとは思いますが、こうした課題がでても私達独自で対応策を導入できるよう仕組みを今後作成していこうと考えています。

まとめ

まだ確認はしていませんが、11月にシカゴで行われたBackstageCONで発表されたNew Frontend Systemもコードを記述することなくシステムを拡張し、画面を定義できるようになりそうです。これまでの苦労が嘘のように感じることになると思います。

2024年はPlatform Engineering / Internal Developer Platform(Portal) が日本でも多く取り上げられるようになる年になると考えております。そうしたトレンドに乗り遅れないためにも、簡単になりつつつあるBackstageをぜひお試しになってはいかがでしょうか。

ご本家OSSサイトではデモ環境も用意されてます。「ちょこっとBackstage」ならばdocker compseですぐにローカル環境で動かすこともできます。またソースコードを自分で修正してお試しいただくことも可能です(残念ながらNew Backend Systemにはまだ対応できてませんが・・)。

github.com

demo.backstage.io

また実際に業務の環境で導入してみたいのだけど、という方がいらっしゃいましたらぜひ私達にご相談ください。

www.ap-com.co.jp

2024年3月20日 追記

Backstage v1.24.0が3月20日に公開されました。このタイミングで、Backstageを新規作成する(create-appを実行する)際のコードが新Backend Systemベースに変更されました。

github.com