This is Part 2 of 6 in the Let’s Explore ARK Core series which documents the development of the next major release of ARK Core, alongside some tips & tricks on how to get started with contributing and building your next idea today.
Introduction
ARK Core v3.0 Github Repository
Bootstrapping
Testing was very difficult as those things were hardcoded in the depths of the application object with no way of easily accessing them. All logic was centralized in a single entity which meant that every small change could break anything and disabling certain steps for testing was not possible.
Available Bootstrappers
// Application
class RegisterErrorHandler implements Bootstrapper
class RegisterBaseConfiguration implements Bootstrapper
class RegisterBaseServiceProviders implements Bootstrapper
class RegisterBaseBindings implements Bootstrapper
class RegisterBaseNamespace implements Bootstrapper
class RegisterBasePaths implements Bootstrapper
class LoadEnvironmentVariables implements Bootstrapper
class LoadConfiguration implements Bootstrapper
class LoadCryptography implements Bootstrapper
class WatchConfiguration implements Bootstrapper
// Service Providers
class LoadServiceProviders implements Bootstrapper
class RegisterServiceProviders implements Bootstrapper
class BootServiceProviders implements Bootstrapper
Application Start
Bootstrap
- The event dispatcher is registered as the first service as it is needed early on in the application lifecycle.
- The initial configuration and CLI flags are stored in the container to make them easily accessible during the lifecycle of the application.
- The ServiceProviderRepository is registered which is a repository that holds the state of registered services through plugins. Those states are registered, loaded, failed and deferred; more on those in a later part.
public async bootstrap(config: JsonObject): Promise<void> {
await this.registerEventDispatcher();
this.container
.bind<JsonObject>(Identifiers.ConfigBootstrap)
.toConstantValue(config);
this.container
.bind<ServiceProviderRepository>(Identifiers.ServiceProviderRepository)
.to(ServiceProviderRepository)
.inSingletonScope();
await this.bootstrapWith("app");
}
public async boot(): Promise<void> {
await this.bootstrapWith("serviceProviders");
this.booted = true;
}
private async bootstrapWith(type: string): Promise<void> {
const bootstrappers: Array<Constructor<Bootstrapper>> = Object.values(Bootstrappers[type]);
for (const bootstrapper of bootstrappers) {
this.events.dispatch(`bootstrapping:${bootstrapper.name}`, this);
await this.container.resolve<Bootstrapper>(bootstrapper).bootstrap();
this.events.dispatch(`bootstrapped:${bootstrapper.name}`, this);
}
}
- We get a key-value pair of available bootstrappers, currently, only app and serviceProviders exist.
- We loop over all available bootstrappers.
- We fire a
event before executing the bootstrapper. This will look something likebootstrapping:{name}
.bootstrapping:LoadEnvironmentVariables
- We resolve the bootstrapper class from the container and call the bootstrap method to execute its task.
- We fire a
event after executing the bootstrapper. This will look something likebootstrapped:{name}
.bootstrapped:LoadEnvironmentVariables
core-kernel
. This contract needs to be satisfied by all event dispatcher implementations, core ships with an in-memory solution by default but something like Redis should be easy enough to implement as an alternative.export interface EventDispatcher {
/**
* Register a listener with the dispatcher.
*/
listen(event: EventName, listener: EventListener): () => void;
/**
* Register many listeners with the dispatcher.
*/
listenMany(events: Array<[EventName, EventListener]>): Map<EventName, () => void>;
/**
* Register a one-time listener with the dispatcher.
*/
listenOnce(name: EventName, listener: EventListener): void;
/**
* Remove a listener from the dispatcher.
*/
forget(event: EventName, listener?: EventListener): void;
/**
* Remove many listeners from the dispatcher.
*/
forgetMany(events: Array<[EventName, EventListener]>): void;
/**
* Remove all listeners from the dispatcher.
*/
flush(): void;
/**
* Get all of the listeners for a given event name.
*/
getListeners(event: EventName): EventListener[];
/**
* Determine if a given event has listeners.
*/
hasListeners(event: EventName): boolean;
/**
* Fire an event and call the listeners in asynchronous order.
*/
dispatch<T = any>(event: EventName, data?: T): Promise<void>;
/**
* Fire an event and call the listeners in sequential order.
*/
dispatchSeq<T = any>(event: EventName, data?: T): Promise<void>;
/**
* Fire an event and call the listeners in synchronous order.
*/
dispatchSync<T = any>(event: EventName, data?: T): void;
/**
* Fire many events and call the listeners in asynchronous order.
*/
dispatchMany<T = any>(events: Array<[EventName, T]>): Promise<void>;
/**
* Fire many events and call the listeners in sequential order.
*/
dispatchManySeq<T = any>(events: Array<[EventName, T]>): Promise<void>;
/**
* Fire many events and call the listeners in synchronous order.
*/
dispatchManySync<T = any>(events: Array<[EventName, T]>): void;
}
core-p2p
to decouple certain tasks like banning and disconnecting a peer. Previously, tasks of that nature were just thrown in wherever they fit best at the time rather than being placed in an entity where they actually belong to.What’s Next?
I Want To Help With Development
To learn more about the program please read our Bounty Program Guidelines blog post.