NIO-based gRPC Swift

Pitch

Include gRPC-Swift among the official and recommended SSWG projects.

Motivation

gRPC enables high-performance open standards-based interoperability for Swift services with services written in other languages.

Quoting grpc.io: "gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services."

gRPC has proven useful to many companies and development teams, and its support in multiple languages has made it a popular framework for RPC services. The gRPC-Swift project was jointly developed by teams at Google, Apple, Lyft, and independent software developer Timing, and has proven useful for building applications at scales seen by all of these contributors.

We believe that including gRPC-Swift among officially-recognized SSWG projects and officially-recognized gRPC implementations will move both gRPC and Swift forward as great systems for distributed application development.

Project Composition

The gRPC-Swift project includes a runtime, a code generator, an extensive suite of tests, and examples of both mobile and server-side applications using gRPC with Swift.

31 Likes

YES please, I don't see any reason not to add it. Especially given that the implementation looks really good!

4 Likes

A huge +1 from me: gRPC is an extremely important protocol, and it is exciting to have a good, almost entirely Swift implementation. It seems like a no-brainer to me.

6 Likes

Yes gRPC is definitely on-topic here!

Great to see that the package already uses the swift-log package we created. Is using swift-metrics on the roadmap too?

One topic of conversation might be about the level of API provided. I haven't looked at grpc-swift in detail but I see it offers a public class Server for starting a gRPC server. SwiftNIO doesn't provide that level of API for HTTP, rather it provides the building blocks for others to build an API for an HTTP server. So as well as providing the ChannelHandlers etc. for gRPC this package also provides some higher level API. I'll be interested to hear whether others think that's OK for SSWG packages.

4 Likes

Plus one from me :-) Useful addition to the sswg recommended projects portfolio. I hope to muster up some more time to go over the APIs in some more depth in the near future...

umm, I am not for this but am for the need of data center rpc services. the idea of gRPC is yes, gr8, but proto buff, no as swift's codable types are very powerful on its own which is contingent upon community to create solutions.

+1 this would be a great addition. it extends the application level protocol options and aligned with the “cloud native” nature of swift

2 Likes

Not at the moment; down the line I think it would be a useful addition though.

True, although AsyncHTTPClient does this, albeit for a client. We haven't gone out of our way to preclude users from taking the handlers we provide and creating their own gRPC server but I imagine there are a bunch of ways we could make this easier (i.e. something similar to configureHTTP2Pipeline in NIO HTTP2).

We have a handy tutorial which covers most of it if you can't wait until the proposal :wink:

3 Likes

Big +1, gRPC is often an obvious protocol choice and having this part of SSWG makes perfect sense.

1 Like

This is a strong addition to SSWG, great to see grpc-swift hit the milestone when it can be pitched. Well done everyone involved.

2 Likes

it's will be great !

Yes! I've been waiting a long time for this. Congratulations to the grpc-swift team as the project nears the 1.0 milestone.

2 Likes

I have written a small library called CombineGRPC that provides Combine framework integration for gRPC Swift. I would love to have some feedback if anybody is willing to take a look.

You can use it to make gRPC calls and implement server side RPC handlers using Combine publishers. All RPC types are supported. Here is a simple bidirectional streaming example. Protobuf:

syntax = "proto3";

service EchoService {
  rpc SayItBack (stream EchoRequest) returns (stream EchoResponse);
}

message EchoRequest {
  string message = 1;
}

message EchoResponse {
  string message = 1;
}

Server side implementation, where EchoProvider is generated by gRPC Swift:

class EchoServiceProvider: EchoProvider {  

  // Bidirectional streaming RPC that echoes back each request message
  func sayItBack(context: StreamingResponseCallContext<EchoResponse>)
    -> EventLoopFuture<(StreamEvent<EchoRequest>) -> Void>
  {
    handle(context) { requests in
      requests
        .map { req in
          EchoResponse.with { $0.message = req.message }
        }
        .setFailureType(to: GRPCStatus.self)
        .eraseToAnyPublisher()
    }
  }
}

Here's how you make RPC calls on the client side. EchoServiceClient is generated by gRPC Swift:

let echoClient = EchoServiceClient(connection: ClientConnection(configuration: configuration))
let grpc = GRPCExecutor()  // Can optionally be configured with retry policy and CallOptions

let requests = Publishers
  .Sequence(sequence: repeatElement(EchoRequest.with { $0.message = "hello"}, count: 10))
  .eraseToAnyPublisher()

