リポジトリ構造がそのままカスタマイズ力である理由
一言でいうと
npx @backstage/create-appが作ってくれるのは、Backstageのコピーではなく、自分たちが所有するモノレポです。カスタマイズは、設定を1行オンにすることではなく、そのリポジトリのコードを直して、ビルドしてデプロイすることであり、そのため、どこに何があるかを知っていることが、そのままカスタマイズの能力です。
なぜ必要なのか
ポータルを導入したチームが、最初にぶつかる質問は、たいていこういうものです。
- オンコールの情報をサービスのページに表示したいが、どのファイルを直せばよいか。
- 社内専用の機能を入れるには、どこに新しいパッケージを作ればよいか。
- プラグインを1つ付けたのに、画面に何も出ない。何を抜かしたのか。
- アップグレードはどうするのか。自分が直したものが上書きされないか。
これらの質問には、共通点があります。すべて、リポジトリの構造を知っていれば答えが出て、知らなければ答えが出ません。Backstageは「設定でオン・オフする製品」ではないので、画面から見つけられる答えがありません。
どう動くのか
リポジトリはワークスペース1つ
create-appが作ったツリーは、yarnワークスペースを使うモノレポです。
package.json 루트. private: true, workspaces 에 packages/* 와 plugins/*
packages/app/ 프런트엔드 애플리케이션 (React)
packages/backend/ 백엔드 애플리케이션 (Node.js)
plugins/<이름>/ 사내에서 만든 플러그인
app-config.yaml 설정
ルートをprivate: trueにする理由は、このパッケージ自体をデプロイしないからです。そして、workspacesにplugins/*を入れてはじめて、社内プラグインをnpmレジストリにアップロードしなくても、@internal/plugin-oncallのような名前で、アプリから持ってきて使えます。
package.jsonのbackstage.roleが鍵
各パッケージのpackage.jsonには、backstage.roleがあります。backstage-cliは、この値1つを見て、そのパッケージをどうビルドし、どうテストするかを決めます。
| role | 何か |
|---|---|
frontend |
packages/appというただ1つのフロントエンドのアプリ |
backend |
packages/backendというただ1つのバックエンドのアプリ |
frontend-plugin |
ブラウザーで動くプラグイン |
backend-plugin |
サーバーで動くプラグイン |
common-library · node-library · web-library |
プラグインではない共通のコード |
役割を間違って書くと、ビルドはできるのに、成果物がおかしくなります。バックエンドパッケージのmainが、ビルドのアーティファクト(dist/...)を指さなければならないのも、同じ理由です。
新しいバックエンドシステムは、登録するだけ
packages/backend/src/index.tsが、バックエンドのすべてです。
const backend = createBackend();
backend.add(import('@backstage/plugin-catalog-backend'));
backend.add(import('@backstage/plugin-catalog-backend-module-github'));
backend.start();
以前のバックエンドは、プラグインごとにルーターを手で作り、ロガー・設定・データベースを直接渡す必要がありました。新しいバックエンドシステムでは、登録するだけで、必要なものを注入されます。ここで、プラグインとモジュールの違いが、試験に出ます。
- プラグインは、機能1つをまるごと提供します。カタログ、スキャフォルダー、TechDocsのようなものです。
- モジュールは、すでにあるプラグインの拡張ポイントに差し込む断片です。
plugin-catalog-backend-module-githubは、カタログプラグインにGitHubのディスカバリーを差し込みます。
名前に-module-が入っていれば、それは単独では動作せず、対になるプラグインが一緒に登録されている必要があります。
フロントエンドプラグインは、3つのファイルから始まる
src/routes.ts createRouteRef 로 라우트 참조를 만들어 내보낸다
src/plugin.ts createPlugin 으로 플러그인을 만들고, 페이지를 확장으로 제공한다
src/index.ts 바깥에 공개할 것만 다시 내보낸다
ルート参照をあえて別のファイルに置くのには、理由があります。ページのコンポーネントがプラグインを参照し、プラグインがまたコンポーネントを参照すると、循環インポートが生じます。参照だけを入れたファイルを別に置けば、その輪が断ち切れます。
そして、ルートを持つページは、createRoutableExtensionで作り、mountPointにルート参照をかけます。コンポーネントを遅延インポートで渡すので、そのプラグインを実際に開くまでは、コードがダウンロードされません。
index.tsだけを公開の入口にするのも、規律です。アプリがプラグインの内部ファイルを直接持っていき始めると、プラグインの内部構造を、二度と変えられなくなります。
エンティティページにタブを付けるには、2か所を直す
<EntitySwitch>
<EntitySwitch.Case if={isKind('component')}>
<EntityLayout>
<EntityLayout.Route path="/oncall" title="On-call">
<OncallPage />
</EntityLayout.Route>
EntitySwitchとisKindでkindごとに別のページを見せ、EntityLayout.Routeでタブを付けます。よくある間違いが、ここで出ます。タブだけを付けてApp.tsxのルートを登録しないと、タブは見えるのに、クリックすると空の画面が表示されます。画面にはエラーがないので、原因を探しにくいです。
開発ワークフロー
yarn install --immutableは、ロックファイルを直さないという意味です。CIでこのフラグを外すと、依存関係が静かに上がって、昨日通ったコミットが、今日は違うようにビルドされます。そのあとは、型検査、ビルド、テストの順序です。この順序が重要な理由は、型エラー1つが、ほかのすべての出力を隠してしまうからです。
現場での姿
このリポジトリにも、同じ境界があります。logos.js、i18n.js、backend/app/grader_paths.pyは生成物なので、手で直すと、次の生成のときに消えます。直すべきなのは、生成器の側です。Backstageのアプリも、まったく同じ性格の境界を持ちます。packages/app/srcは自分のコードで、node_modulesの中のプラグインは、他人のコードです。境界を曖昧にして、他人のコードを直接直し始めると、アップグレードが不可能になります。
もう1つあります。このリポジトリで、複数人が並行して直していた日に、TypeScriptの文法エラー1つのせいで、検査ツールが残りをまったく見ずに終わったことがあります。「検査ツールが何も見つけられなかった」が、実は「何も検査されなかった」だったのです。ポータルのリポジトリも、規模が大きくなると、同じことが起きます。CIで型検査をビルドより先に置いておく理由が、これです。
次のラボですること
/root/cba-app/に、create-appが作り出すリポジトリの構造を、自分で組み立てます。ワークスペースのルートとアプリ2つ、社内プラグイン2つを作り、各パッケージに役割を付けます。そのあと、新しいバックエンドシステムの方式でプラグインとモジュールを登録し、フロントエンドプラグインの3つのファイルを書き、エンティティページにタブを付けます。最後に、パッケージのインベントリを取り出し、CIゲートを立てます。Nodeの依存関係をダウンロードできない環境なので、ビルドは行わず、構造と配線だけを扱います。