From 8037c55ea90698690c6a400ae5def4aa75eb478f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 30 Sep 2022 16:13:51 +0200 Subject: [PATCH 01/13] initial backend docs Signed-off-by: Johan Haals --- docs/api/backend.md | 128 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index 0990343a01..fd3da4fbd8 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -4,4 +4,130 @@ title: Backend description: About Backend --- -## TODO +## Backend System + +**DISCLAMER: The new backend system is under active development and is not considered stable** + +### Overview + +The default backend provides several services out of the box which are available to all plugins but there might cases where you want to provide a completely new service in your installation. + +### Service Refs + +A serviceRef is a named reference to an interface which are later used to resolve the actual service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. +Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database. + +On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them. +ServiceRefs does contain a scope which is used to determine if the serviceFactory creating the service will create a new instance for each plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin and `root` scoped services will be created once per backend instance. + +#### Defining a ServiceRef + +In its simplest form the serviceRef can be defined like this referencing the type of the actual implementation. + +```ts +import { + createServiceFactory, + pluginMetadataServiceRef, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; +import { ExampleImpl } from './ExampleImpl'; + +export interface ExampleApi { + doSomething(): Promise; +} + +export const exampleServiceRef = createServiceRef({ + id: 'example', + scope: 'plugin', // can be 'root' or 'plugin' + + // The defaultFactory is optional to implement but it will be used if no other factory is provided to the backend. + // This is allows for the backend to provide a default implementation of the service without having to wire it beforehand. + defaultFactory: async service => + createServiceFactory({ + service, + deps: { + logger: loggerServiceRef, + plugin: pluginMetadataServiceRef, + }, + // Logger is available directly in the factory as it's a root scoped service and will be created once per backend instance. + async factory({ logger }) { + + // plugin is available as it's a plugin scoped service and will be created once per plugin. + return async ({ plugin }) => { + // This block will be executed once per plugin depending on this serviceRef + logger.info(`Creating example service for for plugin ${plugin.id}`); + return new ExampleImpl({logger}); + }; + }, + }), +}), +``` + +### Overriding services + +In this example replace the default log implementation with a custom one. + +```ts +import { + createServiceFactory, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; +export const gcpLoggerFactory = createServiceFactory({ + service: loggerServiceRef, + deps: {}, + async factory({}) { + return async ({}) => { + // This custom implementation conform with the type of the loggerServiceRef + return new GoogleCloudLogger(); + }; + }, +}); + +// packages/backend/src/index.ts +const backend = createBackend({ + services: [ + // supplies additional/replacement services to the backend + gcpLoggerFactory, + ], +}) +``` + +#### API Overview +`createBackend` +`createBackendPlugin` +`createBackendModule` +`createServiceRef` +`createExtensionPoint` +### Writing Plugins + +### Writing modules + +Some facts about modules + +- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin. +- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. +- A module is always initialized before the plugin it extends. + +A module depend on the extensionPoint exported by the plugins library package(eg `catalog-node`, `scaffolder-backend`) and does not directly declare a dependency on the plugin package itself. + + + +### Overwriting services + + +### Extension Points + +```ts +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +export interface ScaffolderActionsExtensionPoint { + addAction(action: ScaffolderAction): void; +} + +export const ScaffolderActionsExtensionPoint = + createExtensionPoint({ + id: 'scaffolder.actions', + }); +``` + +### Testing From 6a8f7e5a731097b1bfdf84f1d64e7675692e5a61 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 4 Oct 2022 16:18:27 +0200 Subject: [PATCH 02/13] chore: more text Signed-off-by: Johan Haals --- docs/api/backend.md | 122 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 16 deletions(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index fd3da4fbd8..5f04d38214 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -8,22 +8,32 @@ description: About Backend **DISCLAMER: The new backend system is under active development and is not considered stable** +This is an example of how you create, start and add existing plugins to your backend. + +```ts +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +// backend.add(catalogPlugin()); +await backend.start(); +``` + ### Overview -The default backend provides several services out of the box which are available to all plugins but there might cases where you want to provide a completely new service in your installation. +The default backend provides several _services_ out of the box which includes access to config, logging, scheduling and more. +Service are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. ### Service Refs -A serviceRef is a named reference to an interface which are later used to resolve the actual service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. +A serviceRef is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database. On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them. -ServiceRefs does contain a scope which is used to determine if the serviceFactory creating the service will create a new instance for each plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin and `root` scoped services will be created once per backend instance. +ServiceRefs contain a scope which is used to determine if the serviceFactory creating the service will create a new instance scoped per plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin/module and `root` scoped services will be created once per backend instance. #### Defining a ServiceRef -In its simplest form the serviceRef can be defined like this referencing the type of the actual implementation. - ```ts import { createServiceFactory, @@ -65,7 +75,7 @@ export const exampleServiceRef = createServiceRef({ ### Overriding services -In this example replace the default log implementation with a custom one. +In this example replace the default log implementation with a custom logger. ```ts import { @@ -92,15 +102,39 @@ const backend = createBackend({ }) ``` -#### API Overview -`createBackend` -`createBackendPlugin` -`createBackendModule` -`createServiceRef` -`createExtensionPoint` -### Writing Plugins +## Writing Plugins -### Writing modules +```ts +import { configServiceRef, createBackendPlugin } from '@backstage/backend-plugin-api'; + +// export type ExamplePluginOptions = { exampleOption: boolean }; +export const examplePlugin = createBackendPlugin({ + // unique id for the plugin + id: 'example', + // It's possible to provide options to the plugin + // register(env, options: ExamplePluginOptions) { + register(env) { + env.registerInit({ + deps: { + logger: loggerServiceRef, + }, + // logger is provided by the backend based on the dependency on loggerServiceRef above. + async init({ logger }) { + logger.info('Hello from example plugin'); + }, + }); + }, +}); +``` + +The plugin can then be installed to the backend using + +```ts +backend.add(examplePlugin()); +// Options can be passed to the plugin +// backend.add(examplePlugin({ exampleOption: true})); +``` +## Writing Modules Some facts about modules @@ -110,13 +144,35 @@ Some facts about modules A module depend on the extensionPoint exported by the plugins library package(eg `catalog-node`, `scaffolder-backend`) and does not directly declare a dependency on the plugin package itself. +Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint` +```ts +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { MyCustomProcessor } from './processor'; -### Overwriting services - +export const exampleCustomProcessorCatalogModule = createBackendModule({ + moduleId: 'exampleCustomProcessor', + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addProcessor(new MyCustomProcessor()); + }, + }); + }, +}); +``` ### Extension Points +Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. + +#### Defining an Extension Point + ```ts import { createExtensionPoint } from '@backstage/backend-plugin-api'; @@ -130,4 +186,38 @@ export const ScaffolderActionsExtensionPoint = }); ``` +#### Registering an Extension Point + +Extension points are registered by a plugin and extended by modules. + + ### Testing + +Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. + +```ts +import { startTestBackend } from '@backstage/backend-test-utils'; + +describe('Example', () => { + it('should do something', async () => { + await startTestBackend({ + // mock services can be provided to the backend + services: [someServiceFactory], + // plugins and modules for testing + features: [testModule()], + }); + // assertions + }); +}); +``` + + +## Package structure + +The package relationship between plugins, modules and extension are illustrated in the following diagram. + +Taken with an artificial foobar backend plugin. + +- `plugin-foobar-backend` houses the plugin and registers the extension points into the backend system. +- `plugin-foobar-common` houses the shared types including the Extension Point registered by the backend. +- `plugin-foobar-XYZ-module` houses the modules that extend the foobar backend with extension points imported from `plugin-foobar-common` From a83eb55d1c4e6f9b55bed4c4076872bb7f0a0201 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:32:58 +0200 Subject: [PATCH 03/13] docs/api/backend: updates to fit current system and reuse existing docs Signed-off-by: Patrik Oldsberg --- docs/api/backend.md | 102 +++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index 5f04d38214..5c2b4c4245 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -1,38 +1,43 @@ --- id: backend -title: Backend -description: About Backend +title: New Backend System +description: Details of the upcoming backend system --- -## Backend System +> **DISCLAMER: The new backend system is under active development and is not considered stable** -**DISCLAMER: The new backend system is under active development and is not considered stable** +## Overview This is an example of how you create, start and add existing plugins to your backend. ```ts import { createBackend } from '@backstage/backend-defaults'; +import { catalogPlugin } from '@backstage/plugin-catalog-backend'; +// Create your backend instance const backend = createBackend(); -// backend.add(catalogPlugin()); +// Install all desired features +backend.add(catalogPlugin()); + +// Start up the backend await backend.start(); ``` -### Overview +## Backend Services -The default backend provides several _services_ out of the box which includes access to config, logging, scheduling and more. -Service are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. +The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. +Services are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. -### Service Refs +### Service References -A serviceRef is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. +A `ServiceRef` is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database. On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them. ServiceRefs contain a scope which is used to determine if the serviceFactory creating the service will create a new instance scoped per plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin/module and `root` scoped services will be created once per backend instance. -#### Defining a ServiceRef +#### Defining a Service ```ts import { @@ -61,51 +66,59 @@ export const exampleServiceRef = createServiceRef({ }, // Logger is available directly in the factory as it's a root scoped service and will be created once per backend instance. async factory({ logger }) { - // plugin is available as it's a plugin scoped service and will be created once per plugin. return async ({ plugin }) => { - // This block will be executed once per plugin depending on this serviceRef - logger.info(`Creating example service for for plugin ${plugin.id}`); - return new ExampleImpl({logger}); + // This block will be executed once for every plugin that depends on this service + logger.info('Initializing example service plugin instance'); + return new ExampleImpl({ logger }); }; }, }), -}), +}); ``` -### Overriding services +### Overriding Services -In this example replace the default log implementation with a custom logger. +In this example replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service. ```ts import { createServiceFactory, - loggerServiceRef, + rootLoggerServiceRef, + LoggerService, } from '@backstage/backend-plugin-api'; -export const gcpLoggerFactory = createServiceFactory({ - service: loggerServiceRef, - deps: {}, - async factory({}) { - return async ({}) => { - // This custom implementation conform with the type of the loggerServiceRef + +// This custom implementation would typically live separately from +// the backend setup code, either nearby such as in +// packages/backend/src/services/logger/GoogleCloudLogger.ts +// Or you can let it live in its own library package. +class GoogleCloudLogger implements LoggerService { + static factory = createServiceFactory({ + service: rootLoggerServiceRef, + deps: {}, + async factory() { return new GoogleCloudLogger(); - }; - }, -}); + }, + }); + // custom implementation here ... +} // packages/backend/src/index.ts const backend = createBackend({ services: [ - // supplies additional/replacement services to the backend - gcpLoggerFactory, + // supplies additional or replacement services to the backend + GoogleCloudLogger.factory(), ], -}) +}); ``` ## Writing Plugins ```ts -import { configServiceRef, createBackendPlugin } from '@backstage/backend-plugin-api'; +import { + configServiceRef, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; // export type ExamplePluginOptions = { exampleOption: boolean }; export const examplePlugin = createBackendPlugin({ @@ -134,6 +147,7 @@ backend.add(examplePlugin()); // Options can be passed to the plugin // backend.add(examplePlugin({ exampleOption: true})); ``` + ## Writing Modules Some facts about modules @@ -142,9 +156,9 @@ Some facts about modules - A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. - A module is always initialized before the plugin it extends. -A module depend on the extensionPoint exported by the plugins library package(eg `catalog-node`, `scaffolder-backend`) and does not directly declare a dependency on the plugin package itself. +A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. -Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint` +Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: ```ts import { createBackendModule } from '@backstage/backend-plugin-api'; @@ -177,7 +191,7 @@ Modules depend on extension points just as a regular dependency but specifying i import { createExtensionPoint } from '@backstage/backend-plugin-api'; export interface ScaffolderActionsExtensionPoint { - addAction(action: ScaffolderAction): void; + addAction(action: ScaffolderAction): void; } export const ScaffolderActionsExtensionPoint = @@ -190,8 +204,7 @@ export const ScaffolderActionsExtensionPoint = Extension points are registered by a plugin and extended by modules. - -### Testing +## Testing Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. @@ -206,18 +219,17 @@ describe('Example', () => { // plugins and modules for testing features: [testModule()], }); - // assertions + + // assertions }); }); ``` - ## Package structure -The package relationship between plugins, modules and extension are illustrated in the following diagram. +A detailed explanation of the package architecture can be found in the [Backstage Architecture Overview](../overview/architecture-overview.md#package-architecture). The most important packages to consider for this system are `backend`, `plugin--backend`, `plugin--node`, and `plugin--backend-module-`. -Taken with an artificial foobar backend plugin. - -- `plugin-foobar-backend` houses the plugin and registers the extension points into the backend system. -- `plugin-foobar-common` houses the shared types including the Extension Point registered by the backend. -- `plugin-foobar-XYZ-module` houses the modules that extend the foobar backend with extension points imported from `plugin-foobar-common` +- `plugin--backend` houses the implementation of the plugins themselves. +- `plugin--node` houses the extension points and any other utilities that modules or other plugins might need. +- `plugin--backend-module-` houses the modules that extend the plugins via the extension points. +- `backend` is the backend itself that wires everything together to something that you can deploy. From 7c7a124563120e1ea50c3ff242903daea5ce9546 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:34:54 +0200 Subject: [PATCH 04/13] docs/api/backend: move to docs/plugins/new-backend-system and add to sidebar Signed-off-by: Patrik Oldsberg --- docs/{api/backend.md => plugins/new-backend-system.md} | 2 +- microsite/sidebars.json | 3 ++- mkdocs.yml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) rename docs/{api/backend.md => plugins/new-backend-system.md} (99%) diff --git a/docs/api/backend.md b/docs/plugins/new-backend-system.md similarity index 99% rename from docs/api/backend.md rename to docs/plugins/new-backend-system.md index 5c2b4c4245..5ce1b1b81a 100644 --- a/docs/api/backend.md +++ b/docs/plugins/new-backend-system.md @@ -1,5 +1,5 @@ --- -id: backend +id: new-backend-system title: New Backend System description: Details of the upcoming backend system --- diff --git a/microsite/sidebars.json b/microsite/sidebars.json index f00b7b2ee1..3cfa1aa2f6 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -233,7 +233,8 @@ "plugins/proxying", "plugins/backend-plugin", "plugins/call-existing-api", - "plugins/url-reader" + "plugins/url-reader", + "plugins/new-backend-system" ] }, { diff --git a/mkdocs.yml b/mkdocs.yml index 8c86ae095d..906e2aebeb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -137,6 +137,7 @@ nav: - Backend plugin: 'plugins/backend-plugin.md' - Call existing API: 'plugins/call-existing-api.md' - URL Reader: 'plugins/url-reader.md' + - New Backend System: 'plugins/new-backend-system.md' - Testing: - Testing with Jest: 'plugins/testing.md' - Publishing: From a6c98097e206d0f89d683044e3d6b1ce2d7a02af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:38:21 +0200 Subject: [PATCH 05/13] docs/links: added short lint to new backend system docs Signed-off-by: Patrik Oldsberg --- microsite/pages/en/link.js | 1 + 1 file changed, 1 insertion(+) diff --git a/microsite/pages/en/link.js b/microsite/pages/en/link.js index 6f56aa768c..ccda70fa54 100644 --- a/microsite/pages/en/link.js +++ b/microsite/pages/en/link.js @@ -6,6 +6,7 @@ const redirects = { 'bind-routes': '/docs/plugins/composability#binding-external-routes-in-the-app', 'scm-auth': '/docs/auth/#scaffolder-configuration-software-templates', + 'backend-system': '/docs/plugins/new-backend-system' }; const fallback = '/docs'; From fa9bbcfbdbdf1d7dbde0017fe3dd4954c6d8512a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:50:16 +0200 Subject: [PATCH 06/13] docs/new-backend-systen: add status section Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 5ce1b1b81a..3e7f610593 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -4,11 +4,17 @@ title: New Backend System description: Details of the upcoming backend system --- -> **DISCLAMER: The new backend system is under active development and is not considered stable** +> **DISCLAIMER: The new backend system is under active development and is not considered stable** + +## Status + +The new backend system is under active development, and only a small number of plugins and services have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet. + +You can find an example backend setup at https://github.com/backstage/backstage/tree/master/packages/backend-next. ## Overview -This is an example of how you create, start and add existing plugins to your backend. +This is an example of how you create, add plugins, and start up your backend. ```ts import { createBackend } from '@backstage/backend-defaults'; From 69837a3302d96de723a85a0fe29c24101da826f3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 19:32:18 +0200 Subject: [PATCH 07/13] docs/new-backend-systen: add building blocks section Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 38 +++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 3e7f610593..db7a812ecd 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -12,7 +12,43 @@ The new backend system is under active development, and only a small number of p You can find an example backend setup at https://github.com/backstage/backstage/tree/master/packages/backend-next. -## Overview +## Building Blocks + +This section introduces the high-level building blocks upon which this new system is built. These are all concepts that exist in our current system in one way or another, but the have all been lifted up to be first class concerns in the new system. + +### Backend + +This is the backend instance itself, which you can think of at the unity of deployment. It does not have any functionality in itself, but is simply responsible for wiring things together. + +It is up to you to decide how many different backends you want to deploy. You can have all features in a single one, or split things out into multiple smaller deployments. All depending on your need to scale and isolate individual features. + +### Plugins + +Plugins provide the actual features, just like in our existing system. They operate completely independently of each other. If plugins what to communicate with each other, they must do so over the wire. There can be no direct communication between plugins through code. Because of this constraints, each plugins can be considered to be its own microservice. + +### Services + +Services provide utilities to help make it simpler to implement plugins, so that each plugin doesn't need to implement everything from scratch. There are both many built-in services, like the ones for logging, database access, and reading configuration, but you can also import third-party services, or create your own. + +Services are also a customization point for individual backend installations. You can both override services with your own implementations, as well as make smaller customizations to existing services. + +### Extension Points + +Many plugins have ways in which you can extend them, for example entity providers for the Catalog, or custom actions for the Scaffolder. These extension patterns are now encoded into Extension Points. + +Extension Points look a little bit like services, since you depended on them just like you would a service. A key difference is that extension points are registered and provided by plugins themselves, based on what customizations each individual plugin wants to expose. + +Extension Points are also exported separately from the plugin instance itself, and a single plugin can also expose multiple different extension points at once. This makes it easier to evolve and deprecated individual Extension Points over time, rather than dealing with a single large API surface. + +### Modules + +Modules use the plugin Extension Points to add new features for plugins. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. Modules basically plugins for plugins. + +Each module may only extend a single plugin, and the module must be deployed together with that plugin in the same backend instance. Modules may however only communicate with their plugin through its registered extension points. + +Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations. + +## API Overview This is an example of how you create, add plugins, and start up your backend. From 84df1e4d5ffce01a65399637cc0151f9f3256cc8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 19:51:33 +0200 Subject: [PATCH 08/13] docs/new-backend-systen: more reasoning in the overview section and move it back up to the top Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 40 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index db7a812ecd..d1306c3431 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -12,6 +12,28 @@ The new backend system is under active development, and only a small number of p You can find an example backend setup at https://github.com/backstage/backstage/tree/master/packages/backend-next. +## Overview + +The new Backstage backend system is being built to help make it simpler to install backend plugins and keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/TODO). + +One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { catalogPlugin } from '@backstage/plugin-catalog-backend'; + +// Create your backend instance +const backend = createBackend(); + +// Install all desired features +backend.add(catalogPlugin()); + +// Start up the backend +await backend.start(); +``` + +One notable change that helped achieve this much slimmer backend setup is the introduction of dependency injection, with a system that is very similar to the one in the Backstage frontend. + ## Building Blocks This section introduces the high-level building blocks upon which this new system is built. These are all concepts that exist in our current system in one way or another, but the have all been lifted up to be first class concerns in the new system. @@ -48,24 +70,6 @@ Each module may only extend a single plugin, and the module must be deployed tog Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations. -## API Overview - -This is an example of how you create, add plugins, and start up your backend. - -```ts -import { createBackend } from '@backstage/backend-defaults'; -import { catalogPlugin } from '@backstage/plugin-catalog-backend'; - -// Create your backend instance -const backend = createBackend(); - -// Install all desired features -backend.add(catalogPlugin()); - -// Start up the backend -await backend.start(); -``` - ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. From 2b54e0f780a32a4fabf961d9b809db73e7fa686a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 20:35:06 +0200 Subject: [PATCH 09/13] docs/new-backend-system: reorganize to prioritize plugin and module creation + tweaks Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 208 ++++++++++++++++------------- 1 file changed, 116 insertions(+), 92 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index d1306c3431..988086ff23 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -70,6 +70,122 @@ Each module may only extend a single plugin, and the module must be deployed tog Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations. +## Creating Plugins + +Plugins are created using the `createBackendPlugin` function. All plugins must have an ID and a register method. Plugins may also accept an options object, which can be either optional or required. The options are passed to the second parameter of the register method, and the options type is inferred and forwarded to the returned plugin factory function. + +```ts +import { + configServiceRef, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +// export type ExamplePluginOptions = { exampleOption: boolean }; +export const examplePlugin = createBackendPlugin({ + // unique id for the plugin + id: 'example', + // It's possible to provide options to the plugin + // register(env, options: ExamplePluginOptions) { + register(env) { + env.registerInit({ + deps: { + logger: loggerServiceRef, + }, + // logger is provided by the backend based on the dependency on loggerServiceRef above. + async init({ logger }) { + logger.info('Hello from example plugin'); + }, + }); + }, +}); +``` + +The plugin can then be installed in the backend using the returned plugin factory function: + +```ts +backend.add(examplePlugin()); +``` + +If we wanted our plugin to accept options as well, we'd accept the options as the second parameter of the register method: + +```ts +export const examplePlugin = createBackendPlugin({ + id: 'example', + register(env, options?: { silent?: boolean }) { + env.registerInit({ + deps: { logger: loggerServiceRef }, + async init({ logger }) { + if (!options?.silent) { + logger.info('Hello from example plugin'); + } + }, + }); + }, +}); +``` + +Passing the option to the plugin during installation looks like this: + +```ts +backend.add(examplePlugin({ silent: true })); +``` + +## Creating Modules + +Some facts about modules + +- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin. +- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. +- A module is always initialized before the plugin it extends. + +A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. + +Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: + +```ts +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { MyCustomProcessor } from './processor'; + +export const exampleCustomProcessorCatalogModule = createBackendModule({ + moduleId: 'exampleCustomProcessor', + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addProcessor(new MyCustomProcessor()); + }, + }); + }, +}); +``` + +### Extension Points + +Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. + +#### Defining an Extension Point + +```ts +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +export interface ScaffolderActionsExtensionPoint { + addAction(action: ScaffolderAction): void; +} + +export const scaffolderActionsExtensionPoint = + createExtensionPoint({ + id: 'scaffolder.actions', + }); +``` + +#### Registering an Extension Point + +Extension points are registered by a plugin and extended by modules. + ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. @@ -158,98 +274,6 @@ const backend = createBackend({ }); ``` -## Writing Plugins - -```ts -import { - configServiceRef, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; - -// export type ExamplePluginOptions = { exampleOption: boolean }; -export const examplePlugin = createBackendPlugin({ - // unique id for the plugin - id: 'example', - // It's possible to provide options to the plugin - // register(env, options: ExamplePluginOptions) { - register(env) { - env.registerInit({ - deps: { - logger: loggerServiceRef, - }, - // logger is provided by the backend based on the dependency on loggerServiceRef above. - async init({ logger }) { - logger.info('Hello from example plugin'); - }, - }); - }, -}); -``` - -The plugin can then be installed to the backend using - -```ts -backend.add(examplePlugin()); -// Options can be passed to the plugin -// backend.add(examplePlugin({ exampleOption: true})); -``` - -## Writing Modules - -Some facts about modules - -- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin. -- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. -- A module is always initialized before the plugin it extends. - -A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. - -Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: - -```ts -import { createBackendModule } from '@backstage/backend-plugin-api'; -import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; -import { MyCustomProcessor } from './processor'; - -export const exampleCustomProcessorCatalogModule = createBackendModule({ - moduleId: 'exampleCustomProcessor', - pluginId: 'catalog', - register(env) { - env.registerInit({ - deps: { - catalog: catalogProcessingExtensionPoint, - }, - async init({ catalog }) { - catalog.addProcessor(new MyCustomProcessor()); - }, - }); - }, -}); -``` - -### Extension Points - -Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. - -#### Defining an Extension Point - -```ts -import { createExtensionPoint } from '@backstage/backend-plugin-api'; - -export interface ScaffolderActionsExtensionPoint { - addAction(action: ScaffolderAction): void; -} - -export const ScaffolderActionsExtensionPoint = - createExtensionPoint({ - id: 'scaffolder.actions', - }); -``` - -#### Registering an Extension Point - -Extension points are registered by a plugin and extended by modules. - ## Testing Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. From 3449322ac2a653ba70fa7010ccb422c0d61a0e2f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:43:52 -0400 Subject: [PATCH 10/13] Apply suggestions from code review Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 988086ff23..4d688289c9 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -14,7 +14,7 @@ You can find an example backend setup at https://github.com/backstage/backstage/ ## Overview -The new Backstage backend system is being built to help make it simpler to install backend plugins and keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/TODO). +The new Backstage backend system is being built to help make it simpler to install backend plugins and keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611). One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system: @@ -40,7 +40,7 @@ This section introduces the high-level building blocks upon which this new syste ### Backend -This is the backend instance itself, which you can think of at the unity of deployment. It does not have any functionality in itself, but is simply responsible for wiring things together. +This is the backend instance itself, which you can think of as the unit of deployment. It does not have any functionality in itself, but is simply responsible for wiring things together. It is up to you to decide how many different backends you want to deploy. You can have all features in a single one, or split things out into multiple smaller deployments. All depending on your need to scale and isolate individual features. @@ -64,7 +64,7 @@ Extension Points are also exported separately from the plugin instance itself, a ### Modules -Modules use the plugin Extension Points to add new features for plugins. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. Modules basically plugins for plugins. +Modules use the plugin Extension Points to add new features for plugins. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. Modules are basically plugins for plugins. Each module may only extend a single plugin, and the module must be deployed together with that plugin in the same backend instance. Modules may however only communicate with their plugin through its registered extension points. @@ -138,7 +138,7 @@ Some facts about modules - A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. - A module is always initialized before the plugin it extends. -A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. +A module depends on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: @@ -165,7 +165,7 @@ export const exampleCustomProcessorCatalogModule = createBackendModule({ ### Extension Points -Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. +Modules depend on extension points just as a regular dependency by specifying it in the `deps` section. #### Defining an Extension Point @@ -189,7 +189,7 @@ Extension points are registered by a plugin and extended by modules. ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. -Services are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. +Services are declared using their _serviceRef_ in the `deps` section of the plugin or module requiring them and are then available in the `init` method of the plugin or module. ### Service References @@ -241,7 +241,7 @@ export const exampleServiceRef = createServiceRef({ ### Overriding Services -In this example replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service. +In this example we replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service. ```ts import { From 398df81469a9a4edd11546d247c68e14179465a6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:45:11 -0400 Subject: [PATCH 11/13] docs/new-backend-system: fix for plugin meta service being unused in example Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 4d688289c9..478f9b8609 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -232,7 +232,7 @@ export const exampleServiceRef = createServiceRef({ return async ({ plugin }) => { // This block will be executed once for every plugin that depends on this service logger.info('Initializing example service plugin instance'); - return new ExampleImpl({ logger }); + return new ExampleImpl({ logger, plugin }); }; }, }), From 7b90049030df19b1f76cae6148556be3787d2146 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:46:41 -0400 Subject: [PATCH 12/13] microsite/pages: prettify links page Signed-off-by: Patrik Oldsberg --- microsite/pages/en/link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/pages/en/link.js b/microsite/pages/en/link.js index ccda70fa54..fadb4f50d0 100644 --- a/microsite/pages/en/link.js +++ b/microsite/pages/en/link.js @@ -6,7 +6,7 @@ const redirects = { 'bind-routes': '/docs/plugins/composability#binding-external-routes-in-the-app', 'scm-auth': '/docs/auth/#scaffolder-configuration-software-templates', - 'backend-system': '/docs/plugins/new-backend-system' + 'backend-system': '/docs/plugins/new-backend-system', }; const fallback = '/docs'; From 3a9133e3ad6f292b51dd0f8f835b0204b7593f30 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:57:24 -0400 Subject: [PATCH 13/13] microsite/pages: vale tweak Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 478f9b8609..e5c318d5c2 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -189,7 +189,7 @@ Extension points are registered by a plugin and extended by modules. ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. -Services are declared using their _serviceRef_ in the `deps` section of the plugin or module requiring them and are then available in the `init` method of the plugin or module. +Service dependencies are declared using their `ServiceRef`s in the `deps` section of the plugin or module, and the implementations are then forwarded to the `init` method of the plugin or module. ### Service References