grpc.call(echoClient.sayItBack)(requests)
  .filter { $0.message == "hello" }
  .count()
  .sink(receiveValue: { count in
    assert(count == 10)
  })

The proposal is here: [Discussion] gRPC Swift

2 Likes

I had a chance to test a generated client out with Vapor 4 and it works great! I've included the code I used below for reference.

The biggest issue I ran into getting this up and running was configuring protoc. I noticed you have a Makefile but I couldn't really figure out how to get that working. It seems like it's built specifically for the grpc-swift repo and it was difficult (for me at least) to parse what was happening inside due to all the variables.

I ended up installing SwiftProtobuf via brew install swift-protobuf. That worked great, but I still needed protoc-gen-grpc-swift. After a lot of debugging strange errors, I finally realized if I build grpc-swift in release mode, then put that executable in my bin, protoc can find it automatically. Once all said and done, I was able to use this command:

protoc HelloWorld.proto \
    --proto_path=Sources/App \
    --swift_out=Sources/App \
    --grpc-swift_out=Sources/App

That finally generated both the HelloWorld.pb.swift and HelloWorld.grpc.swift files where I wanted them.

Two takeaways from setup:

  • It would be awesome if you could brew install swift-grpc or at least clone grpc/swift-grpc and do make install
  • The docs I found seemed more geared toward running the grpc-swift repo's examples, and not toward using it in your own project. In fairness, I might have just glossed over those accidentally.

Some takeaways from the actual code:

  • I'm initializing a new ClientConnection each request. Is this the recommended pattern or do I need to keep a shared client for connection pooling?
  • I got a strange error Missing package product 'Logging' if I didn't explicitly include apple/swift-log in my project. I haven't seen this before and I'm not sure why I'd need to do that. @weissi any ideas?

Here's the main.swift file I used.

import GRPC
import Vapor

var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)

let app = Application(env)
defer { app.shutdown() }

app.get("greeting") { req -> EventLoopFuture<String> in
    // Provide some basic configuration for the connection, in this case we connect to an endpoint on
    // localhost at the given port.
    let configuration = ClientConnection.Configuration(
      target: .hostAndPort("localhost", 50323),
      eventLoopGroup: req.eventLoop
    )

    // Create a connection using the configuration.
    let connection = ClientConnection(configuration: configuration)

    // Provide the connection to the generated client.
    let greeter = Helloworld_GreeterServiceClient(connection: connection)

    // Form the request with the name, if one was provided.
    let request = Helloworld_HelloRequest.with {
        $0.name = req.query["name"] ?? ""
    }

    // Make the RPC call to the server.
    let sayHello = greeter.sayHello(request)

    // map response
    return sayHello.response.map { reply in
        reply.message
    }
}

try app.run()
$ http get localhost:8080/greeting name==Vapor
HTTP/1.1 200 OK
content-length: 12
content-type: text/plain; charset=utf-8

Hello Vapor!

Finally, the Package.swift:

// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "foo",
    platforms: [
       .macOS(.v10_14)
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta"),
        .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0-alpha"),
        // Not sure why this one is needed. 
        .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
    ],
    targets: [
        .target(name: "App", dependencies: ["Logging", "GRPC", "Vapor"])
    ]
)
2 Likes

Thanks for the feedback @tanner0101 – super helpful!

That's completely fair: I had the same issues when I started out with gRPC! How to invoke protoc to do the right thing isn't obvious. We have some documentation on the different options available to the protoc-gen-grpc-swift plugin (https://github.com/grpc/grpc-swift/blob/nio/docs/plugin.md) but nothing that really explains how to use without prior knowledge of protoc. The idea of a "cookbook" was suggested recently which could be a good way to show people how to get started, I think this would alleviate the problem that the docs are quite tightly coupled to the examples. What do you think?

Something I just noticed is that the "Using gRPC Swift" section of the README is buried right at the bottom, bring that up to the top and highlighting the different documentation may also help.

Couldn't agree more! There's already a formula for grpc-swift but it provides the plugin for the non-NIO based version. We'll update this before we tag 1.0.0.

It would be better to keep a connection open instead of creating one per request: gRPC runs over HTTP/2 and each RPC runs in a separate HTTP/2 stream so you can have many concurrent RPCs per connection. A single connection may well be sufficient, but of course this depends on what your service is doing!

2 Likes
Terms of Service

Privacy Policy

Cookie Policy