浏览代码

Add an example using swift-service-lifecycle (#2195)

George Barnett 9 月之前
父节点
当前提交
85f0fc74c9

+ 42 - 0
Examples/service-lifecycle/Package.swift

@@ -0,0 +1,42 @@
+// swift-tools-version:6.0
+/*
+ * Copyright 2025, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import PackageDescription
+
+let package = Package(
+  name: "service-lifecycle",
+  platforms: [.macOS(.v15)],
+  dependencies: [
+    .package(url: "https://github.com/grpc/grpc-swift.git", from: "2.0.0"),
+    .package(url: "https://github.com/grpc/grpc-swift-protobuf.git", from: "1.0.0"),
+    .package(url: "https://github.com/grpc/grpc-swift-extras", from: "1.0.0"),
+  ],
+  targets: [
+    .executableTarget(
+      name: "service-lifecycle",
+      dependencies: [
+        .product(name: "GRPCCore", package: "grpc-swift"),
+        .product(name: "GRPCInProcessTransport", package: "grpc-swift"),
+        .product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"),
+        .product(name: "GRPCServiceLifecycle", package: "grpc-swift-extras"),
+      ],
+      plugins: [
+        .plugin(name: "GRPCProtobufGenerator", package: "grpc-swift-protobuf")
+      ]
+    )
+  ]
+)

+ 41 - 0
Examples/service-lifecycle/README.md

@@ -0,0 +1,41 @@
+# Service Lifecycle
+
+This example demonstrates gRPC Swift's integration with Swift Service Lifecycle
+which is provided by the gRPC Swift Extras package.
+
+## Overview
+
+A "service-lifecycle" command line tool that uses generated stubs for a
+'greeter' service starts an in-process client and server orchestrated using
+Swift Service Lifecycle. The client makes requests against the server which
+periodically changes its greeting.
+
+## Prerequisites
+
+You must have the Protocol Buffers compiler (`protoc`) installed. You can find
+the instructions for doing this in the [gRPC Swift Protobuf documentation][0].
+The `swift` commands below are all prefixed with `PROTOC_PATH=$(which protoc)`,
+this is to let the build system know where `protoc` is located so that it can
+generate stubs for you. You can read more about it in the [gRPC Swift Protobuf
+documentation][1].
+
+## Usage
+
+Build and run the server using the CLI:
+
+```console
+$ PROTOC_PATH=$(which protoc) swift run service-lifecycle
+Здравствуйте, request-1!
+नमस्ते, request-2!
+你好, request-3!
+French, request-4!
+Olá, request-5!
+Hola, request-6!
+Hello, request-7!
+Hello, request-8!
+नमस्ते, request-9!
+Hello, request-10!
+```
+
+[0]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation/grpcprotobuf/installing-protoc
+[1]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation/grpcprotobuf/generating-stubs

+ 80 - 0
Examples/service-lifecycle/Sources/GreetingService.swift

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2025, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import GRPCCore
+import ServiceLifecycle
+import Synchronization
+
+/// Implements the "Hello World" gRPC service but modifies the greeting on a timer.
+///
+/// The service conforms to the 'ServiceLifecycle.Service' and uses its 'run()' method
+/// to execute the run loop which updates the greeting.
+final class GreetingService {
+  private let updateInterval: Duration
+  private let currentGreetingIndex: Mutex<Int>
+  private let greetings: [String] = [
+    "Hello",
+    "你好",
+    "नमस्ते",
+    "Hola",
+    "French",
+    "Olá",
+    "Здравствуйте",
+    "こんにちは",
+    "Ciao",
+  ]
+
+  private func personalizedGreeting(forName name: String) -> String {
+    let index = self.currentGreetingIndex.withLock { $0 }
+    return "\(self.greetings[index]), \(name)!"
+  }
+
+  private func periodicallyUpdateGreeting() async throws {
+    while !Task.isShuttingDownGracefully {
+      try await Task.sleep(for: self.updateInterval)
+
+      // Increment the greeting index.
+      self.currentGreetingIndex.withLock { index in
+        // '!' is fine; greetings is non-empty.
+        index = self.greetings.indices.randomElement()!
+      }
+    }
+  }
+
+  init(updateInterval: Duration) {
+    // '!' is fine; greetings is non-empty.
+    let index = self.greetings.indices.randomElement()!
+    self.currentGreetingIndex = Mutex(index)
+    self.updateInterval = updateInterval
+  }
+}
+
+extension GreetingService: Helloworld_Greeter.SimpleServiceProtocol {
+  func sayHello(
+    request: Helloworld_HelloRequest,
+    context: ServerContext
+  ) async throws -> Helloworld_HelloReply {
+    return .with {
+      $0.message = self.personalizedGreeting(forName: request.name)
+    }
+  }
+}
+
+extension GreetingService: Service {
+  func run() async throws {
+    try await self.periodicallyUpdateGreeting()
+  }
+}

+ 74 - 0
Examples/service-lifecycle/Sources/LifecycleExample.swift

@@ -0,0 +1,74 @@
+/*
+ * Copyright 2025, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import GRPCCore
+import GRPCInProcessTransport
+import GRPCServiceLifecycle
+import Logging
+import ServiceLifecycle
+
+@main
+struct LifecycleExample {
+  static func main() async throws {
+    // Create the gRPC service. It periodically changes the greeting returned to the client.
+    // It also conforms to 'ServiceLifecycle.Service' and uses the 'run()' method to perform
+    // the updates.
+    //
+    // A more realistic service may use the run method to maintain a connection to an upstream
+    // service or database.
+    let greetingService = GreetingService(updateInterval: .microseconds(250))
+
+    // Create the client and server using the in-process transport (which is used here for
+    // simplicity.)
+    let inProcess = InProcessTransport()
+    let server = GRPCServer(transport: inProcess.server, services: [greetingService])
+    let client = GRPCClient(transport: inProcess.client)
+
+    // Configure the service group with the services. They're started in the order they're listed
+    // and shutdown in reverse order.
+    let serviceGroup = ServiceGroup(
+      services: [
+        greetingService,
+        server,
+        client,
+      ],
+      logger: Logger(label: "io.grpc.examples.service-lifecycle")
+    )
+
+    try await withThrowingDiscardingTaskGroup { group in
+      // Run the service group in a task group. This isn't typically required but is here in
+      // order to make requests using the client while the service group is running.
+      group.addTask {
+        try await serviceGroup.run()
+      }
+
+      // Make some requests, pausing between each to give the server a chance to update
+      // the greeting.
+      let greeter = Helloworld_Greeter.Client(wrapping: client)
+      for request in 1 ... 10 {
+        let reply = try await greeter.sayHello(.with { $0.name = "request-\(request)" })
+        print(reply.message)
+
+        // Sleep for a moment.
+        let waitTime = Duration.milliseconds((50 ... 400).randomElement()!)
+        try await Task.sleep(for: waitTime)
+      }
+
+      // Finally, shutdown the service group gracefully.
+      await serviceGroup.triggerGracefulShutdown()
+    }
+  }
+}

+ 7 - 0
Examples/service-lifecycle/Sources/Protos/grpc-swift-proto-generator-config.json

@@ -0,0 +1,7 @@
+{
+  "generate": {
+    "clients": true,
+    "servers": true,
+    "messages": true
+  }
+}

+ 1 - 0
Examples/service-lifecycle/Sources/Protos/helloworld.proto

@@ -0,0 +1 @@
+../../../../dev/protos/upstream/grpc/examples/helloworld.proto