Skip to content

Commit

Permalink
feat(core-api): plugin import types: LOCAL & REMOTE
Browse files Browse the repository at this point in the history
List of files with the interesting changes
=================================

The rest of the diff  is mostly just compiler worship.

./packages/cactus-core-api/src/main/typescript/openapi-spec.ts
./packages/cactus-core-api/src/main/typescript/plugin-factory-factory.ts
./packages/cactus-core-api/src/main/typescript/i-plugin-factory-options.ts
./packages/cactus-cmd-api-server/src/main/typescript/api-server.ts

Primary change
=============

 The original plugin import type that the API server received via configuration
 (via ENV, CLI or FILE).
 The `type` property of the plugin import can be used by the different
 `createPluginFactory()` implementations to determine the kind of factory
 they need to return. For example:

 1. For `LOCAL` type of plugin imports, the factory returned will construct
 the actual plugin implementation class (e.g. directly instantiate it)

 2. For `REMOTE` type of plugin imports, the factory returned will create an
 API client object, configured to point to an arbitrary implementation of
 the plugin over the network (which is how we enable/unlock the possibility
 to have language independent plugin implementations since by specifying
 `REMOTE` when importing a plugin, you can provide a network host where a
 plugin is deployed that was implemented in your preferred programming
 langugage rather than Typescript/Javascript for example.)

 Important note:
 When specifying `REMOTE` as the plugin import type, you still need to also
 specify the `packageName` property pointing to an npm package that has the
 API client class definition and the corresponding factory in it. The local
 implementation however does not have to be present in that npm package,
 which is the whole point.

 To provide a specific example of the above case: Writing a ledger connector
 plugin in a non-NodeJS language (Rust, Go, Java, etc.) can be done in the
 following way:
 1. Define the API client for your plugin in Typescript (only needed if
 not already defined for the ledger of your choice)
 We recommend auto-generating this after having written a platform/language
 netural OpenAPI spec file first. This API client doesn't provide any
 actual implementation of your plugin, it just maps method names of your
 plugin to HTTP requests that your actual plugin implementation (in your
 language of choice) will have to be able to service.
 2. Write the actual code of your plugin in your language of choice.
 3. Profit.

 Also note: If you are re-implementing a connector plugin in a different
 language that already has an implementation in NodeJS/Typescript, then
 you can skip the step above that has you publish the npm package with the
 API client class and really only have to work in your language of choice.

Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
  • Loading branch information
petermetz committed Jan 14, 2021
1 parent 734fab0 commit f4d51da
Show file tree
Hide file tree
Showing 22 changed files with 226 additions and 55 deletions.
23 changes: 13 additions & 10 deletions packages/cactus-cmd-api-server/src/main/typescript/api-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import bodyParser from "body-parser";
import cors from "cors";

import {
PluginFactory,
ICactusPlugin,
isIPluginWebService,
IPluginWebService,
IPluginFactoryOptions,
PluginFactoryFactory,
} from "@hyperledger/cactus-core-api";

