The .proto
file is as below:
syntax = "proto3";
package bamboo;
// -----------------Cart service-----------------
service CartService {
rpc AddItem(AddItemRequest) returns (Empty) {}
rpc GetCart(GetCartRequest) returns (Cart) {}
rpc EmptyCart(EmptyCartRequest) returns (Empty) {}
}
message CartItem {
string product_id = 1;
int32 quantity = 2;
}
message AddItemRequest {
string user_id = 1;
CartItem item = 2;
}
message EmptyCartRequest {
string user_id = 1;
}
message GetCartRequest {
string user_id = 1;
}
message Cart {
string user_id = 1;
repeated CartItem items = 2;
}
message Empty {}
And I have a Cart
service on gRPC that is as simple as below:
from grpczoo import bamboo_pb2_grpc, bamboo_pb2
from concurrent import futures
import grpc
import time
class CartService(bamboo_pb2_grpc.CartServiceServicer):
def AddItem(self, request, context):
print(request.user_id, request.item)
return bamboo_pb2.Empty()
if __name__ == "__main__":
channel = grpc.insecure_channel('127.0.0.1')
cart_stub = bamboo_pb2_grpc.CartServiceStub(channel)
# create gRPC server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
# add class to gRPC server
service = CartService()
bamboo_pb2_grpc.add_CartServiceServicer_to_server(service, server)
# start server
server.add_insecure_port('[::]:9000')
server.start()
# keep alive
try:
while True:
time.sleep(10000)
except KeyboardInterrupt:
server.stop(0)
Let's assume that starting a service like above seems good when you have 40 services (We can create a base class for starting a service)
The most annoying part of the system is where I have to initiate a client when I need to send a RPC call to a service (here cart service):
import grpc
# set up server stub
from grpczoo import bamboo_pb2_grpc, bamboo_pb2
channel = grpc.insecure_channel('localhost:9000')
stub = bamboo_pb2_grpc.CartServiceStub(channel)
# form request
request = bamboo_pb2.AddItemRequest(user_id="123", item={'product_id': '21', 'quantity': 2})
# make call to server
response = stub.AddItem(request)
print(response)
As you can see for a simple RPC call like above I have to open channel to that service and form a request and then call the remote method AddItem
. In a real world micro-service project I have to call at least 5 different methods and for each I have to create a channel and for the request and so on.
The question is how should I manage RPC calls to different methods when project gets bigger and I have 20 to 50 different services? The above code does not seems maintainable at all. How do you handle such cases?
-
\$\begingroup\$ You should change your title according to Titling your question to make it more descriptive. \$\endgroup\$AlexV– AlexV2019年05月08日 18:12:19 +00:00Commented May 8, 2019 at 18:12
2 Answers 2
Keep-alive
Your sleep
method is curious. There are alternates here:
https://stackoverflow.com/questions/20170251/how-to-run-the-python-program-forever
but those are generic to Python; there is a better option for gRPC:
In this case, you can call
server.wait_for_termination()
to cleanly block the calling thread until the server terminates.
Stopping
You should not only call it on KeyboardInterrupt
; you should put it in a finally
:
server.start()
try:
# ...
finally:
server.stop(0)
This way, the server will be stopped if the user breaks with Ctrl+C, or if there is any other exception. However, I doubt it's necessary to call this at all if you use wait_for_termination
.
Worked with gRPC client in C#. There you can initialize the client in the constructor with no additional code needed on request. It should be the same in Python.
public MyClient(IConfiguration configuration, ILogger < MyClient > logger) {
_logger = logger;
_configuration = configuration;
var config = _configuration.GetGrpcConfigObject(...);
var myConfig = config.HostName + ":" + config.Port;
_channel = new Channel(myConfig, ChannelCredentials.Insecure);
_client = new ServiceClient(_channel);
}
public async Task <IEnumerable<Model.Permission>> GetPermissions() {
IEnumerable <Model.Permission> resultList = null;
try {
PermissionResponse response = await _client.GetPermissionsAsync(new Empty());
resultList = _mapper.Map <IEnumerable <Permission > , IEnumerable <Model.Permission >> (response.PermissionList);
} catch (Exception ex) {
_logger.LogError($ "BatchSave failed:{ex}");
}
return resultList;
}
Explore related questions
See similar questions with these tags.