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

Add operation interceptors #14

Merged
merged 5 commits into from
Dec 5, 2024

Conversation

Quinn-With-Two-Ns
Copy link
Contributor

@Quinn-With-Two-Ns Quinn-With-Two-Ns commented Oct 17, 2024

Based of the internal proposed interface. See the test case for two example middlewares that are possible with this interface

@Quinn-With-Two-Ns
Copy link
Contributor Author

Here is an example of how these interceptors would be used temporalio/sdk-java#2278

@Quinn-With-Two-Ns Quinn-With-Two-Ns marked this pull request as ready for review November 25, 2024 00:36
public interface OperationMiddleware {

/** Intercepts the given operation. Called once for each operation invocation. */
OperationHandler<Object, Object> intercept(OperationHandler<Object, Object> next);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cretz I know one of your desires was to allow the interceptor to switch on different operations, right now that is possible inside the wrapped OperationHandler , we could also add a OperationContext as the first parameter here.

Copy link
Contributor

@cretz cretz Nov 25, 2024

Choose a reason for hiding this comment

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

Yes, but I also wonder if that's enough for future proofing if we wanted to add something else. Maybe a OperationMiddlewareInterceptContext that contains the operation context and maybe even this next handler (though I'd be ok if they remained separate parameters). Or could just be an inner class of Context here. Also, I wonder if people should be allowed to alter the input information passed to the next middleware similar to how one might in Temporal interceptors today.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

wonder if people should be allowed to alter the input information passed to the next middleware similar to how one might in Temporal interceptors today.

That is already possible with this implementation in the exact same way

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you address the earlier part of the paragraph about adding some kind of contextual object even if it's empty or mostly empty today? As for altering the values, so long as nobody expects to alter the operation name itself, agreed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but I also wonder if that's enough for future proofing if we wanted to add something else.

I think if we want to add anything that is common across all operation methods it would go in the OperationContext , if it specific to a certain method like start then the wrapped handler should deal with 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.

@bergundy if you have any thoughts here

Copy link

Choose a reason for hiding this comment

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

I agree we can supply some context here. It would contain the service name, operation name, and request header. The rest of the information is method specific, and IMHO, should be intercepted by the wrapped handler.

Copy link

Choose a reason for hiding this comment

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

The obvious downside for instantiating these handlers per request is the extra garbage generated but I'm not too concerned with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK added context as a first parameter

@@ -53,7 +53,7 @@ import com.vanniktech.maven.publish.SonatypeHost
mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)

signAllPublications()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: need to remove this before merging, this line just makes local development impossible

@@ -87,7 +103,7 @@ public HandlerResultContent fetchOperationResult(
if (handler == null) {
throw newUnrecognizedOperationException(context.getService(), context.getOperation());
}
Object result = handler.fetchResult(context, details);
Object result = getOperationHandler(handler).fetchResult(context, details);
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 we did https://github.com/nexus-rpc/sdk-java/pull/14/files#r1855645828 we could also consider switching where we call getOperationHandler to be before the data coversion. This would allow to fail requests before the data converter runs.

Copy link

Choose a reason for hiding this comment

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

Yeah, that'd be great.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, this only applied to start

Copy link
Contributor

@cretz cretz left a comment

Choose a reason for hiding this comment

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

LGTM only minor things

public interface OperationMiddleware {

/** Intercepts the given operation. Called once for each operation invocation. */
OperationHandler<Object, Object> intercept(OperationHandler<Object, Object> next);
Copy link
Contributor

@cretz cretz Nov 25, 2024

Choose a reason for hiding this comment

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

Yes, but I also wonder if that's enough for future proofing if we wanted to add something else. Maybe a OperationMiddlewareInterceptContext that contains the operation context and maybe even this next handler (though I'd be ok if they remained separate parameters). Or could just be an inner class of Context here. Also, I wonder if people should be allowed to alter the input information passed to the next middleware similar to how one might in Temporal interceptors today.

public interface OperationMiddleware {

/** Intercepts the given operation. Called once for each operation invocation. */
OperationHandler<Object, Object> intercept(OperationHandler<Object, Object> next);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
OperationHandler<Object, Object> intercept(OperationHandler<Object, Object> next);
OperationHandler<Object, Object> apply(OperationHandler<Object, Object> next);

I wonder if we don't need to call this "intercept" or these "interceptors" if we're adopting middleware-ish terminology. But I don't mind intercept either.

Copy link

Choose a reason for hiding this comment

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

Maybe wrap? But intercept also works for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Left as intercept for now

Copy link

@bergundy bergundy left a comment

Choose a reason for hiding this comment

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

Agree we should add the context and allow intercepting before deserializing the input.

public interface OperationMiddleware {

/** Intercepts the given operation. Called once for each operation invocation. */
OperationHandler<Object, Object> intercept(OperationHandler<Object, Object> next);
Copy link

Choose a reason for hiding this comment

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

I agree we can supply some context here. It would contain the service name, operation name, and request header. The rest of the information is method specific, and IMHO, should be intercepted by the wrapped handler.

public interface OperationMiddleware {

/** Intercepts the given operation. Called once for each operation invocation. */
OperationHandler<Object, Object> intercept(OperationHandler<Object, Object> next);
Copy link

Choose a reason for hiding this comment

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

Maybe wrap? But intercept also works for me.

public interface OperationMiddleware {

/** Intercepts the given operation. Called once for each operation invocation. */
OperationHandler<Object, Object> intercept(OperationHandler<Object, Object> next);
Copy link

Choose a reason for hiding this comment

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

The obvious downside for instantiating these handlers per request is the extra garbage generated but I'm not too concerned with that.

@@ -87,7 +103,7 @@ public HandlerResultContent fetchOperationResult(
if (handler == null) {
throw newUnrecognizedOperationException(context.getService(), context.getOperation());
}
Object result = handler.fetchResult(context, details);
Object result = getOperationHandler(handler).fetchResult(context, details);
Copy link

Choose a reason for hiding this comment

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

Yeah, that'd be great.

Copy link
Contributor

@cretz cretz left a comment

Choose a reason for hiding this comment

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

LGTM, only minor things

@Quinn-With-Two-Ns Quinn-With-Two-Ns merged commit c9bbb1a into nexus-rpc:main Dec 5, 2024
1 check passed
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.

3 participants