Skip to content

Make adding a new RPC to a service non-breaking for generated C##5928

Merged
jtattermusch merged 20 commits intogrpc:masterfrom
jtattermusch:csharp_clientside_abstractclass
Apr 5, 2016
Merged

Make adding a new RPC to a service non-breaking for generated C##5928
jtattermusch merged 20 commits intogrpc:masterfrom
jtattermusch:csharp_clientside_abstractclass

Conversation

@jtattermusch
Copy link
Copy Markdown
Contributor

Based on #5751.

The intention is to fix #5416, but also fixes #5323 along the way and addresses some other concerns.

Note: below I am talking about "MathClient" for simplicity, but everything is meant in general for all client stubs.

As suggested in #5416, I initially wanted to generate two classes - the "AbstractMathClient" and "MathClient", but that ended up not making much sense: It seems reasonable that client stubs would inherit from a base class (here ClientBase) to be able to establish some common ground for all client classes (and to be able to add some general functionality to all clients in the future). But with that logic, both "AbstractMathClient" and "MathClient" would need to inherit from the same class (and define the right constructors), which in the end would not make "AbstractMathClient" any more lightweight (for testing etc.) than "MathClient". So currently there's only one class "MathClient" that can be used in all situations - as a real client and for creating test doubles, if needed.

Having only a single "MathClient" class, I needed to get rid of the tight coupling between MathClient and a "real" Channel. I achieved that by introducing a concept of "CallInvoker" - an abstract class that allows invoking calls (DefaultCallInvoker is the default implementation that starts calls using a real Channel).
When defining custom test doubles (by using a mocking framework or by manually inheriting from MathClient), the CallInvoker concept is used to make all invocations just throw NotImplementedException (which basically gives us the functionality we would normally expect from AbstractMathClient).

The CallInvoker concept also allows intercepting of the calls - I used the opportunity and refactored the ClientBase a bit to allow a cleaner (and thread safe) approach to override a "host" for the client. Same approach can be used to intercept the fields in CallOptions (e.g. to provide a per-client default timeout or a header interceptor). See the "ClientBase.WithHost" method to see how client stubs are configured now.

