Skip to content

grpc services and @Transactional #41

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

Closed
jorgheymans opened this issue May 24, 2017 · 19 comments
Closed

grpc services and @Transactional #41

jorgheymans opened this issue May 24, 2017 · 19 comments

Comments

@jorgheymans
Copy link
Contributor

Hi,

It seems that spring's @transactional has no effect when used in a @GrpcService class. This is probably expected but nevertheless surprising. Integrating with spring's tx interceptor mechanics could make a worthy addition to this starter.

(and no i don't have a PR ready)

@jvmlet
Copy link
Collaborator

jvmlet commented May 24, 2017

Have you tried to autowire the transactional bean into grpc service?

@jorgheymans
Copy link
Contributor Author

jorgheymans commented May 25, 2017 via email

@jvmlet
Copy link
Collaborator

jvmlet commented May 26, 2017

As I understand, the service method is being invoked by grpc framework directly and spring has no idea about it, that's why it can't create transaction around the invocation... I think custom grpc interceptor is the way to apply the @transactional logic....

@jvmlet
Copy link
Collaborator

jvmlet commented May 27, 2017

Another thought : I was taught to always have clear separation of responsibility layers : transport, business and data access. Having @transactional being applied to transport method smells :-), at least for me. So, IMHO, you should continue to have facade to integrate with DB. What is your opinion?

@jorgheymans
Copy link
Contributor Author

yes the grpc interceptor would suffice for the basic case, and custom per-method transactional semantics are more easily expressed using the separate transactional facade.

About the layer responsibility, It's the accepted way in java to pretend a clean architecture :-) . I tend to agree but for the simple case layering just bogs things down.

@pagrus7
Copy link

pagrus7 commented Oct 8, 2017

As a Spring framework user I confirm the natural expectation of @transactional (and other Spring goodness) working for grpc services, just like it works with e.g. spring MVC controllers and apache CXF services.

@jvmlet
Copy link
Collaborator

jvmlet commented Oct 9, 2017

@pagrus7 , contributions are welcome.

@jorgheymans
Copy link
Contributor Author

jorgheymans commented Oct 9, 2017

@pagrus7 how do you see this working with streaming rpc's ? The tx interceptor will only work well for the standard "simple RPC" case, once the client starts streaming you can no longer assume the same model applies. In some cases you might want to start a transaction server side per streaming call, whereas others would want the whole stream to be in the same transaction. You would probably need to implement a custom annotation or attribute on @GrpcService to express all these semantics. Don't think this is a small feat :-)

@pagrus7
Copy link

pagrus7 commented Oct 9, 2017

@jorgheymans I agree that in streaming context the declarative transactions on GRPC service are unlikely to be useful. Can't think of a valid use case. Unary RPC calls though can get the most benefit from transactions, and there are tons of use cases for them.

In short, I see @transactional working exactly like it would elsewhere: transaction opens before the method execution and commits right after. It is up to developer to decide on the meaningful use with respect to the RPC pattern they choose.

With all that said, based on a few experiments I suspect that transaction management (and other proxy-based goodness) does work already, assuming the right configuration is in place. Stay tuned as I'm writing the next comment.

@pagrus7
Copy link

pagrus7 commented Oct 9, 2017

Here is my example which combines @transactional and @GRpcService. It configures spring for proxying target class rather than using dynamic proxies. spring.aop.proxy-target-class=true does the trick; @EnableTransactionManagement(proxyTargetClass=true) would also work. Then, when GRPC binds a service and passes this as a service impl, interceptors are still preserved.

Looks like this proxy mode will be default in Spring Boot 2.x.

I encourage others to try switching from jdk proxies to cglib and validating if this indeed is a solution.

Update: noticed too late that AOP with class proxies is already under test

@jorgheymans
Copy link
Contributor Author

@pagrus7 interesting thanks, so it seems that at most this 'feature' would need a small section in the documentation explaining things - great :)

@jvmlet
Copy link
Collaborator

jvmlet commented Oct 10, 2017

It's interesting to see that interceptors are invoked before the actual proxied service call, run the
DemoAppTestAop test :

