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

[RFC] Handler interface #36509

Merged
merged 6 commits into from
Jun 18, 2019
Merged

[RFC] Handler interface #36509

merged 6 commits into from
Jun 18, 2019

Conversation

epixa
Copy link
Contributor

@epixa epixa commented May 11, 2019

This is still a work in progress, but I wanted to post it since it overlaps with at least one other RFC that is in flight.

View Rendered RFC

[skip-ci]

@epixa epixa added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Feature:New Platform RFC labels May 11, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform

@epixa
Copy link
Contributor Author

epixa commented May 11, 2019

@joshdover Take a look at this. It's not completely done yet, but I think it overlaps with your application mounting RFC. Specifically, I think the mounting interface should function as a handler rather than us passing entire plugin contracts down into application business logic, as you mentioned here.

@joshdover joshdover marked this pull request as ready for review May 22, 2019 21:27
@joshdover
Copy link
Contributor

Gahh woops, I hit "Ready for Review" on the wrong PR.

rfcs/text/0003_handler_interface.md Outdated Show resolved Hide resolved
rfcs/text/0003_handler_interface.md Show resolved Hide resolved
rfcs/text/0003_handler_interface.md Outdated Show resolved Hide resolved
@joshdover joshdover mentioned this pull request May 28, 2019
@joshdover joshdover removed the WIP Work in progress label May 28, 2019
@joshdover
Copy link
Contributor

@elastic/kibana-platform This is now ready for review.

@lukeelmers
Copy link
Member

Handlers are asynchronous functions registered with core services invoked to respond to events like a HTTP request, or mounting an application.

Might just be me, but this RFC would be clearer with an additional example that uses handlers for application mounting or something besides HTTP requests.

As the "Drawbacks" section accurately points out, handlers as a pattern are commonly used for HTTP routes, but people might not necessarily associate them with other areas. A concrete example could help bridge that gap.

@eliperelman eliperelman self-requested a review May 30, 2019 18:08
@joshdover
Copy link
Contributor

@lukeelmers Additional examples have been added!

@lukeelmers
Copy link
Member

those are super helpful & much clearer - thanks!

@mshustov mshustov mentioned this pull request Jun 4, 2019
3 tasks
@joshdover joshdover added the non-issue Indicates to automation that a pull request should not appear in the release notes label Jun 5, 2019
@joshdover
Copy link
Contributor

@elastic/kibana-platform Still looking for approvals here. Once I have one approval, I'll put this RFC into the "final comment period" and will presume that it's good enough to accept.

