Explore the Swift on Server ecosystem
SwiftUI & UI Frameworks 进阶 20m

探索 Swift 服务端生态

Explore the Swift on Server ecosystem

2024年6月10日

在 Apple 官方观看视频

一句话判断

Apple 用这场 Session 证明 Swift 在服务端不是实验项目——iCloud、Private Cloud Compute 都在生产环境跑着,生态工具链已经足够成熟。

这场 Session 讲了什么

这场 Session 全面介绍了 Swift 在服务端的应用现状和生态体系。开场就给出了有力的数据:Apple 内部的 iCloud Keychain、Photos、Notes、App Store 处理流水线、SharePlay 文件共享,以及全新的 Private Cloud Compute 服务,都在用 Swift 处理每秒百万级请求。

内容分三个部分。第一部分解释了 Swift 适合服务端的原因:ARC(而非 GC)带来可预测的内存使用和快速启动、编译期类型安全消除运行时错误、一流的并发支持。第二部分是实操演示:用 Swift OpenAPI Generator + Vapor + PostgresNIO 构建一个事件管理服务。第三部分介绍了 Swift Server Workgroup 的组织方式和生态包的孵化流程。

演示部分很有说服力。从一个 OpenAPI YAML 定义出发,通过 Swift OpenAPI Generator 自动生成类型安全的 server/client 代码,再用 Vapor 作为 HTTP 传输层,PostgresNIO 作为数据库驱动,整个过程不到半小时就搭起了一个可运行的服务。PostgresNIO 1.21 新增的 PostgresClient 提供了内置连接池,利用结构化并发实现网络故障自动恢复。

值得深挖的点

PostgresNIO 的连接池与结构化并发

PostgresNIO 1.21 引入的 PostgresClient 不只是一个数据库驱动,它是一个完整的连接管理方案。内置连接池会自动预热连接、在多个连接间分发查询、在网络故障时利用结构化并发进行重连。

Session 中展示的使用模式是:创建一个 PostgresClient 实例,用 discarding task group 同时运行 PostgresClient 和 Vapor application。run() 方法会阻塞当前任务直到客户端结束,所以需要用 task group 来并行管理多个长期运行的服务。这个模式和传统服务端框架中”阻塞主线程等待数据库连接”的方式完全不同,体现了 Swift 并发模型的优势——你可以用结构化的方式管理多个并发的生命周期。

Swift OpenAPI Generator 的声明式服务开发

Swift OpenAPI Generator 的思路和 TypeScript 生态的 OpenAPI 工具链类似:用一份 YAML 定义 API 规格,自动生成 Swift 的类型安全代码。生成的代码包括请求/响应的类型定义和协议接口,你只需要实现协议方法即可。

这种模式的好处是 API 定义成为 single source of truth。前后端(或客户端和服务端)可以共享同一份 OpenAPI 定义,通过代码生成确保类型完全匹配。当 API 需要变更时,修改 YAML 后重新生成代码,编译器会指出所有需要更新的地方。这比手动维护两边的模型类型要可靠得多。

代码片段

场景一:用 OpenAPI 定义和实现事件服务

# openapi.yaml
openapi: "3.0.3"
info:
  title: EventService
  version: "1.0"
paths:
  /events:
    get:
      operationId: listEvents
      responses:
        "200":
          description: 事件列表
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Event"
    post:
      operationId: createEvent
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event"
      responses:
        "201":
          description: 创建成功
        "400":
          description: 请求无效
components:
  schemas:
    Event:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        attendees:
          type: array
          items:
            type: string
# 坑点:YAML 缩进错误会导致生成代码失败
# 建议用 OpenAPI 编辑器工具(如 Swagger Editor)验证后再生成

场景二:实现生成的 APIProtocol

import OpenAPIRuntime
import OpenAPIVapor

struct EventServiceHandler: APIProtocol {
    let postgresClient: PostgresClient

    // 实现 listEvents 方法
    func listEvents(
        _ input: Operations.listEvents.Input
    ) async throws -> Operations.listEvents.Output {
        // 使用 PostgresClient 查询数据库
        var events: [Components.Schemas.Event] = []

        let rows = try await postgresClient.query(
            "SELECT id, name, attendees FROM events"
        )

        for try await row in rows {
            let id = try row.decode(String.self, at: 0)
            let name = try row.decode(String.self, at: 1)
            // 注意:AsyncSequence 会自动预取行,提升性能
            events.append(.init(id: id, name: name, attendees: []))
        }

        return .ok(.init(body: .json(events)))
    }

    // 实现 createEvent 方法
    func createEvent(
        _ input: Operations.createEvent.Input
    ) async throws -> Operations.createEvent.Output {
        guard case .json(let event) = input.body else {
            return .badRequest
        }

        try await postgresClient.query(
            "INSERT INTO events (id, name) VALUES ($1, $2)",
            [event.id, event.name]
        )

        return .created
        // 坑点:PostgresClient 的 query 方法参数需要显式类型
        // 否则可能因为类型推断歧义导致编译错误
    }
}

场景三:用 Task Group 并行运行服务和数据库客户端

import Vapor
import PostgresNIO

@main
struct Server {
    static func main() async throws {
        // 创建 PostgresClient
        let postgresClient = PostgresClient(
            host: "localhost",
            port: 5432,
            username: "postgres",
            database: "events",
            password: "password"
        )

        // 创建 Vapor 应用和传输层
        let app = Vapor.Application()
        let transport = VaporTransport(routesBuilder: app)

        // 注册服务处理器
        let handler = EventServiceHandler(postgresClient: postgresClient)
        try handler.registerHandlers(on: transport, serverURL: URL(string: "/")!)

        // 使用 discarding task group 并行运行
        try await withDiscardingTaskGroup { group in
            // 子任务 1:运行 PostgresClient
            group.addTask {
                try await postgresClient.run()
                // run() 方法会阻塞直到客户端关闭
            }

            // 子任务 2:运行 Vapor HTTP 服务器
            group.addTask {
                try await app.execute()
            }
        }
        // 坑点:两个子任务中任何一个出错都会触发整个 group 退出
        // 需要确保错误处理逻辑正确
    }
}

最佳实践

  • 迁移建议:如果你在考虑用 Swift 写服务端,从 OpenAPI Generator + Vapor 这个组合开始。OpenAPI 定义文件可以作为 API 文档和代码生成的双用途资产,投入产出比很高。PostgresNIO 是 Apple 和 Vapor 团队共同维护的,可以放心在生产环境使用。
  • 编辑器选择:Swift 服务端开发不限于 Xcode。VS Code + Swift 扩展、Neovim 等 LSP 兼容编辑器都能获得完整的代码补全和调试体验。Session 中的演示全程使用 VS Code。
  • 包管理:遵循 Swift Server Workgroup 的孵化流程,优先选择经过孵化认证的包。这些包在 API 兼容性、文档质量和长期维护方面有更好的保障。

还有什么值得关注

  • Swift Server Workgroup 成立于 2016 年,是 Swift 社区最早的工作组,生态成熟度比很多人想象的要高
  • Private Cloud Compute 是 Apple 全新的云服务架构,完全用 Swift 构建,代表了对 Swift 服务端能力的最高级别信任
  • Swift Package Index 可以浏览所有经过审核的服务端相关包,是选型时的好起点
WWDC 2024