2017-10-10 13:28:25.870  INFO 8236 --- [    Test worker] o.lognet.springboot.grpc.DemoAppTestAop  : Started DemoAppTestAop in 3.083 seconds (JVM running for 4.273)
Greeter/SayHello
2017-10-10 13:28:26.224  INFO 8236 --- [ault-executor-0] o.l.springboot.grpc.demo.LogInterceptor  : Greeter/SayHello
2017-10-10 13:28:26.235  INFO 8236 --- [ault-executor-0] o.l.s.grpc.demo.AopServiceMonitor        : Hi from AOP. - before
2017-10-10 13:28:26.254  INFO 8236 --- [ault-executor-0] o.l.springboot.grpc.demo.GreeterService  : Returning Hello John
2017-10-10 13:28:26.255  INFO 8236 --- [ault-executor-0] o.l.s.grpc.demo.AopServiceMonitor        : Hi from AOP. - after

@pagrus7
Copy link

pagrus7 commented Oct 10, 2017

@jvmlet while I'm very new to GRPC, it looks like the interceptors are intended for augmenting a chain of listeners and handlers, rather than executing the cross-cutting logic in-place.

E.g. the following implementation of a logging interceptor

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers,
                                                                 ServerCallHandler<ReqT, RespT> next) {

        ForwardingServerCall.SimpleForwardingServerCall loggingCall = new ForwardingServerCall.SimpleForwardingServerCall(call) {
            @Override
            public void sendMessage(Object message) {
                log.info("{} - responding with message - {} ", call.getMethodDescriptor().getFullMethodName(), message);
                super.sendMessage(message);
            }
        };

        ServerCall.Listener<ReqT> loggingListener = next.startCall(loggingCall, headers);
        SimpleForwardingServerCallListener loggingListenerWrapper = new SimpleForwardingServerCallListener(loggingListener) {
            @Override
            public void onMessage(Object message) {
                log.info("{} - received message - {} ", call.getMethodDescriptor().getFullMethodName(), message);
                super.onMessage(message);
            }

            @Override
            public void onComplete() {
                log.info("{} - call completed", call.getMethodDescriptor().getFullMethodName());
                super.onComplete();
            }
        };

        return loggingListenerWrapper;
    }

... produces the output which makes sense to me

2017-10-10 17:57:43.778  INFO 7924 --- [ault-executor-0] o.l.springboot.grpc.demo.LogInterceptor  : Greeter/SayHello - received message - name: "John"
 
2017-10-10 17:57:43.833  INFO 7924 --- [ault-executor-0] o.l.s.grpc.demo.AopServiceMonitor        : Hi from AOP. - before
2017-10-10 17:57:43.839  INFO 7924 --- [ault-executor-0] o.l.springboot.grpc.demo.LogInterceptor  : Greeter/SayHello - responding with message - message: "Hello John"
 
2017-10-10 17:57:43.843  INFO 7924 --- [ault-executor-0] o.l.springboot.grpc.demo.GreeterService  : Returning Hello John
2017-10-10 17:57:43.843  INFO 7924 --- [ault-executor-0] o.l.s.grpc.demo.AopServiceMonitor        : Hi from AOP. - after
2017-10-10 17:57:43.844  INFO 7924 --- [ault-executor-1] o.l.springboot.grpc.demo.LogInterceptor  : Greeter/SayHello - call completed

@jvmlet
Copy link
Collaborator

jvmlet commented Oct 10, 2017

I would expect the LogInterceptor inerceptor to be executed between AopServiceMonitor's @Before and @After pointcuts, not vise versa...

@pagrus7
Copy link

pagrus7 commented Oct 10, 2017

Added a one-liner PR for README.adoc which mentions class proxying.

@jvmlet
Copy link
Collaborator

jvmlet commented Oct 10, 2017

Still, if your interceptor requires transaction, it will not be available... Only the service method itself will be transactional...

@pagrus7
Copy link

pagrus7 commented Oct 10, 2017

@jvmlet I think this is "business as usual". If an interceptor needs a transaction, it can start one.
To me, if grpc interceptors started joining business transactions without invitation (since only service implementation is annotated), it would come as a surprise.

@jvmlet
Copy link
Collaborator

jvmlet commented Oct 10, 2017

@jorgheymans, what do you think? Should I close this issue?

@jorgheymans
Copy link
Contributor Author

jorgheymans commented Oct 10, 2017 via email

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

No branches or pull requests

3 participants