This is Part 4 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 In the , we have laid the foundation to understand how the new Core infrastructure looks and getting an understanding of how it works. Today we’ll take a look at the arguably highest impact changes for package developers which make several improvements to the extensibility and testability of packages for Core. previous part No special treatment for plugins In Core 2.0 plugins were treated as a type of special entity inside the container. This is no longer the case in Core 3.0, everything that is bound to the container is treated the same and is just another binding. Plugins now expose themselves to Core through “service providers”. In part 3 we explained what they do and how they are executed but the basic gist of them is to be isolated entities with a single responsibility, provide information about a plugin and ensure that it is properly registered, booted and disposed to avoid unwanted side-effects like ghost processes or hanging database connections after Core has already been stopped. The Basics Most of the following has already been covered in Part 2 but lets quickly refresh our minds about service providers as they are at the heart of it all. Service Providers contain 3 methods that control how a plugin interacts with Core while it starts. Those methods are , and which take care of registering bindings with the container, booting any services that were registered and finally disposing of those services when the Core process terminated. register boot dispose Conditional Plugins A new feature that comes with the revamped plugin system of Core 3.0 is the ability to conditionally enable and disable plugins at runtime. Use-cases for this feature would be to enable or disable a plugin after a given height , require some other plugins to be installed or require that a node has forked to perform some recovery tasks. (similar to how milestones work) Let's have a look at an example service provider that implements the and methods and break it down into pieces. enableWhen disableWhen { Providers } ; ServiceProvider Providers.ServiceProvider { register(): < > { .app.log.warning( ); } boot(): < > { .app.log.warning( ); } dispose(): < > { .app.log.warning( ); } enableWhen(): < > { { data } = .app .resolve<StateService>( ) .getStore() .getLastBlock(); data.height % === ; } disableWhen(): < > { { data } = .app .resolve<StateService>( ) .getStore() .getLastBlock(); data.height % === ; } } import from "@arkecosystem/core-kernel" export class extends public async Promise void this "[kernel-dummy-plugin] REGISTER" public async Promise void this "[kernel-dummy-plugin] BOOT" public async Promise void this "[kernel-dummy-plugin] DISPOSE" public async Promise boolean const this "state" return 2 0 public async Promise boolean const this "state" return 3 0 When the application bootstrappers call we log a message. register When the application bootstrappers call we log a message. boot When the application bootstrappers call we log a message. dispose The plugin will be enabled when the height of the last received block is divisible by 2. The plugin will be enabled when the height of the last received block is divisible by 3. You might have noticed that there is a collision with values being divisible by both 2 and 3, like for example 6. This won’t cause any issues as the is only called if a plugin is not already booted, the same goes for the method which is only called if a plugin is booted. boot dispose A common use-case for conditional enabling and disabling could be a plugin that fixes the state of a node if it forks and once new blocks are received it disables itself again. Triggers & Hooks Triggers are a new feature that will make it a lot easier for forks and bridgechains to modify how certain things behave without ever having to touch Core implementations, all your modifications will be done through your plugins that run in combination with the official stock Core available via . @arkecosystem/core In simple terms, triggers are just functional bindings in the container that can be easily rebound by plugins to alter how Core performs specific tasks. An example of this would be how a block is validated, let's have a look at what that would look like and how you could alter this behavior with your own plugin. app .get<Services.Triggers.TriggerService>(Container.Identifiers.TriggerService) .bind( , { .log( ) }) .before( { .log( ) }) .error( { .log( ) }) .after( { .log( ) }); isValid: = app.get<Services.Triggers.TriggerService>(Container.Identifiers.TriggerService).call( , someBlock); "validateBlock" ( ) => data: Block console 'I will validate the block' => () console 'I run before a block is validated' => () console 'I run if block validation fails with an uncaught exception' => () console 'I run after a block is validated' const boolean await "validateBlock" We bind a trigger called that performs the validation of a block. validateBlock We register a hook that executes before the trigger we created via is executed.We register an hook that executes when there is an uncaught exception in the trigger we created via is executed. before bind error bind We register an hook that executes after the trigger we created via is executed. after bind This very basic example illustrates how we can take advantage of triggers and hooks. If you would want to use your own implementation of block validation, all you have to do is to register a trigger with the same name and it will be overwritten and used by Core. Since triggers are bound to the container like anything else in Core they are easy to work with in tests as you can simply bind them to a dummy container whenever you need to, without having to spin up a full application instance. Mixins A big flaw of inheritance in the world of OOP is that it is limited to one level. This means you cannot extend more than a single class, which can get messy quickly as you end up with large classes that have multiple responsibilities. Languages like PHP have something called which works around this issue by allowing you to move methods into entities with a single responsibility. Those traits are easily reusable and could be things like or which could be shared between entities like , , without having to duplicate the implementation. Traits HasComments HasReviews Movie Project Post TypeScript has something called mixins. They act somewhat like traits, with the major difference being that under the hood they extend an object and return that. The result of that is that rather than simply adding some methods, a completely new object is created that contains the old and new methods. Let's break down the following example to understand how mixins inside of Core work and the pros and cons that come with them. Block {} { Base { timestamp = ( ); }; } AnyFunction<T = > = T; Mixin<T AnyFunction> = InstanceType<ReturnType<T>>; TTimestamped = Mixin< Timestamped>; MixinBlock = TTimestamped & Block; app.mixins.set( , Timestamped); block: MixinBlock = (app.mixins.apply<MixinBlock>( , Block))(); expect(block.timestamp).toEqual( ( )); class < >( ) function Timestamped TBase extends Constructor Base: TBase return class extends new Date "2019-09-01" // Types type any ( ) => ...input: [] any type extends type typeof type // Example "timestamped" const new "timestamped" new Date "2019-09-01" We define a few types that will help TypeScript understand what methods the object that was created by the mixin contain. We register the function as a mixin with the name of timestamped. Timestamped We apply the mixin to the class to create a new variant of it that contains the property with the current date, instantiate a new block instance to make assertions on it. timestamped Block timestamp Mixins are a powerful tool to make use of composition over inheritance but they should be used with caution, like everything, or you’ll end up with the same issues that come with the excessive use of inheritance. What's Next? This concludes Part 4 of the ARK Core Adventures series. In the next part, we will delve into how ARK Core 3.0 has made several improvements to internal testing and what new testing tools have come out of those. I Want To Help With Development That's great news! If you want to help out, 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. our GitHub repositories To learn more about the program please read our blog post and to learn more about ARK Core and blockchain visit our . Bounty Program Guidelines Learning Hub Previously published at https://blog.ark.io/lets-explore-core-part-4-extensibility-c60522f1b700