Finally, breaking the coupling between client and channel theoretically allows implementation of a testing in-process client-server pair that bypasses C core (which is something we've discussed in the past and it seems to offer some benefits). It is still by no means clear to me that this is something we actually want to do, but this PR at least brings an API that would allow this at all.

There are two ways in which this change is breaking:

  • changes to ClientBase (killing Host and HeaderInterceptor properties and some other changes)
  • removing the IMathClient interface - technically this is not required and we can still make the MathClient class implement IMathClient and mark the interface as obsolete.

Sorry that this change had to be so big, I didn't really find a good way to split it up. It might make sense to review some changes commit-by-commit (the last commit contains the regenerated code).

@jtattermusch
Copy link
Copy Markdown
Contributor Author

@jskeet @chrisdunelm I think this is ready for review.

@jtattermusch jtattermusch changed the title Generate abstract class as a client stub for C# Only generate an abstract class as a client stub for C# Mar 23, 2016
@jskeet
Copy link
Copy Markdown
Contributor

jskeet commented Mar 24, 2016

Am I right in saying that if we didn't need the WithHost call, we wouldn't need ClientBase, ClientBaseConfiguration or ClientBase<T>? I can see the use case for specifying a Host header, effectively (that's what it does, right?) but it feels like there should be a simpler way of doing that - it's pretty invasive at the moment.

@jtattermusch
Copy link
Copy Markdown
Contributor Author

Basically ClientBase<T> is needed to be able to provide immutable client configuration. There might be ways to avoid the generic class if we were fine with just setting the property "Host" (the way it used to be).
WithHost is just a demonstration of how configuration of client stub would work in general, I think there will be more things one will want to configure for the stub - I just didn't want to add too many new public methods at this point.
I agree that things around ClientBase have gotten more complicated, but to be honest, I am not sure how an alternative would look like. I am also wary of dropping of ClientBase concept completely, because the existence of ClientBase is the only thing that allows us to define some common functionality for all client stubs - and I think we'll definitely need that in the future.

@jskeet
Copy link
Copy Markdown
Contributor

jskeet commented Mar 24, 2016

I definitely prefer WithHost over the mutable approach before :) If you think we need to ClientBase anyway, that's reasonable.

In our generated code we have a separation of an abstract base class and then a concrete subclass, rather than your approach here with channel/call invoker - but I think each makes sense in its own context.

Note that I've only looked at the generated code, and none of the C++ - I assume that's the intention?

@jtattermusch
Copy link
Copy Markdown
Contributor Author

(I am mostly trying to first figure out the right C# API here, then I can get the rest of the PR reviewed by folks on the gRPC team.)

I could have a separate abstract class and concrete subclass, but because they both need to inherit from ClientBase, they would essentially look the same, so such distinction wouldn't buy us much here (I actually had two classes in earlier version of this PR and decided to merge them into one).

Btw, in earlier versions of the PR, I also didn't have the ClientBaseConfiguration concept - basically the WithHost (and others) were implemented by just adding a decorator over the existing callInvoker (and I only needed the MathClient(CallInvoker callInvoker) constructor) but the problem turned out to be that because of the way you compose the callInvokers, if you did mathClient.WithHost("xxx").WithHost("yyy"), you would get a client that actually sets the host to "xxx" - and that's pretty counterintuitive. ClientBaseConfiguration is essentially a trick how to pass state from one client class to another.

@jskeet
Copy link
Copy Markdown
Contributor

jskeet commented Mar 24, 2016

Right. The ClientBase definitely affects things - in our case the concrete class is a subclass of the abstract class, and the abstract class just throws exceptions. As no base class is involved, there's no state at all. Will have one more look at this, but I think it's looking good - it certainly solves the versioning problem we had before.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think the range is required here - I believe you can just leave it as 2015.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's required by our copyright checker - it sees the file has been touched in git history in 2016 and demands updating the copyright.

@jskeet
Copy link
Copy Markdown
Contributor

jskeet commented Mar 24, 2016

Okay, basically happy - worth waiting for Chris to have a careful look too though, I think.
(If we could ask you to codegen the Pubsub API for us with this code, that might help us, too...)

@jtattermusch
Copy link
Copy Markdown
Contributor Author

FYI I've built protoc.exe and grpc_csharp_plugin.exe from commit ee908f5555 (current head of this PR) using our Jenkins artifact builder.

You can download it here:
https://grpc-testing.appspot.com/view/Artifacts/job/gRPC_build_artifacts/443/architecture=x86,language=protoc,platform=windows/

or you can use a dev version of Grpc.Tools nuget package here:
https://grpc-testing.appspot.com/view/Artifacts/job/gRPC_build_packages/platform=windows/415/

@chrisdunelm
Copy link
Copy Markdown
Contributor

Apologies, but I'm not going to be able to take a detailed look at this until next Tuesday (due to UK Easter public holidays).

Generally I think it looks OK, although I can't quite see the need for some of the complexity at the moment - I'll spend more time looking at it as soon as I can.
I also haven't yet run the generator to see what code is produced, so can't comment on that either yet.
Sorry for my slowness :(

@jtattermusch jtattermusch force-pushed the csharp_clientside_abstractclass branch from ee908f5 to 09e1688 Compare March 25, 2016 23:55
@jtattermusch jtattermusch changed the title Only generate an abstract class as a client stub for C# Make adding a new RPC to a service non-breaking for generated C# Mar 25, 2016
@jtattermusch
Copy link
Copy Markdown
Contributor Author

Rebased this PR and fixed some copyrights.

Also, instead of completely removing the client-side interface I marked it as obsolete (I've done the same for the server-side for now). Removing both interfaces (IMathClient and IMath) in a followup pull request will be trivial.

@jtattermusch
Copy link
Copy Markdown
Contributor Author

@stanley-cheung friendly ping

@stanley-cheung
Copy link
Copy Markdown
Contributor

LGTM I don't have much to add. Coincidentally we have the same ClientBase construct on the web side.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

5 participants