-
Notifications
You must be signed in to change notification settings - Fork 28
9.3.0 unstable.20251003 - Middleware Branch #310
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
base: main
Are you sure you want to change the base?
Conversation
middleware poc with compression middleware
opt in middleware
Logic mapping/reference:Constructor (lines 281-286, 315-317):
_request method:
request method: |
JoshMock
left a comment
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.
A few thoughts and questions on my first thorough pass through this PR:
- What is the mechanism for respecting the
TransportRequestOptionsobject that is passed to everyTransport.requestcall? TheMiddlewareContextobject includes it as itsoptionsvalue, so each middleware may need to checkctx.optionsevery time to decide whether it needs to do anything. - We need to add unit tests for each middleware to prove that we're able to test them each in isolation, which is a big perk of this refactor.
- Just based on the size of this change, we might want to consider releasing it in phases. Like, leave all the new middleware modules in here, but only automatically enable a couple of the simpler ones for the next minor release of the transport, just to validate our work in the wild.
And one little nit: we can remove Middleware from the filenames of each middleware, since it's already in the directory name.
| - Legacy + retries: 0.003ms (faster than baseline?) | ||
| - Middleware + retries: 0.005ms | ||
|
|
||
| This warrants investigation but may be measurement variance. |
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'd argue it might not be a significant concern if the difference is two thousandths of a millisecond.
src/middleware/MiddlewareEngine.ts
Outdated
| additionalArg?: TransportResult | Error | ||
| ): Promise<{ context: MiddlewareContext, error?: Error }> { | ||
| let currentContext = initialContext | ||
| const syncPhase = (phase + 'Sync') as keyof Middleware |
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.
Instead of adding Sync to the function name, we can detect a Promise at runtime:
let result = handler(currentContext)
if (result != null && typeof result.then === 'function') {
result = await result
}Simplifies the code a bit!
src/middleware/RetryMiddleware.ts
Outdated
| retryBackoff: (min: number, max: number, attempt: number) => number | ||
| } | ||
|
|
||
| export class RetryMiddleware implements Middleware { |
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 wonder if there's a way to encapsulate all this retry logic in the middleware so we can pull it out of Transport. Improves the readability of Transport.request and probably makes testing retry logic a lot easier, too.
src/middleware/MiddlewareEngine.ts
Outdated
| currentContext = this.mergeContext(currentContext, result.context) | ||
| } | ||
| } catch (error) { | ||
| console.warn(`Middleware ${middleware.name} failed in ${phase}:`, error) |
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.
We should drop the console.warn and just throw a MiddlewareException that uses Error.cause to wrap the original error.
For a refactor like this, I want our changes to fail as loudly as possible, to make sure we're catching any issues it introduces early, and to make debugging issues from users more straightforward. Instead of someone noticing compression suddenly stopped working, for example, they'll see they got a MiddlewareException, which will help both them and us to diagnose the underlying cause faster.
| } | ||
| } | ||
|
|
||
| private async _requestWithMiddleware (params: TransportRequestParams, options: TransportRequestOptions = {}): Promise<any> { |
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.
Rather than break this out into a totally separate function, I think we should add all the middleware handling to Transport.request directly. It will make the code longer and uglier in the short term, until we can confidently turn on middleware by default. Duplicating the code could make it easy to overlook that a bug in Transport.request might exist in more than one place.
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 I understand that you mean to suggest this:
async request(params, options) {
if (this[kUseMiddleware]) {
// inline all the middlware code
const meta = ...
middlewareContext = await this[kMiddlewareEngine].executePhase('onBeforeRequest', ...)
} else {
I understand the problem of handing bugs/fixes and needing to remember to modify both of the code paths -
But I also would like to argue in keeping the _requestWithMiddleware as a separate method not only for readability.
Once middleware becomes the default (after rollout), we can merge the methods and remove the old code path. This keeps the migration cleaner and makes it easier to AB test or to rollback.
Yes, middleware should check Eg.
|
Related PRS:
Related: elastic/elasticsearch-js#2902