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.
ARK Core v3.0 Github Repository
Before starting we should set the stage by defining what a service inside of Core is. In simple terms, a service is a self-contained feature that only exposes a small chunk of the total business logic that makes up Core. For example, a log service, cache service, database service, transaction service, etc. — all of those are important building blocks of Core and should be easy to modify, maintain and test.
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.We’ll go more in-depth into those services in a later part of this series.
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;
}
}
This is quite a bit of code so let's break it down into digestible parts.
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();
}
}
As you can see it’s pretty easy to register your own services without much hassle and everything is clearly named, let's end with listing some of the benefits of this new architecture compared to Core 2.0.
Clear separation of responsibilities during application bootstrap.Easy testing due to a clear separation of responsibilities. Ability to enable and disable packages at runtime without a full teardown.
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.
This concludes Part 3 of the Let's Explore ARK Core series. In the next part, we will delve into how ARK Core 3.0 is more extensible than ever and how you can take advantage of this to reduce your time spent on developing packages.
That's great news! If you want to help out, our GitHub repositories are wide open, but that is not all, we also have special Monthly Development GitHub Bounties on-going where you can earn money for every valid and merged Pull-Request.
To learn more about the program please read our Bounty Program Guidelines blog post and to learn more about ARK Core and blockchain visit our Learning Hub.