-
Notifications
You must be signed in to change notification settings - Fork 348
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
Plugins RFC #588
Plugins RFC #588
Conversation
|
||
### Commands | ||
|
||
The `commands` plugin property must be an object with the following |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like commands
should be an object or array of objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought of that, but then I thought if they wanted to implement multiple commands, they could just do them as subcommands. So instead of hz crom-input
and hz crom-output
they'd do hz crom input
and hz crom output
One piece of feedback: I think we should require people to specify a namespace for their plugin, and then namespace things like environment variables and endpoints and whatnot. (So if I was making a GraphQL plugin, I might register |
some_other_key: 'string', | ||
} | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should just use Joi
for this, since it's how we're doing schema validation elsewhere. We won't have to reimplement a bunch of stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea, I'll change it
@mlucy While I didn't mention an explicit namespacing scheme, the plugin system should verify at plugin activation time whether anything is in conflict. This would allow people to use nicer names for things. Essentially the idea is that the plugins are somewhat less paranoid about name conflicts since they declare everything they use. So there might be multiple plugins that provide a |
can include creating | ||
[subparsers](http://nodeca.github.io/argparse/#ArgumentParser.prototype.addSubparsers) | ||
- default is a no-op function | ||
- `processConfig`: a function that receives: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should omit processConfig
(and maybe simplify addArguments
to be more restrictive). I think we should try to force plugins to follow certain standards for what arguments they accept, specifically that all arguments can be specified with the same name in the config file, in environment variables, or on the command line (in increasing order of precedence). In practice every good plugin will end up doing that anyway, so why allow for bad plugins with confusing argument semantics like giving the config file precedence over the command line or using different names for the environment variable and the command line option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addArguments
is for specifying the command line syntax, whereas processConfig
runs after the command has been run and the options are parsed out successfully. I don't have a strong opinion, but it seems like environment variables may need more namespacing than config options or commands. Command options can be part of subcommands and the config has sections which are both like namespaces.
It'd be nice if we didn't enforce a namespacing scheme on the environment variables, or ensure that they have a specific precedence order, but maybe it's not a useful kind of flexibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, OK.
Personally I would:
- Only let plugins claim environment variables prefixed with their name (or a shortname or something). So an Elasticsearch plugin could register
ELASTICSEARCH_PORT
but not just plain oldPORT
. I think that would be clearer to users and less prone to conflicts. (And arguable more consistent since the config sections are acting basically like namespacing.) - Enforce the command line beats environment beats config ordering. I think it would be extremely confusing if different plugins were using different precedence schemes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These seem reasonable suggestions
Is the idea that Horizon will look in a certain directory on startup to figure out what plugins to load? Is it OK for different Horizon nodes to be running different sets of plugins? (Also, am I correct in assuming that plugins live on disk rather than inside RethinkDB?) |
@deontologician -- I'm more worried about e.g. a GraphQL plugin and a performance graphing plugin both providing a I think we should have namespacing for the same reason people who write packages for programming languages don't get to just dump their functions into the global namespace: if people put things into the global namespace, conflicts are literally inevitable once there are a certain number of plugins, and it's extremely frustrating to not be able to use two totally unrelated plugins together just because they happened to pick the same name for something. (You could argue that a responsible plugin writer would namespace their endpoints anyway, which is 100% true, but if that's what a responsible plugin writer would do, why not just force everyone to be responsible?) |
@mlucy in the case where two plugins pick the same name for an endpoint, the plugins should cooperate and pick different names. I think this kind of conflict will be pretty rare, and as long as we don't allow them both to run (by checking that endpoints don't overlap) it won't be a problem. It's much better to have clean looking paths and command names than to namespace everything in case two plugins happen to have a name clash. I'm not arguing a responsible plugin writer should namespace their endpoints, I think a responsible plugin writer should name the endpoint whatever is most natural, and if/when a conflict occurs, decide if the newer project should rename its endpoints or namespace them (and they'll probably always just rename them). Or they could decide the conflict is ok since usage of the two plugins should be mutually exclusive (like two graphQL plugins) |
Node modules have a field in their package.json that allows them to specify keywords. So we'll assume any modules currently installed that have the correct keyword are horizon plugins and try to load them. This is how another module project, Yeoman, detects plugins and it seems reasonable to me.
My gut feeling is no for simplicity's sake, but it might be worth allowing so people can add worker plugins only on certain servers, things like that.
yes, they'll live in the node_modules directory |
Just at first glance, I think this RFC is trying to squeeze too much under the "plugin" umbrella. There are more or less four basic ways one might want to extend the Horizon server:
I'm not sure it makes sense for a single "plugin" to be responsible for configuring all these things at once. Primus has done really well in this regard and is a pretty solid example for many of the goals in this RFC. Several core components in Primus are in fact implemented as plugins/middleware, which I think is along the same lines as how you guys are thinking about this. Notice how they have different mechanisms for hooking into the server (authorization, middleware, plugins, events) and each is tailored to a particular set of use-cases. Likewise, I don't think " |
We haven't totally fleshed out how plugins should be integrated if you're embedding the server, but I think it's reasonable to just have the cli aspect be a no-op in that case. It's kind of nice if you have a server plugin to be able to add related convenience commands to the cli (like create an example config), and it's probably not worth having multiple plugin types. If you're doing manual backend and a plugin only has a cli component, you just wouldn't use that plugin, so it's not a huge burden. The idea of linking them together is to make it clear they're intended to be significant fully featured extensions to Horizon, rather than disconnected addons. In addition, the plugin will be able to share its own state that is isolated from other plugins, so if several of these pieces need to work together, they can. For example, if a GraphQL plugin wanted to define both an http endpoint and a new Horizon request type it could share configuration and state between the two. |
@deontologician right, this makes sense but there are two cases to consider:
I think (1) is going to be the more common case and I wouldn't want to create a full plugin definition just to add a couple HTTP endpoints. What I'm getting at is that I would prefer a solution that provides specialized "addon" APIs for integrating into the server in various ways and then a "plugin definition" just exports some metadata and a handler function that receives a server instance to configure. Rough example: const plugin = function (server, options, next) {
// add HTTP endpoint
server.http.endpoint('/test', (req, res) => {
res.send(...);
});
// extend WS protocol
server.ws.command('test', ...);
// more configuration here...
next();
};
plugin.meta = {
name: 'my-plugin',
version: 'v0.0.1',
config: Joi.object() // schema for `options`, validated before handler is called
};
module.exports = plugin; |
I think fundamentally we should avoid any kind of free-form configuration like this. It's more flexible, but I think it's better to make it hard for plugins to step on each other's toes than it is to make them completely free to add any functionality to the server. Specifically, I think we should avoid the middleware paradigm with plugins, leaving middleware kinds of setups to users who are embedding horizon |
How about exposing a wrapper around the horizon server with limited functionality? (It'd be great if this wrapper was isomorphic to the horizon client, but I don't know if that's doable.) Also, as I noted in #373, I really think authentication and validation should be implemented through plugins hooking into onconnect and oninsert/onupdate events, respectively:
|
@deontologician I actually completely disagree. For one thing I think Node developers are used to this paradigm and it is adopted by virtually all popular server frameworks. More importantly, though, I think the flexibility it provides generally leads to a much richer plugin ecosystem. You can imagine plugins that simply provide additional abstractions for things like configuring validation or a plugin that would fully integrate PassportJS authentication. These kinds of things would be nearly impossible to do under a structured and isolated plugin interface. |
the name of the new requests, the values are an object with the | ||
following properties: | ||
- `optionsSchema`: A Joi schema to validate the request type. | ||
- `clientValidation`: An object with the keys: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure doing argument validation in the client is necessarily a good idea. We'll have to add a special parameter for every type of validation we want to let people do (as compared to validating on the server, where they can just write whatever JS code they want), and the only benefit is that it reduces the time to first error by like 50ms.
(Also, I would guess that people will be doing validation on the server anyway, especially if their semantics are at all complicated.)
I would at least strongly consider making these arguments optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's reasonable that they be optional, but these client side restrictions are just the "general shape" restrictions we enforce on ast methods currently.
How are we going to handle errors in plugins? Specifically:
|
the `config` plugin property. | ||
- `env`: an object with relevant environment variables (defined in | ||
[environment](#environment)) | ||
- `rdbConn`: a function that returns an open rethinkdb connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to call this multiple times (e.g. if we're briefly disconnected from RethinkDB and the connection they got the first time is invalidated)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in general a hard thing, since we don't provide resumable changefeeds. I'd say we should expect dispose() to be called before activate is called a second time
Open to suggestions on this one
This is interesting. It seems like it probably shouldn't be a plugin setting, but some general policy. Given that the plugin in question is buggy, it sort of implies the author of the plugin doesn't know where the bug is, so it's not clear whether it's ok to restart it or not. (Say it crashes before or after some system resource is allocated, it might or might not be a good idea to restart it) |
Ok, at this point, the RFC is pretty big, if you're going to comment, please do it in Reviewable, where I can keep track of things better. |
Review status: 0 of 1 files reviewed at latest revision, 15 unresolved discussions. rfcs/plugins.md, line 5 [r6] (raw file):
It might help to make the above more explicit in the RFC, as that was not entirely clear (particularly as there is currently no documentation on how one could go about writing a custom backend that does the things listed in this RFC). It would also be helpful, perhaps as a separate RFC, to articulate proposed changes to the backend API that will (a) be needed to enable the plugin functionality, and (b) make it easier to develop custom backend functionality (via embedding Horizon rather than via plugins). For example, will any hooks be added to the request-response cycle? Will it be possible to create or add to rulesets programmatically as well as incorporate custom validation methods? Right now there is the rfcs/plugins.md, line 16 [r6] (raw file):
It might help to explain a bit how plugins can be used to achieve some of the above, particularly things like custom authentication methods, server-side rendering, and scheduled tasks. Comments from Reviewable |
Review status: 0 of 1 files reviewed at latest revision, 15 unresolved discussions. rfcs/plugins.md, line 5 [r6] (raw file):
I agree, sorry for the confusion
I think we should do this once this plugin api is implemented, as well as propose a proper solution for middleware in embedded horizon ala #24, since it'll entail refactoring that @Tryneus will be in the best position to make decisions on as he's writing it. Once plugins are in, it'll be easier to do some minor changes to the details to create an api that will be nice to use. rfcs/plugins.md, line 16 [r6] (raw file):
|
I still don't think we've addressed logging or any mechanism for a plugin to declare it needs to be restarted (deactivated then reactivated) |
Another thing I just thought of is that plugins exposing |
@bopjesvla made some relevant comments on this proposal in #607 #607 (comment). Also, I think we should perhaps provide a |
@marshall007 What kind of credentials would be needed? I don't think we store anything other than "this user was authenticated". Would we need #216 ? |
@deontologician I just mean the credentials obtained from the OAuth handshake. Not the user account/profile info, but it's the same credentials you'd use to make that request too. |
oh whoops
What if I want to use the API-key-adding capabilities of one plugin and the location-adding capabilities of another? The plugin responsible for the behaviour is either the one named
in |
To be clear, this for the same document at the same insert. |
We also need to allow plugins to add an entry to |
- `config`: config options from the config.toml section for the | ||
plugin. This includes user overrides which are not specified in | ||
the `config` plugin property. | ||
- `metadata`: the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"the"
Review status: 0 of 1 files reviewed at latest revision, 17 unresolved discussions. a discussion (no related file):
Comments from Reviewable |
Review status: 0 of 1 files reviewed at latest revision, 17 unresolved discussions. rfcs/plugins.md, line 156 [r8] (raw file):
|
Rendered version of RFC
This change is