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
Hope that helps!