Skip to content

Commit 85f0fc7

Browse files
authored
Add an example using swift-service-lifecycle (#2195)
1 parent 362efe5 commit 85f0fc7

File tree

6 files changed

+245
-0
lines changed

6 files changed

+245
-0
lines changed

Diff for: Examples/service-lifecycle/Package.swift

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// swift-tools-version:6.0
2+
/*
3+
* Copyright 2025, gRPC Authors All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import PackageDescription
19+
20+
let package = Package(
21+
name: "service-lifecycle",
22+
platforms: [.macOS(.v15)],
23+
dependencies: [
24+
.package(url: "https://github.com/grpc/grpc-swift.git", from: "2.0.0"),
25+
.package(url: "https://github.com/grpc/grpc-swift-protobuf.git", from: "1.0.0"),
26+
.package(url: "https://github.com/grpc/grpc-swift-extras", from: "1.0.0"),
27+
],
28+
targets: [
29+
.executableTarget(
30+
name: "service-lifecycle",
31+
dependencies: [
32+
.product(name: "GRPCCore", package: "grpc-swift"),
33+
.product(name: "GRPCInProcessTransport", package: "grpc-swift"),
34+
.product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"),
35+
.product(name: "GRPCServiceLifecycle", package: "grpc-swift-extras"),
36+
],
37+
plugins: [
38+
.plugin(name: "GRPCProtobufGenerator", package: "grpc-swift-protobuf")
39+
]
40+
)
41+
]
42+
)

Diff for: Examples/service-lifecycle/README.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Service Lifecycle
2+
3+
This example demonstrates gRPC Swift's integration with Swift Service Lifecycle
4+
which is provided by the gRPC Swift Extras package.
5+
6+
## Overview
7+
8+
A "service-lifecycle" command line tool that uses generated stubs for a
9+
'greeter' service starts an in-process client and server orchestrated using
10+
Swift Service Lifecycle. The client makes requests against the server which
11+
periodically changes its greeting.
12+
13+
## Prerequisites
14+
15+
You must have the Protocol Buffers compiler (`protoc`) installed. You can find
16+
the instructions for doing this in the [gRPC Swift Protobuf documentation][0].
17+
The `swift` commands below are all prefixed with `PROTOC_PATH=$(which protoc)`,
18+
this is to let the build system know where `protoc` is located so that it can
19+
generate stubs for you. You can read more about it in the [gRPC Swift Protobuf
20+
documentation][1].
21+
22+
## Usage
23+
24+
Build and run the server using the CLI:
25+
26+
```console
27+
$ PROTOC_PATH=$(which protoc) swift run service-lifecycle
28+
Здравствуйте, request-1!
29+
नमस्ते, request-2!
30+
你好, request-3!
31+
French, request-4!
32+
Olá, request-5!
33+
Hola, request-6!
34+
Hello, request-7!
35+
Hello, request-8!
36+
नमस्ते, request-9!
37+
Hello, request-10!
38+
```
39+
40+
[0]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation/grpcprotobuf/installing-protoc
41+
[1]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation/grpcprotobuf/generating-stubs
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2025, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import GRPCCore
18+
import ServiceLifecycle
19+
import Synchronization
20+
21+
/// Implements the "Hello World" gRPC service but modifies the greeting on a timer.
22+
///
23+
/// The service conforms to the 'ServiceLifecycle.Service' and uses its 'run()' method
24+
/// to execute the run loop which updates the greeting.
25+
final class GreetingService {
26+
private let updateInterval: Duration
27+
private let currentGreetingIndex: Mutex<Int>
28+
private let greetings: [String] = [
29+
"Hello",
30+
"你好",
31+
"नमस्ते",
32+
"Hola",
33+
"French",
34+
"Olá",
35+
"Здравствуйте",
36+
"こんにちは",
37+
"Ciao",
38+
]
39+
40+
private func personalizedGreeting(forName name: String) -> String {
41+
let index = self.currentGreetingIndex.withLock { $0 }
42+
return "\(self.greetings[index]), \(name)!"
43+
}
44+
45+
private func periodicallyUpdateGreeting() async throws {
46+
while !Task.isShuttingDownGracefully {
47+
try await Task.sleep(for: self.updateInterval)
48+
49+
// Increment the greeting index.
50+
self.currentGreetingIndex.withLock { index in
51+
// '!' is fine; greetings is non-empty.
52+
index = self.greetings.indices.randomElement()!
53+
}
54+
}
55+
}
56+
57+
init(updateInterval: Duration) {
58+
// '!' is fine; greetings is non-empty.
59+
let index = self.greetings.indices.randomElement()!
60+
self.currentGreetingIndex = Mutex(index)
61+
self.updateInterval = updateInterval
62+
}
63+
}
64+
65+
extension GreetingService: Helloworld_Greeter.SimpleServiceProtocol {
66+
func sayHello(
67+
request: Helloworld_HelloRequest,
68+
context: ServerContext
69+
) async throws -> Helloworld_HelloReply {
70+
return .with {
71+
$0.message = self.personalizedGreeting(forName: request.name)
72+
}
73+
}
74+
}
75+
76+
extension GreetingService: Service {
77+
func run() async throws {
78+
try await self.periodicallyUpdateGreeting()
79+
}
80+
}
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2025, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import GRPCCore
18+
import GRPCInProcessTransport
19+
import GRPCServiceLifecycle
20+
import Logging
21+
import ServiceLifecycle
22+
23+
@main
24+
struct LifecycleExample {
25+
static func main() async throws {
26+
// Create the gRPC service. It periodically changes the greeting returned to the client.
27+
// It also conforms to 'ServiceLifecycle.Service' and uses the 'run()' method to perform
28+
// the updates.
29+
//
30+
// A more realistic service may use the run method to maintain a connection to an upstream
31+
// service or database.
32+
let greetingService = GreetingService(updateInterval: .microseconds(250))
33+
34+
// Create the client and server using the in-process transport (which is used here for
35+
// simplicity.)
36+
let inProcess = InProcessTransport()
37+
let server = GRPCServer(transport: inProcess.server, services: [greetingService])
38+
let client = GRPCClient(transport: inProcess.client)
39+
40+
// Configure the service group with the services. They're started in the order they're listed
41+
// and shutdown in reverse order.
42+
let serviceGroup = ServiceGroup(
43+
services: [
44+
greetingService,
45+
server,
46+
client,
47+
],
48+
logger: Logger(label: "io.grpc.examples.service-lifecycle")
49+
)
50+
51+
try await withThrowingDiscardingTaskGroup { group in
52+
// Run the service group in a task group. This isn't typically required but is here in
53+
// order to make requests using the client while the service group is running.
54+
group.addTask {
55+
try await serviceGroup.run()
56+
}
57+
58+
// Make some requests, pausing between each to give the server a chance to update
59+
// the greeting.
60+
let greeter = Helloworld_Greeter.Client(wrapping: client)
61+
for request in 1 ... 10 {
62+
let reply = try await greeter.sayHello(.with { $0.name = "request-\(request)" })
63+
print(reply.message)
64+
65+
// Sleep for a moment.
66+
let waitTime = Duration.milliseconds((50 ... 400).randomElement()!)
67+
try await Task.sleep(for: waitTime)
68+
}
69+
70+
// Finally, shutdown the service group gracefully.
71+
await serviceGroup.triggerGracefulShutdown()
72+
}
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"generate": {
3+
"clients": true,
4+
"servers": true,
5+
"messages": true
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../dev/protos/upstream/grpc/examples/helloworld.proto

0 commit comments

Comments
 (0)