import { PluginRegistry } from "@hyperledger/cactus-core";
Expand Down Expand Up @@ -163,23 +164,25 @@ export class ApiServer {

public async initPluginRegistry(): Promise<PluginRegistry> {
const registry = new PluginRegistry({ plugins: [] });
const { logLevel } = this.options.config;
const { logLevel, plugins } = this.options.config;
this.log.info(`Instantiated empty registry, invoking plugin factories...`);

for (const pluginImport of this.options.config.plugins) {
for (const pluginImport of plugins) {
const { packageName, options } = pluginImport;
this.log.info(`Creating plugin from package: ${packageName}`, options);
const pluginOptions = { ...options, logLevel, pluginRegistry: registry };

const {
createPluginFactory,
} = require(/* webpackIgnore: true */ packageName);
const pluginPackage = require(/* webpackIgnore: true */ packageName);
const createPluginFactory = pluginPackage.createPluginFactory as PluginFactoryFactory;

const pluginFactoryOptions: IPluginFactoryOptions = {
pluginImportType: pluginImport.type,
};

const pluginFactory = await createPluginFactory(pluginFactoryOptions);

const pluginFactory: PluginFactory<
ICactusPlugin,
any
> = await createPluginFactory();
const plugin = await pluginFactory.create(pluginOptions);

registry.add(plugin);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,15 @@ import {
} from "@hyperledger/cactus-common";
import { FORMAT_PLUGIN_ARRAY } from "./convict-plugin-array-format";
import { SelfSignedPkiGenerator, IPki } from "./self-signed-pki-generator";
import { Consortium, ConsortiumDatabase } from "@hyperledger/cactus-core-api";
import {
ConsortiumDatabase,
PluginImport,
PluginImportType,
} from "@hyperledger/cactus-core-api";

convict.addFormat(FORMAT_PLUGIN_ARRAY);
convict.addFormat(ipaddress);

export interface IPluginImport {
packageName: string;
options?: any;
}

export interface ICactusApiServerOptions {
configFile: string;
cactusNodeId: string;
Expand All @@ -45,7 +44,7 @@ export interface ICactusApiServerOptions {
apiTlsCertPem: string;
apiTlsKeyPem: string;
apiTlsClientCaPem: string;
plugins: IPluginImport[];
plugins: PluginImport[];
keyPairPem: string;
keychainSuffixKeyPairPem: string;
minNodeVersion: string;
Expand Down Expand Up @@ -450,13 +449,15 @@ export class ConfigService {
const pkiGenerator = new SelfSignedPkiGenerator();
const pkiServer: IPki = pkiGenerator.create("localhost");

const plugins = [
const plugins: PluginImport[] = [
{
packageName: "@hyperledger/cactus-plugin-keychain-memory",
type: PluginImportType.LOCAL,
options: {},
},
{
packageName: "@hyperledger/cactus-plugin-consortium-manual",
type: PluginImportType.LOCAL,
options: {
keyPairPem,
consortium,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export { launchApp } from "./cmd/cactus-api";

export {
ConfigService,
IPluginImport,
ICactusApiServerOptions,
} from "./config/config-service";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import test, { Test } from "tape-promise/tape";
import { v4 as uuidv4 } from "uuid";

import { LogLevelDesc } from "@hyperledger/cactus-common";
import { PluginRegistry } from "@hyperledger/cactus-core";

import { IPluginKeychainMemoryOptions } from "@hyperledger/cactus-plugin-keychain-memory";
import { PluginImportType } from "@hyperledger/cactus-core-api";

import { ApiServer, ConfigService } from "../../../main/typescript/public-api";
import { IPluginKeychainMemoryOptions } from "../../../../../cactus-plugin-keychain-memory/dist/types/main/typescript";

const logLevel: LogLevelDesc = "TRACE";

test("can import plugins at runtime (CLI)", async (t: Test) => {
const pluginRegistry = new PluginRegistry({ plugins: [] });

const configService = new ConfigService();
const apiServerOptions = configService.newExampleConfig();
apiServerOptions.configFile = "";
Expand All @@ -22,6 +21,7 @@ test("can import plugins at runtime (CLI)", async (t: Test) => {
apiServerOptions.plugins = [
{
packageName: "@hyperledger/cactus-plugin-keychain-memory",
type: PluginImportType.LOCAL,
options: {
instanceId: uuidv4(),
keychainId: uuidv4(),
Expand Down
28 changes: 28 additions & 0 deletions packages/cactus-core-api/src/main/json/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,34 @@
],
"components": {
"schemas": {
"PluginImport": {
"type": "object",
"required": [
"packageName",
"type"
],
"properties": {
"packageName": {
"type": "string",
"minLength": 1,
"maxLength": 1024,
"nullable": false
},
"type": {
"nullable": false,
"description": "",
"$ref": "#/components/schemas/PluginImportType"
},
"options": {}
}
},
"PluginImportType": {
"type": "string",
"enum": [
"org.hyperledger.cactus.plugin_import_type.LOCAL",
"org.hyperledger.cactus.plugin_import_type.REMOTE"
]
},
"ConsensusAlgorithmFamily": {
"type": "string",
"description": "Enumerates a list of consensus algorithm families in existence. Does not intend to be an exhaustive list, just a practical one, meaning that we only include items here that are relevant to Hyperledger Cactus in fulfilling its own duties. This can be extended later as more sophisticated features of Cactus get implemented. This enum is meant to be first and foremest a useful abstraction for achieving practical tasks, not an encyclopedia and therefore we ask of everyone that this to be extended only in ways that serve a practical purpose for the runtime behavior of Cactus or Cactus plugins in general. The bottom line is that we can accept this enum being not 100% accurate as long as it 100% satisfies what it was designed to do.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,41 @@ export enum LedgerType {
SAWTOOTH1X = 'SAWTOOTH_1X'
}

/**
*
* @export
* @interface PluginImport
*/
export interface PluginImport {
/**
*
* @type {string}
* @memberof PluginImport
*/
packageName: string;
/**
*
* @type {PluginImportType}
* @memberof PluginImport
*/
type: PluginImportType;
/**
*
* @type {any}
* @memberof PluginImport
*/
options?: any | null;
}
/**
*
* @export
* @enum {string}
*/
export enum PluginImportType {
LOCAL = 'org.hyperledger.cactus.plugin_import_type.LOCAL',
REMOTE = 'org.hyperledger.cactus.plugin_import_type.REMOTE'
}

/**
*
* @export
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { PluginImportType } from "./generated/openapi/typescript-axios/index";

/**
* The base interface for describing the object that is to be passed in to all
* the `createPluginFactory()` functions that all plugin packages must expose.
*
*/
export interface IPluginFactoryOptions {
/**
* The original plugin import type that the API server received via configuration
* (via ENV, CLI or FILE).
* The `type` property of the plugin import can be used by the different
* `createPluginFactory()` implementations to determine the kind of factory
* they need to return. For example:
*
* 1. For `LOCAL` type of plugin imports, the factory returned will construct
* the actual plugin implementation class (e.g. directly instantiate it)
*
* 2. For `REMOTE` type of plugin imports, the factory returned will create an
* API client object, configured to point to an arbitrary implementation of
* the plugin over the network (which is how we enable/unlock the possibility
* to have language independent plugin implementations since by specifying
* `REMOTE` when importing a plugin, you can provide a network host where a
* plugin is deployed that was implemented in your preferred programming
* langugage rather than Typescript/Javascript for example.)
*
* Important note:
* When specifying `REMOTE` as the plugin import type, you still need to also
* specify the `packageName` property pointing to an npm package that has the
* API client class definition and the corresponding factory in it. The local
* implementation however does not have to be present in that npm package,
* which is the whole point.
*
* To provide a specific example of the above case: Writing a ledger connector
* plugin in a non-NodeJS language (Rust, Go, Java, etc.) can be done in the
* following way:
* 1. Define the API client for your plugin in Typescript (only needed if
* not already defined for the ledger of your choice)
* We recommend auto-generating this after having written a platform/language
* netural OpenAPI spec file first. This API client doesn't provide any
* actual implementation of your plugin, it just maps method names of your
* plugin to HTTP requests that your actual plugin implementation (in your
* language of choice) will have to be able to service.
* 2. Write the actual code of your plugin in your language of choice.
* 3. Profit.
*
* Also note: If you are re-implementing a connector plugin in a different
* language that already has an implementation in NodeJS/Typescript, then
* you can skip the step above that has you publish the npm package with the
* API client class and really only have to work in your language of choice.
*/
pluginImportType: PluginImportType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IPluginFactoryOptions } from "./i-plugin-factory-options";
import { ICactusPlugin } from "./plugin/i-cactus-plugin";
import { PluginFactory } from "./plugin/plugin-factory";

/**
* This is the function that each plugin npm package must export under the name
* `"createPluginFactory"` so that the API server can perform the automatic
* wiring of the plugin once a plugin import has been placed in the configuration
* of it (API server) via either ENV, CLI or a config file.
*/
export type PluginFactoryFactory = (
pluginFactoryOptions: IPluginFactoryOptions
) => Promise<PluginFactory<ICactusPlugin, any, IPluginFactoryOptions>>;
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export abstract class PluginFactory<T, K> {
abstract async create(options: K): Promise<T>;
import { IPluginFactoryOptions } from "../i-plugin-factory-options";
export abstract class PluginFactory<T, K, C extends IPluginFactoryOptions> {
constructor(public readonly options: C) {}

abstract create(options: K): Promise<T>;
}
4 changes: 4 additions & 0 deletions packages/cactus-core-api/src/main/typescript/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export {
} from "./plugin/i-cactus-plugin";

export { PluginAspect } from "./plugin/plugin-aspect";

export { IPluginFactoryOptions } from "./i-plugin-factory-options";

export { PluginFactoryFactory } from "./plugin-factory-factory";
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { PluginFactory } from "@hyperledger/cactus-core-api";
import {
IPluginFactoryOptions,
PluginFactory,
} from "@hyperledger/cactus-core-api";
import {
IPluginConsortiumManualOptions,
PluginConsortiumManual,
} from "./plugin-consortium-manual";

export class PluginFactoryWebService extends PluginFactory<
PluginConsortiumManual,
IPluginConsortiumManualOptions
IPluginConsortiumManualOptions,
IPluginFactoryOptions
> {
async create(
options: IPluginConsortiumManualOptions
pluginOptions: IPluginConsortiumManualOptions
): Promise<PluginConsortiumManual> {
return new PluginConsortiumManual(options);
return new PluginConsortiumManual(pluginOptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ export * from "./generated/openapi/typescript-axios/index";

export { PluginFactoryWebService } from "./plugin-factory-consortium-manual";

import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api";
import { PluginFactoryWebService } from "./plugin-factory-consortium-manual";

export async function createPluginFactory(
options?: any
pluginFactoryOptions: IPluginFactoryOptions
): Promise<PluginFactoryWebService> {
return new PluginFactoryWebService();
return new PluginFactoryWebService(pluginFactoryOptions);
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { v4 as uuidv4 } from "uuid";

import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api";
import { PluginFactory } from "@hyperledger/cactus-core-api";

import {
IPluginKeychainMemoryOptions,
PluginKeychainMemory,
} from "./plugin-keychain-memory";

export class PluginFactoryKeychain extends PluginFactory<
PluginKeychainMemory,
IPluginKeychainMemoryOptions
IPluginKeychainMemoryOptions,
IPluginFactoryOptions
> {
async create(
options: IPluginKeychainMemoryOptions = {
pluginOptions: IPluginKeychainMemoryOptions = {
backend: new Map(),
instanceId: uuidv4(),
keychainId: uuidv4(),
logLevel: "TRACE",
}
): Promise<PluginKeychainMemory> {
return new PluginKeychainMemory(options);
return new PluginKeychainMemory(pluginOptions);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api";

export {
PluginKeychainMemory,
IPluginKeychainMemoryOptions,
Expand All @@ -7,7 +9,7 @@ export { PluginFactoryKeychain } from "./plugin-factory-keychain";
import { PluginFactoryKeychain } from "./plugin-factory-keychain";

export async function createPluginFactory(
options?: any
pluginFactoryOptions: IPluginFactoryOptions
): Promise<PluginFactoryKeychain> {
return new PluginFactoryKeychain();
return new PluginFactoryKeychain(pluginFactoryOptions);
}

0 comments on commit f4d51da

Please sign in to comment.