http.router.route({
path: '/foo',
async routeHandler(context) {
context.elasticsearch.search(); // === callWithRequest(request, 'search')
Copy link
Contributor

Choose a reason for hiding this comment

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

How does context get created when the request argument is only known at the time of execution?

Like I'd think this route handler would have to be something like:

sync routeHandler(context, request) {
  context.getElasticsearch(request).search();
}

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 context gets created on a per-request basis, so in this example each route handler would technically be getting access to a different Elasticsearch client that is already pre-configured for the current request.

@joshdover We should make this super clear in the RFC since it's pretty much the most important detail of this whole proposal :)

@stacey-gammon
Copy link
Contributor

Looks nice! This is interesting from my perspective from dealing with actions and I'm wondering how it fits. If I'm following this all correctly, Handlers use context as well as optional additional arguments. The extra arguments are data that is only known at the time the handler is executed, whereas context is known and established at the time the plugins start up. Does that sound accurate?

My Actions are somewhat similar, but handlerContext type data would be passed into the constructor, and data that is only known at the time the action handler is executed, is passed in as a single triggerContext argument.

So it might look something like this:

// myaction.ts
class MyAction {
  constructor(context) {
    this.context = context;
  }

  execute(dynamicContext) {
    this.context.elasticsearch.search(dynamicContext);
  }
}

// myplugin.ts
setup({ core }) {
   const handlerContext = somehowCreateHandlerContext(core);
  actionRegistry.set(MY_ACTION_ID, new MyAction(handlerContext));
}

// somewhereelse.ts
// someone kicks off the action and passes in the data only known at that time
actionRegistry.get(MY_ACTION_ID).execute(dynamicContext);

So if actions want to use a similar Api it seems like the context should be passed in when it's executed... but then I'm still not sure how context gets created using the providers and the dynamic data.


```js
http.router.registerRequestContext('elasticsearch', async request => {
const client = await core.elasticsearch.client$.toPromise();
Copy link
Contributor

Choose a reason for hiding this comment

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

it's an implementation detail, but imo important don't forget to make context extension lazy (as a getter?) and execute this function every time when someone reads from context.[extension point] otherwise context may have stale version of the client.

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 think context needs to be generated immediately before invoking the handler. The idea is that context does not change once its corresponding handler begins executing, so even if the ES client were to update at the service level, a handler that has already started executing would continue to use the original client until it finishes executing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@joshdover We should call this out specifically in the RFC because it's an important detail.

@joshdover
Copy link
Contributor

@stacey-gammon I've coalesced answers to your questions here, but let me know if I missed anything.

The extra arguments are data that is only known at the time the handler is executed, whereas context is known and established at the time the plugins start up. Does that sound accurate?

No, context is created right before a handler is executed by calling each context provider and constructing an object from the return values. I've added a section to the RFC that explains how and when context is created.

how args fits in to both the provider and the handler function. Are they the same or totally unrelated?

The arguments available to context providers will differ by service. In the HTTP example, context providers would have access to request but not toolkit because context providers cannot intercept the execution of the handler. (This is not middleware).

The service that implements the Handler Context pattern will have to define which arguments are available to context providers and which are available to handlers themselves.

is the http.router one the only one that uses args?

Unlikely, I'll add some more fleshed out examples in the RFC itself, but one off the top of my head is for the ApplicationService's mount handler. Context providers could be provided access to the App definition that is about to be mounted in order to provided scoped services. For example, a context provider that returns a Telemetry Client that automatically prefixes telemetry keys with the app's id.

I'm not sure I'm sold on this pattern vs passing context into functions provided on the plugin contract. All examples I can think of seem a little more straightforward if you just use the plugin contracts. Like it's only one pattern I have to learn about.

I think one of the main benefits of using this pattern over plugin contracts is that this pattern does not require the developer to know all of the available APIs in every plugin in order to get a useful set of functionality in a handler.

Without handler context, I think many developers would re-implement the same logic over-and-over (possibly with bugs) rather than being able to leverage TypeScript/Intellisense to explore what's available in the context argument.

@epixa
Copy link
Contributor Author

epixa commented Jun 13, 2019

Another benefit of this pattern is that you don't need to pass around core and dependent services throughout all of your business logic. Contextual handlers allow for service capabilities to be injected directly where you need them.

With this proposal implemented, we won't even need to pass things like the saved object client to plugin lifecycle functions because you'll be able to access it directly from your handlers.

@joshdover joshdover added the RFC/final-comment-period If no concerns are raised in 3 business days this RFC will be accepted label Jun 13, 2019
@joshdover
Copy link
Contributor

This RFC is now in the final comment period. If you have any comments about a fundamental problem with this proposal, make them now. Otherwise this will be accepted on Monday, June 17th.

@joshdover joshdover added the release_note:skip Skip the PR/issue when compiling release notes label Jun 13, 2019
provider so neither service depends directly on the other.
- The existence of plugins that a given plugin does not depend on may leak
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this RFC is about a broad concept rather than a specific feature, I'm fine proceeding with this RFC as-is, but this detail is an important problem to address for implementations. Realistically, I think this means that core must provide plugin developers a mechanism to build a context since by nature a plugin won't even know about plugins that it does not depend on, so they can't properly construct the context themselves.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed. We may actually need to expose a service to plugins that allows them to offer context registration and construction. This service would handle the dependency management aspect of constructing context.

This needs to be built before any plugins begin offering context in their handlers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:New Platform non-issue Indicates to automation that a pull request should not appear in the release notes release_note:skip Skip the PR/issue when compiling release notes RFC/final-comment-period If no concerns are raised in 3 business days this RFC will be accepted RFC Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants