Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SFX-99: Add Core module with registration mechanism #5

Merged
merged 62 commits into from
Jul 5, 2019
Merged

Conversation

jmbtutor
Copy link
Contributor

@jmbtutor jmbtutor commented Jul 3, 2019

Core is the backbone of the entire SF-X system. It manages plugins and allows them to communicate with each other. The core package defines interfaces for plugins.

A registration mechanism is currently implemented. Unregistration will be added separately.

jmbtutor and others added 30 commits July 3, 2019 10:47
Use the template from the dummy sayt-plugin package.
sinon-chai makes assertions using chai expect and sinon less awkward
Each plugin will expose a `register` function that takes in a plugins
object that contains a reference to all plugins and returns a value to
expose.
CoreUtils.getMissingDependencies() now takes a list of
plugins and the plugin registry. It uses this to
first determine the list of dependencies, and second
to determine which dependencies are not satisfied.

TODO: The newly supplied plugins can satisfy their
own dependencies, and this is not taken into account.

WIP: Comment out tests and update signature of getMissingDependencies()

WIP: Uncomment & fix one getMissingDependencies() test

WIP: Update plugin in getMissingDependencies() to use interface.

Fix remaining commented getMissingDependencies() test.
"Calculate" does not imply that the actual plugin is returned.
TypeScript can infer these types.
Use the arrow function form.
* another plugin. Accessing the plugins through Core outside of a
* plugin is discouraged.
*/
plugins: PluginRegistry = Object.create(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called registry instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it should, I think it makes it more clear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registry is better than plugins, as they need not be actual plugins, but rather the values exposed by each plugin.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If plugins has been renamed to registry, should the interface be renamed as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface name is already PluginRegistry. Since it will be used outside of Core, I think it can stay the way it is.

Copy link
Contributor

@JoeyDeol JoeyDeol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, just some comments to look over.

* 2. **Initialization:** Plugins perform setup tasks. Plugins are
* aware of the existence of other plugins, but should not make use
* of their functionality as they may not yet be initialized.
* 3. **Ready:** Plugins may make use of other plugins.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we explicitly mention that plugins are initialized in the ready phase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugins are initialized in the init phase, not the ready phase. The ready phase exists as a hook to say that the registration process is done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather I meant to say that we should mention that all plugins should have been initialized and their functionality is now available for use within the ready phase.

* another plugin. Accessing the plugins through Core outside of a
* plugin is discouraged.
*/
plugins: PluginRegistry = Object.create(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it should, I think it makes it more clear.

const pluginA = new PluginA();
const pluginB = new PluginB();

core.register([pluginA, pluginB]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just take an arbitrary number of plugins as arguments instead of a single array? This need not be done right away, but the interface would feel simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepting an array allows us to accept registration options that apply to the batch in the future. Looking ahead to a possible "provides" implementation, one such option may be to specify which plugin, out of multiple plugins that provide the same name, should be exposed under that name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably still accept options if we do some magic in the register() method. The plugins have to be actual plugins that conform to a specific interface, and the final argument could be an optional plain old object.

It's possible that this is considered not-cool in JavaScript, but it would still be an easier interface for our clients.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that if we do that, we would have no way to differentiate between a plugin and the options, especially if the options are optional (which it should be). In addition, I think the common case will be to register a number of plugins at the start, so an array is reasonable. I think I'll keep the array for those reasons.

* another plugin. Accessing the plugins through Core outside of a
* plugin is discouraged.
*/
plugins: PluginRegistry = Object.create(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registry is better than plugins, as they need not be actual plugins, but rather the values exposed by each plugin.

* the name of a plugin and its exposed value.
*/
export interface PluginRegistry {
[key: string]: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScript object keys are all strings. Do we need to specify a type here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little explanation would be nice. Why do we need the type? Do we not get to decide what we type and what we do not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the PluginRegistry makes it more explicit that a plugin registry is a dictionary, and allows us to change the type in the future if we so decide. It also provides the assertion that the keys can indeed by anything. Using the generic type object doesn't convey the intentions and is less flexible.

Having to specify string is required as a side effect of the syntax. This is what I meant by "unfortunately, yes".

const requiredSet = new Set(required);

const difference = new Set(Array.from(requiredSet).filter((p) => !availableSet.has(p)));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove some of the empty lines above.

it('should return a map of new plugin keys to exposed values', () => {
const valueA = { a: 'a' };
const valueB = { b: 'b' };
const valueC = { c: 'c' };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should have considered using varying datatypes for the values above. It makes it look like plugins have to expose objects, which is not true, and I find that distracts from the test.

Copy link
Contributor

@JoeyDeol JoeyDeol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All my feedback has been addressed, once the rest of the feedback has been resolved this should be ready to go.

]

const missing = calculateMissingDependencies(plugins, registry);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but it's not a convention we agreed upon. I was also not making the point for optimization, but rather to minimize noise and to generally make the file feel more readable and consistent with how we'd be coding elsewhere. I see no reason to blindly follow rules like Arrange Act Assert if they aren't really providing value. The tests would still be in the spirit of those guidelines.

const pluginA = new PluginA();
const pluginB = new PluginB();

core.register([pluginA, pluginB]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably still accept options if we do some magic in the register() method. The plugins have to be actual plugins that conform to a specific interface, and the final argument could be an optional plain old object.

It's possible that this is considered not-cool in JavaScript, but it would still be an easier interface for our clients.

* the name of a plugin and its exposed value.
*/
export interface PluginRegistry {
[key: string]: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little explanation would be nice. Why do we need the type? Do we not get to decide what we type and what we do not?

it('should export the PluginRegistry interface', () => {
// The following should cause a compiler error if the two interfaces are not equal.
const plugin_registry_is_assignable_to_plugin_registry_export: PluginRegistryExport = {} as PluginRegistry;
const plugin_registry_export_is_assignable_to_plugin_registry: PluginRegistry = {} as PluginRegistryExport;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this is a good use case for comments. This feels very noisy, and I found the variable names confused me more than anything.

All of that said, we're testing an index file here. The actual value in the tests feels limited to me, so I don't want to spend too much time on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I add a helper function to assert that two interfaces are equal, I think it'll make the test much clearer.

jmbtutor added 5 commits July 4, 2019 11:50
Plugins need not return objects, though that is probably the most common type.
The AssertTypesEqual asserts equality by ensuring that the two types are
subtypes of each other.
@jmbtutor jmbtutor merged commit 367de56 into master Jul 5, 2019
@jmbtutor jmbtutor deleted the SFX-99 branch July 5, 2019 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants