Replace PluginDatabaseClientFactory with PluginDatabaseManager

This commit is contained in:
Joel Low
2020-10-05 10:03:10 +08:00
parent 8c78b4e9dd
commit b083c97f84
13 changed files with 71 additions and 68 deletions
+1
View File
@@ -14,6 +14,7 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re
- The default mount point for backend plugins have been changed to `/api`. These changes are done in the backend package itself, so it is recommended that you sync up existing backend packages with this new pattern. [#2562](https://github.com/spotify/backstage/pull/2562)
- A service discovery mechanism for backend plugins has been added, and is now a requirement for several backend plugins. See [packages/backend/src/index.ts](./packages/backend/src/index.ts) for how to set it up using `SingleHostDiscovery` from `@backstage/backend-common`. Note that the default base path for plugins is set to `/api` to that change, but it can be set to use the old behavior via the `basePath` option. [#2600](https://github.com/spotify/backstage/pull/2600)
- A plugin database manager has been created, and plugins can now accept that interface as an argument during initialisation. Notably, the `auth` plugin has a [`createRouter` signature change](./plugins/auth-backend/src/service/router.ts). See [packages/backend/src/index.ts](./packages/backend/src/index.ts) on how to set it up. [#2697](https://github.com/spotify/backstage/pull/2697)
### @backstage/auth-backend
@@ -17,16 +17,28 @@
import Knex from 'knex';
import { Config } from '@backstage/config';
import { createDatabaseClient, ensureDatabaseExists } from './connection';
import { PluginDatabaseClientFactory } from './types';
import { PluginDatabaseManager } from './types';
/**
* Implements a Database Manager which will automatically create new databases for plugins when requested. All
* requested databases are created with the credentials provided.
* Implements a Database Manager which will automatically create new databases
* for plugins when requested. All requested databases are created with the
* credentials provided.
*/
export class SingleConnectionManager {
export class SingleConnectionDatabaseManager {
private readonly config: Config;
constructor(config: Config) {
/**
* Creates a new SingleConnectionDatabaseManager instance by reading from the `backend`
* config section, specifically the `.database` key for discovering the management
* database configuration.
*/
static fromConfig(config: Config): SingleConnectionDatabaseManager {
return new SingleConnectionDatabaseManager(
config.getConfig('backend.database'),
);
}
private constructor(config: Config) {
this.config = config;
}
@@ -39,17 +51,21 @@ export class SingleConnectionManager {
}
/**
* Generates a PluginDatabaseClientFactory for consumption by plugins.
* Generates a PluginDatabaseManager for consumption by plugins.
*/
getDatabaseClientFactory(pluginId: string): PluginDatabaseClientFactory {
return (database?: string): Promise<Knex> => {
return this.getDatabase(pluginId, database);
forPlugin(pluginId: string): PluginDatabaseManager {
const _this = this;
return {
getClient(database?: string): Promise<Knex> {
return _this.getDatabase(pluginId, database);
},
};
}
private async getDatabase(pluginId: string, suffix?: string): Promise<Knex> {
const config = this.getDatabaseConfig();
const overrides = SingleConnectionManager.getDatabaseOverrides(
const overrides = SingleConnectionDatabaseManager.getDatabaseOverrides(
pluginId,
suffix,
);
+13 -7
View File
@@ -17,11 +17,17 @@
import knex from 'knex';
/**
* The PluginDatabaseClientFactory is used to provide a mechanism for backend plugins to obtain database connections
* for itself.
*
* The purpose of this factory is to allow plugins to get isolated data stores so that plugins are discouraged from
* database integration. Plugins can omit the `database` parameter to get the default plugin database, or
* provide an identifier that will be used to identify a separate database from the default.
* The PluginDatabaseManager manages access to databases that Plugins get.
*/
export type PluginDatabaseClientFactory = (database?: string) => Promise<knex>;
export interface PluginDatabaseManager {
/**
* getClient provides backend plugins database connections for itself.
*
* The purpose of this method is to allow plugins to get isolated data
* stores so that plugins are discouraged from database integration. Plugins
* can omit the `database` parameter to get the default plugin database, or
* provide an identifier that will be used to identify a separate database
* from the default.
*/
getClient(database?: string): Promise<knex>;
}
+4 -6
View File
@@ -29,7 +29,7 @@ import {
getRootLogger,
useHotMemoize,
notFoundHandler,
SingleConnectionManager,
SingleConnectionDatabaseManager,
SingleHostDiscovery,
UrlReaders,
} from '@backstage/backend-common';
@@ -54,14 +54,12 @@ function makeCreateEnv(config: ConfigReader) {
root.info(`Created UrlReader ${reader}`);
const databaseManager = new SingleConnectionManager(
config.getConfig('backend.database'),
);
const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
return (plugin: string): PluginEnvironment => {
const logger = root.child({ type: 'plugin', plugin });
const databaseFactory = databaseManager.getDatabaseClientFactory(plugin);
return { logger, databaseClientFactory: databaseFactory, config, reader, discovery };
const database = databaseManager.forPlugin(plugin);
return { logger, database, config, reader, discovery };
};
}
+2 -7
View File
@@ -19,14 +19,9 @@ import { PluginEnvironment } from '../types';
export default async function createPlugin({
logger,
databaseClientFactory,
database,
config,
discovery,
}: PluginEnvironment) {
return await createRouter({
logger,
config,
database: databaseClientFactory,
discovery,
});
return await createRouter({ logger, config, database, discovery });
}
+4 -5
View File
@@ -30,14 +30,13 @@ export default async function createPlugin({
logger,
config,
reader,
databaseClientFactory,
database,
}: PluginEnvironment) {
const locationReader = new LocationReaders({ logger, reader, config });
const db = await DatabaseManager.createDatabase(
await databaseClientFactory(),
{ logger },
);
const db = await DatabaseManager.createDatabase(await database.getClient(), {
logger,
});
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
const locationsCatalog = new DatabaseLocationsCatalog(db);
const higherOrderOperation = new HigherOrderOperations(
+3 -3
View File
@@ -17,14 +17,14 @@
import { Logger } from 'winston';
import { Config } from '@backstage/config';
import {
PluginDatabaseClientFactory,
PluginDatabaseManager,
PluginEndpointDiscovery,
UrlReader
UrlReader,
} from '@backstage/backend-common';
export type PluginEnvironment = {
logger: Logger;
databaseClientFactory: PluginDatabaseClientFactory;
database: PluginDatabaseManager;
config: Config;
reader: UrlReader;
discovery: PluginEndpointDiscovery;
@@ -13,7 +13,7 @@ import {
getRootLogger,
useHotMemoize,
notFoundHandler,
SingleConnectionManager,
SingleConnectionDatabaseManager,
SingleHostDiscovery,
UrlReaders,
} from '@backstage/backend-common';
@@ -32,20 +32,12 @@ function makeCreateEnv(config: ConfigReader) {
root.info(`Created UrlReader ${reader}`);
const databaseManager = new SingleConnectionManager(
config.getConfig('backend.database')
);
const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
return (plugin: string): PluginEnvironment => {
const logger = root.child({ type: 'plugin', plugin });
const databaseFactory = databaseManager.getDatabaseClientFactory(plugin);
return {
logger,
databaseClientFactory: databaseFactory,
config,
reader,
discovery
};
const database = databaseManager.forPlugin(plugin);
return { logger, database, config, reader, discovery };
};
}
@@ -3,14 +3,9 @@ import { PluginEnvironment } from '../types';
export default async function createPlugin({
logger,
databaseClientFactory,
database,
config,
discovery,
}: PluginEnvironment) {
return await createRouter({
logger,
config,
database: databaseClientFactory,
discovery
});
return await createRouter({ logger, config, database, discovery });
}
@@ -14,12 +14,11 @@ export default async function createPlugin({
logger,
config,
reader,
databaseClientFactory,
database,
}: PluginEnvironment) {
const locationReader = new LocationReaders({ logger, reader, config });
const db = await DatabaseManager.createDatabase(
await databaseClientFactory(),
const db = await DatabaseManager.createDatabase(await database.getClient(),
{ logger },
);
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
@@ -1,14 +1,14 @@
import { Logger } from 'winston';
import { Config } from '@backstage/config';
import {
PluginDatabaseClientFactory,
PluginDatabaseManager,
PluginEndpointDiscovery,
UrlReader
UrlReader,
} from '@backstage/backend-common';
export type PluginEnvironment = {
logger: Logger;
databaseClientFactory: PluginDatabaseClientFactory;
database: PluginDatabaseManager;
config: Config;
reader: UrlReader
discovery: PluginEndpointDiscovery;
+3 -3
View File
@@ -24,12 +24,12 @@ import { DatabaseKeyStore, TokenFactory, createOidcRouter } from '../identity';
import {
NotFoundError,
PluginEndpointDiscovery,
PluginDatabaseClientFactory,
PluginDatabaseManager,
} from '@backstage/backend-common';
export interface RouterOptions {
logger: Logger;
database: PluginDatabaseClientFactory;
database: PluginDatabaseManager;
config: Config;
discovery: PluginEndpointDiscovery;
}
@@ -48,7 +48,7 @@ export async function createRouter({
const keyDurationSeconds = 3600;
const keyStore = await DatabaseKeyStore.create({
database: await database(),
database: await database.getClient(),
});
const tokenIssuer = new TokenFactory({
issuer: authUrl,
@@ -53,8 +53,10 @@ export async function startStandaloneServer(
const router = await createRouter({
logger,
config,
database: async () => {
return database;
database: {
async getClient() {
return database;
},
},
discovery,
});