-
Notifications
You must be signed in to change notification settings - Fork 86
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
[controller] Client side foundations and refactor to support gRPC transport for controller APIs #1259
base: main
Are you sure you want to change the base?
Conversation
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.
Left some comments, which may be a bit superficial in nature. I don't have a solid end-to-end view of the whole work, so maybe some of my suggestions don't make sense. Please take a look and LMK.
Thanks for this work! Looking forward to it landing!
private final Map<Class<?>, RequestConverter<? extends ControllerRequest, ControllerHttpRequest>> requestRegistry = | ||
new HashMap<>(); |
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.
The GrpcConvertersRegistry
has both request and response converters. Does this one likewise need to have both?
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.
Our current response classes are in a much better shape due to its decoupling from the transport details. It was only our request that had a strong coupling the underlying transport to begin with and hence the need to introduce a request abstraction that is purely driven and interacted by the controller client.
As a result, the current http flow already returns this response abstraction defined by the controller (ControllerResponse
and its extensions); So I decided to piggy back on that abstraction and have GRPC ones convert their responses into the controller abstracted response.
Additionally, I didn't want to refactor the deserialization handling piece within the ControllerHttpTransport
and even if we want to extract it, each ones don't need a concrete converter as the jackson way of deserializing things already converts the response into concrete ControllerReponse
objects.
Let me know if that answers your question.
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 guess I just don't understand why we need it for one but not the other...?
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.
that is because, we decided to use a separate response data model for gRPC and ControllerResponse
POJO isn't suitable, unless, we loose the typing information on the gRPC path and have responses represent byte[] in which case we can reuse all of it;
We decided as a team to follow gRPC convention and make the new request, response strongly typed.
* @param <T> Source controller request type | ||
* @param <D> Transport specific request type |
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.
What do T
and D
stand for? Although it is fine to use single letters for the generic type names, I think it's important to try to make these names as self-explanatory as possible. For example, I
/O
which stand for Input/Output might be fine. It is also fine to spell out whole words instead of using single letters, e.g. SOURCE
/DESTINATION
, or SRC
/DEST
.
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.
T -> The controller request type (e.g., storeCreateRequest
, jobStatusRequest
)
D -> controller transport specific request type (e.g., http, grpc etc)
I did use some of elaborative naming for generic types in few places in the code where I saw no ambiguity. I thought a bit and then wasn't fully convinced if SRC
and DEST
was going convey its intent; I even wondered if SRCREQ
to DESTREQ
would help.
Given this converter interface is not a general converter interface rather specific to the controller requests, I found the above choices too generic and hence resorted write the parameter java document to provide some clarity. Do we have any convention on naming generic types within our code base?
For e.g., Can we have something like ControllerRequestType
TransportRequestType
?
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.
T
usually stands for "Type", which is fine if there is just a single thing to maintain a generic "type" for. In this case there are two things, however, so T
seems ambiguous. D
might stand for "Destination" in this context, though I'm not sure?
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.
ah i see; T
is your concern as it by default has the type connotation. Let me update ControllerRequestType
and TransportRequestType
if you have no objections. If not, I can fallback to SRC
and DEST
which i think is okay along with the java docs
...venice-common/src/main/java/com/linkedin/venice/controller/converters/ResponseConverter.java
Show resolved
Hide resolved
* send to the controller as part of controller APIs. This is a container class defined to help with the | ||
* refactoring of ControllerClient to become transport protocol agnostic. | ||
*/ | ||
public class ControllerHttpRequest { |
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.
What is the relationship between this class and ControllerRequest
? Should this one extend the other?
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.
ControllerRequest
abstraction is meant to decouple the actual transport related information. ControllerHttpRequest
however, describes how ControllerRequest
will look like if http was chosen as the medium of transport.
Originally, I thought about having some coupling between ControllerRequest
, ControllerHttpRequest
and GeneratedV3..
(grpc general request type)in the form of having ControllerRequest
host few abstract methods like
public abstract ControllerRequest {
...
abstract ControllerHttpRequest convertToHttpRequest();
abstract <T extends GeneratedV3..> T convertToGrpcRequest();
}
However, the above spills some of the transport details into the model and hence chose against it;
Method method = stub.getClass().getMethod(grpcRoute.getMethod(), grpcRoute.getRequestType()); | ||
Object responseObject = method.invoke(stub, convertToGrpcRequest(request)); |
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.
Must we use reflection? Is there not a design pattern that could allow us to plug in the correct stuff without it? e.g. perhaps this "stub" class can return a Function<REQ, RES>
, or similar?
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.
Good point. Of the 5 days in the hackathon, I spent considerable amount of time to make this generic instead of using reflection. Most of my attempts failed due to type erasure and the need to introduce type token to carry information in a way it could potentially work.
That said, GRPC way of defining these methods are also sort of either too abstracted or too narrow; e.g., the stub generated with createStore method takes a concrete CreateStoreRequest so having type bounds on REQ
and RES
to work seemed a bit more tricky and hence I chose this one for now.
I will continue looking into this and see if TypeToken
is the way to go and perhaps refactor this out if I land on something promising.
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.
Ok well, this is not a hot path so the performance downside of reflection is not a concern... the only concern is the maintainability aspect since we lost strong typing. I guess it could be fine to let this in as is, and keep iterating later.
@@ -0,0 +1,85 @@ | |||
syntax = 'proto3'; | |||
package com.linkedin.venice.protocols; |
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.
Should we include controller
in the package name? Either in the last or second to last position?
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.
should be fine; just need to coordinate the change w/ @sushantmane
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 are going to have client/server (or "data plane") protocols as well. It would seem cleaner to me to have a clear separation between control plane and data plane protocols.
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.
One more idea/suggestion about this...
public final class ConverterUtil { | ||
|
||
// store related converters | ||
public static CreateStoreGrpcRequest convertCreateStoreToGrpcRequest(CreateStoreRequest request) { |
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.
Can this code be a function of CreateStoreRequest
? Perhaps the parent class ControllerRequest
can become ControllerRequest<GRPC>
and define a public abstract GRPC getGRPC()
API. Then CreateStoreRequest extends ControllerRequest<CreateStoreGrpcRequest>
? Would that eliminate the need for the whole registry?
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.
This goes back to my earlier comment - #1259 (comment)
Controller models (responses and requests) taking dependencies on the underlying transport through methods like toGrpc
and fromGrpc
would make the consumers of these models exposed to some underlying details (although we could control the access modifier) this also potential brings in the dependencies of the underlying transport dependencies for consumers of models.
Hence the choice to invert the dependency and have these concrete transport implementations depend on the models of controller abstraction and the conversion residing outside these models.
return builder.build(); | ||
} | ||
|
||
public static ControllerHttpRequest convertCreateStoreToHttpRequest(CreateStoreRequest request) { |
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.
And similarly, the function to convert into a ControllerHttpRequest
could likewise live over there?
return ControllerHttpRequest.newBuilder().setParam(params).build(); | ||
} | ||
|
||
public static NewStoreResponse convertCreateStoreGrpcResponse(CreateStoreGrpcResponse grpcResponse) { |
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.
And similarly to the idea I mentioned in my last review... can this become a function of NewStoreResponse
? We could have ControllerResponse<GRPC, R extends ControllerResponse >
, with NewStoreResponse extends ControllerResponse<CreateStoreGrpcResponse, NewStoreResponse>
? Although the API would be the reverse, e.g. public R fromGRPC(GRPC) {}
or similar?
Summary, imperative, start upper case, don't end with a period
createStore
APIHow was this PR tested?
Unit tested
Does this PR introduce any user-facing changes?