visit
In the Part 1 and Part 2 of this series, we gave a rough overview of how the Core infrastructure and application bootstrapping process was revamped. Today we’ll learn what services inside of Core are, why they were introduced and what their job is to keep Core running.
Services aren’t actually all new in Core 3.0 but rather a rework of how certain plugins operated in Core 2.0. Core 2.0 had one major issue that was the result of the late container and plugin system introduction, it was so heavily fragmented to the point where required plugins for functionality like loggers existed. You would always be required to include those plugins when bundling Core instead of them being part of a single package that you require in your own packages and have everything ready.
In Core 3.0 we introduced a new package called
core-kernel
which is the amalgamation of various packages that have previously existed in Core 2.0. Examples of those would be core-container
, core-logger
, core-event-emitter
and more.The
core-kernel
package is the heart of Core 3.0 with the goal of resolving a lot of pain points from previous versions, improve DX, reducing boilerplate for package developers and lastly to reduce the fragmentation that plagues Core 2.0.Part of
core-kernel
is the services
directory which comes with a variety of services out of the box to reduce the boilerplate needed for the development of new packages and reduce duplication in existing ones.Registering your own services is as easy as can be and only takes a few lines of code due to the abstractions that come out of the box for package developers. Lets first have a look at the abstract ServiceProvider that comes with Core to reduce the necessary boilerplate for service registrations.
@injectable()
export abstract class ServiceProvider {
/**
* The application instance.
*/
@inject(Identifiers.Application)
protected readonly app: Kernel.Application;
/**
* The application instance.
*/
private packageConfiguration: PackageConfiguration;
/**
* The loaded manifest.
*/
private packageManifest: PackageManifest;
/**
* Register the service provider.
*/
public abstract async register(): Promise<void>;
/**
* Boot the service provider.
*/
public async boot(): Promise<void> {
//
}
/**
* Dispose the service provider.
*/
public async dispose(): Promise<void> {
//
}
/**
* Get the manifest of the service provider.
*/
public manifest(): PackageManifest {
return this.packageManifest;
}
/**
* Set the manifest of the service provider.
*/
public setManifest(manifest: PackageManifest): void {
this.packageManifest = manifest;
}
/**
* Get the name of the service provider.
*/
public name(): string | undefined {
if (this.packageManifest) {
return this.packageManifest.get("name");
}
return undefined;
}
/**
* Get the version of the service provider.
*
* @returns {string}
* @memberof ServiceProvider
*/
public version(): string | undefined {
if (this.packageManifest) {
return this.packageManifest.get("version");
}
return undefined;
}
/**
* Get the configuration of the service provider.
*/
public config(): PackageConfiguration {
return this.packageConfiguration;
}
/**
* Set the configuration of the service provider.
*/
public setConfig(config: PackageConfiguration): void {
this.packageConfiguration = config;
}
/**
* Get the configuration defaults of the service provider.
*/
public configDefaults(): JsonObject {
return {};
}
/**
* Get the configuration schema of the service provider.
*/
public configSchema(): object {
return {};
}
/**
* Get the dependencies of the service provider.
*/
public dependencies(): Kernel.PackageDependency[] {
return [];
}
/**
* Enable the service provider when the given conditions are met.
*/
public async enableWhen(): Promise<boolean> {
return true;
}
/**
* Disable the service provider when the given conditions are met.
*/
public async disableWhen(): Promise<boolean> {
return false;
}
/**
* Determine if the package is required, which influences how bootstrapping errors are handled.
*/
public async required(): Promise<boolean> {
return false;
}
}
That’s the functionality a service provider comes with out of the box
but the only methods you’ll interact with in most cases are register, boot and dispose. Let us take a look at an example service provider to illustrate their use.
import { Providers } from "@arkecosystem/core-kernel";
import { Server } from "@hapi/hapi";
export class ServiceProvider extends Providers.ServiceProvider {
public async register(): Promise<void> {
this.app.bind<Server>("api").toConstantValue(new Server());
}
public async boot(): Promise<void> {
await this.app.get<Server>("api").start();
}
public async dispose(): Promise<void> {
await this.app.get<Server>("api").stop();
}
}
The by far biggest benefit for package developers is that it is now possible to alter or extend other packages due to how the application bootstrapping now works. An example of this would be a plugin that adds new routes or plugins to the
core-api
package before the server is started, all it would take is to resolve the hapi.js server from the container in the register method and call the usual methods provided by hapi.js on the resolved value.This removes the need to spin up your own HTTP server if you just need 1–2 extra API endpoints. Being able to do modify other plugins before they are launched will provide developers with greater control and possibilities to modify how core behaves.To learn more about the program please read our blog post and to learn more about ARK Core and blockchain visit our .