Creating gRPC service - Do I need a framework?

Hi everyone,
I recently wanted to build a swift server with a RESTful interface, and was learning about the vapor framework to plan an implementation of my ideas.
However I've recently been pointed towards gRPC.
I like a lot of things about it, mainly the self-documenting interface, and client generation, which I think after the initial learning curve, would reduce the developer errors and effort to work with a new gRPC API.

I was wondering, do people building gRPC services generally use any kind of framework for this, akin to something like vapor?
Or would this be almost redundant, since the services are automatically generated, and manually creating them would be error prone?

I notice there isn't really a concept of URL routing, since the service defines what methods are available, so there's no need for a framework to do that, but I was just curious, can it really be a simple as the tutorials look, to build a large, production-ready service?
How do services handle interface changes?
How do servers handle authorization?

Any thoughts would be appreciated.

Regards,
Mark

Hey @mark.m!

I was wondering, do people building gRPC services generally use any kind of
framework for this, akin to something like vapor? Or would this be almost
redundant, since the services are automatically generated, and manually
creating them would be error prone?

Let's step back a little bit and consider how to create a gRPC service.

First you need to define the service and and the method request and response types using protocol buffers. Once that's been done you need to generate the client and server stubs using the protocol buffer compiler (protoc) and appropriate plugins. For Swift this would be protoc-gen-grpc-swift from gRPC Swift for generating the gRPC parts of the code, and protoc-gen-swift from SwiftProtobuf to generate the data models for the request and response types.

Let's look at the gRPC parts.

For the server, a "provider" protocol is generated for each service you defined using protocol buffers, where each protocol contains one function per service method. As someone creating a gRPC service you must then provide a type which conforms to the generated "provider" protocol to implement the business logic for your service.

To start your gRPC server you then provide an instance of that implementation and some configuration (which I won't go into here) to a Server component from the framework. This does all the heavy lifting around the transport, the gRPC protocol, encoding and decoding the wire format, routing requests and so on.

The story is similar on the client side: the generated code contains a client for each of your defined services which is instantiated with a connection to your service. Again the framework does the heavy lifting with regards to the transport and the gRPC protocol but you have to use that framework to create the connection.

To answer your question: you should use a framework for this, but your interaction with that framework to create a service is fairly limited.

I notice there isn't really a concept of URL routing, since the service defines
what methods are available, so there's no need for a framework to do that,

The routing format is defined by the protocol and follows the format of
"/ServiceName/MethodName", the generated client code must follow that so
the underlying server code is able to route requests to the right service method you provided an implementation for. There's still routing, it's just that the user doesn't really have any say in it.

Can it really be a simple as the tutorials look, to build a large, production-ready service?

It's certainly simple to create a production ready service. We have a number of tutorials for getting started: both a quick-start guide https://github.com/grpc/grpc-swift/blob/nio/docs/quick-start.md, and a basic tutorial: https://github.com/grpc/grpc-swift/blob/nio/docs/basic-tutorial.md.

How do services handle interface changes?

There are a number of ways in which your interface can change: you could add new methods to your service, you could add fields to request/response types or you could make backwards incompatible changes like completely changing a request/response type for a method.

For any of the above changes you need to regenerate some code. In the case of adding a new method your service provider implementation (the business logic for your service) will no longer conform to your generated protocol: you have a compile time guarantee that your service is up to date. The client doesn't have to be updated, but it won't be able to talk to your new method of course! (This isn't entirely true: you can use a client to talk to a service for which you don't have a generated client, providing you do have generated the correct request and response models and have sufficient information about the service and method, i.e. it's path and what type of RPC it is; unary, bidiretional streaming, etc.)

If you add fields to your message types then you only need to recompile the message (the client and service code can remain the same since they depend on the generated messages). Naturally you'd need to update the service provider implementation to make use of the new fields.

Finally, if the request/response types are changed altogether then the service and client must be regenerated. This is a much more painful process since they need to be updated in lockstep. This should be avoided, as should making backwards incompatible in general.

How do servers handle authorization?

One thing gRPC Swift currently lacks on the server is a middleware plugin layer
for things like authorization. Instead this must be handled via the initial
metadata (headers) sent at the start of each RPC in your provider
implementation.

Hope that helps!

2 Likes

Great summary, @georgebarnett!

One point I would like to add is that using gRPC does not prevent you from using Vapor's functionalities, such as for authentication and database access. You would need to call the corresponding methods yourself, because we currently do not support Vapor middlewares, but you can definitely use the functionality.

You may also want to have a look at my Server Side Swift talk on gRPC; it includes a short snippet on how to create a Vapor Request (i.e. a service container) for any gRPC request.

1 Like

Hey guys,
thanks so much for the pointers here.
I've been trying it out. I wanted a practical example, so I forked google's hipster shop microservices demo, as it uses a lot of gRPC with containers.

However I've come across an issue I'm hoping someone can explain for me.
When I build my code on my computer within xcode or command line, both the server and client run as expected.
I'm having difficulties with containerising my program successfully.
When I run the server in a container on my local computer using docker desktop (for mac), I don't seem to be able to connect to the gRPC service from outside the container.
I've got a fair bit of experience with containers and networking, so this feels like something I'd be able to grok pretty quickly if it was an issue with docker. Already tried exposing port vs. running on host network vs. a dedicated network. Tried swapping from localhost to 127.0.0.1, no luck.

However, if I start the container, and get a shell into it, and run the client program, it works fine.

I'm wondering if anyone might be able to point out something wrong?
My code is here: microservices-demo/src/currencyswiftservice at master · Mark-McCracken/microservices-demo · GitHub
Not doing anything special to exercise it, docker build, docker run (expose port/specify network), docker run for client.

Thanks,
Mark

Scratch that, seems to be working fine. Might have been related to macs allowing multiple processes to bind to a single port in some occasions, like when running one of those process in docker and one as a normal binary.

In case this comes up again, try using 0.0.0.0 as the hostname. Maybe 127.0.01 listens only inside the container, without allowing external connections, I'm not 100% sure.

Yeah this was one of the changes I made before it worked actually, thanks