0

I already have a working gRPC project working. I'm looking to build an API to be able to do some HTTP requests.

I have the following 2 types:

message FindRequest {
  ModelType model_type = 1;
  oneof by {
    string id = 2;
    string name = 3;
  }
}
message GetAllRequest {
  ModelType model_type = 1;
  int32 page_size = 2;
  oneof paging {
    int32 page = 3;
    bool skip_paging = 4;
  }
}

And then, I would like to have those 2 endpoints:

  // Get a data set by ID or name. Returns an empty data set if there is no such
  // data set in the database. If there are multiple data sets with the same
  // name in the database it returns the first of these data sets.
rpc Find(FindRequest) returns (DataSet){
    option (google.api.http) = { get: "/datasets" };
 }
  // Get (a page of) all data sets of a given type. If no page size is given
  // (page <= 0) it defaults to 100. An unset page (page <= 0) defaults to the
  // first page.
rpc GetAll(GetAllRequest) returns (GetAllResponse){
    option (google.api.http) = { get: "/datasets" };
}

It makes sense to me to have 2 different endpoints with the same name, but that differ with the parameters. For instance, requesting /datasets?model-type=XXX should be mapped to the GetAll function, while requesting /datasets?model-type=XXX&name=YYY should be mapped to Find function. However, it doesn't work, since the mapping fails I guess, so none of these endpoints returns me anything.

I think the solution to make the mapping working would be to force the parameter to be required, however, I am working with proto3, which has disallowed the required field.

So how could I be able to have 2 endpoints with the same name, but different parameters, with proto3 ?

I know that if I am using different endpoint names, it is working, for example for the findRequest, I could have the following endpoint : /findDatasets, but regarding the best practice about API naming convention, it is not advisable, or is it ?

TheTisiboth
  • 1,431
  • 1
  • 6
  • 13

1 Answers1

1

The conventional way to solve this problem is to use different methods. My hunch is that it's an anti-pattern to try to differentiate using the fields in the request string.

service YourService {
    rpc FindSomething(FindSomethingRequest) returns (FindSomethingResponse){
        option (google.api.http) = { get: "/something/find" };
    }
    rpc ListSomething(ListSomethingRequest) returns (ListSomethingResponse){
        option (google.api.http) = { get: "/something/list" };
    }
}

message FindSomethingRequest {
  ModelType model_type = 1;
  string id = 2;
  string name = 3;
}
message ListSomethingRequest {
  int32 page_size = 2;
  int32 page_token = 3;
}
message ListSomethingResponse {
  repeated ModelType model_types = 1;
  int32 page_size = 2;
  int32 next_page_token = 3;
}

I'm unsure of your underlying thing structure but, I think it's better practice to model things with all possible properties and permit leaving some unset (e.g. either id or name or possibly both in FindSomethingRequest) rather than creating different message types for all possible queries. You model the thing not how you interact with it.

In your implementation (!) of FindSomething, you then deal with the permutations of how users of the message may construct the fields. Perhaps reporting an error "Either id or name is required`.

I think ListSomething's messages could be simpler too. You request a List of (ModelTypes) and give a page_size and an page_token (that could be ""). It returns a list of ModelTypes, the size of the page returned (possibly less than requested) and a next_page_token if there is more data, that you can use to make the next ListSomething request.

DazWilkin
  • 32,823
  • 5
  • 47
  • 88
  • Thank you for your answer. It is actually working, but I read that it is better to avoid having verbs in the API endpoints. However, if this is the only way to make it works, I will do this way! – TheTisiboth Jun 14 '21 at 07:20