Description
What are you trying to achieve?
I am building an iOS app for a complex product that speaks to multiple logical GRPC services, all served from the same server. So, for example, I can call UserService.GetUser() and ProgramService.GetProgram() and they will both go to the same (load-balanced, horizontally scaled) host.
Every example I've seen for usage of Swift GRPC 2 is for a one-off single call to a single service, looking like:
try await withGRPCClient(
transport: .http2NIOPosix(
target: .dns(host: hostname, port: 443),
transportSecurity: .tls
)
) { client in
let greeter = GreetingService.Client(wrapping: client)
let greeting = try await greeter.sayHello(.with { $0.name = "swift.org" })
print(greeting.message)
}
I'd like to understand best practices for usage with multiple services, within an actual app that needs a connection to function. A couple of options spring to mind:
- Create a new GRPCClient for each service client.
- Create a single GRPCClient and then re-use that for each service client.
- Create a single GRPCClient that has some kind of pool behind it (of say, 4 connections), then re-use that single exposed GRPCClient for each service client.
Option 2/3 is most similar to what I had for Swift GRPC 1, and my preferred approach if possible. (There are already a dozen services, and there will be more in the future.)
I would also like to understand if there's a blocking/synchronous approach to ensuring a connection is established.
What have you tried so far?
Previously, I had a single synchronously established GRPCChannel, exposed as a static computed variable:
class GRPCManager {
private static var host: String {
Bundle.main.infoDictionary?["APP_CORE_HOST"] as! String
}
static var connection: GRPCChannel {
guard host != "" else {
fatalError("GRPC Host is not set")
}
return connect()
}
private static func connect() -> GRPCChannel {
let eventLoopGroup = NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default)
return ClientConnection.usingPlatformAppropriateTLS(for: eventLoopGroup)
.connect(host: host, port: 443)
}
}
and then each services had an init like:
init() {
client = App_UserServiceAsyncClient(channel: GRPCManager.connection)
}
init() {
client = App_ProgramServiceAsyncClient(channel: GRPCManager.connection)
}
This lazily established the connection the first time it was needed, requiring no explicit setup in the @main App
. It seemed to work well. I appreciate that client.runConnections()
is now asynchronous, but I need to know when it's reliably available/block until the connection is established, because the app is unusable without it.
I would like to achieve this while keeping the service clients themselves isolated and independent of each other, in particular not requiring a specific order of service usage.