我又新写了一款 Swift 服务端框架,这次是基于 Vapor 4 - 技术博文 - 平行宇宙

我又新写了一款 Swift 服务端框架,这次是基于 Vapor 4

    2023-07-03 00:42
    




    767
    




    13
    




    3

引言

我在四年前发过一篇博客:「回首三年Swift后端之旅,今年我用Swift写前端了」。在这篇文章中,我介绍了当时我最新写的 Swift 后端框架和前端框架。

已经有几年没有更新博客的技术栈了,今天我会在这篇文章里做一点更新。

回顾一下过去

1.0 时代

2016 年,这是一个相当蛮荒的时代。

当时 Swift 刚刚开源不久,在 Linux 上运行可谓是遍地是坑。我当时使用的框架是Perfect,这是个相当“渐进式”的框架,使用了大量 C 接口来让整个框架可以跑起来。

由于整个项目是出于探索 Swift for Linux 的意图,在网页方面我用非常原始地用 Bootstrap / JQuery 简单写了一个就匆匆上线跑着了。

当年甚至还别出心裁地做了个线上可以跑代码的平台:

2.0 时代

2017 年,我的北京元年,我来到了北京加入了百度。

我在前一份工作中接触了 Python 著名后端框架Django。我基于Django的业务抽象,在Perfect提供的能力上搭了一个有业务能力的框架出来,取名为Pjango

之后我也在这个框架的基础上重写了网页,并一起上线了重写后的新博客。

3.0 时代

2019 年,旧世界末年(指新冠前)。

Pjango虽然拥有了一定的业务抽象,但过分参考了 Python 的设计,在诸如环境变量等等许多写法上非常不 Swifty,于是这一年我开启了新的计划:Project Virgo

在这个计划中我重新设计了前后端,两个项目分别为:Heze(后端) 和SPiCa(前端)

这篇博客:「回首三年Swift后端之旅,今年我用Swift写前端了」就是在发布这个项目之后写的。

小知识:

这个计划的命名灵感来自于初音未来的《SPiCa》,于是我找到了一些相关的内容来组成这个项目的名称。在“关于”页面里的头像用的也就是这首歌的图。

Virgo:室女座。选择个星座是因为 SPiCa 属于这个星座。

SPiCa:室女座 α 星,角宿一,是室女座的最亮星。

Heze:室女座 ζ 星,角宿二。

这个项目极具创意:

  • Heze 的数据库驱动能一行代码不写就把一条记录渲染成 JSON,并提供了自定义字段等等能力,以及巧妙地利用 GCD 实现了许多异步操作。
  • SPiCa 则是作为代码生成器,网页端的 Vue 代码是由她的 Swift 代码生成的。截止到这篇文章发布位置,读者你看到的网页仍然是SPiCa生成的。

然后呢?

Heze虽然拥有一些相对先进的能力,但是地基依然是 Perfect。而很不幸的是,这个项目已经没有人维护了。

在那个年代,Swift 在 Linux 和 macOS 上仍然由许多不同的行为。Swift 近几年 Feature 井喷,随着版本迭代,代码甚至出现了编译问题,我不得不维护了两个分支:

  • Linux 分支:仍然停留在 Swift 4.2。
  • macOS 分支:支持到最新 Swift / Xcode。

我曾经好几次想把 Swift 版本 bump 上去,都失败了。

随着async/await等 Feature 在 Swift 新版本上出现,我们可能需要考虑使用新的异步 API 来实现了。可能真的是时候让 Perfect 退出了。

回到现在

2022 年底,我开始了新的计划:Helios

这个计划会重写整个服务端框架,使用最新的 Vapor 4 以及最新的 Swift 版本进行开发。完全切到异步 API 上。

Vapor 4 的数据库驱动能够使用KeyPath写出结构化的 SQL 查询语句和 Model 查询方法,还是挺炫酷的。

小剧场:

这个命名灵感来自于米哈游《崩坏3》的早期(2016 年)的开局 PV。这个 PV 现在已经没有了,但能在视频网站上搜到别人的录像。

剧情是琪亚娜从空中跳下着陆在 Helios 号运输舰上。嗯,没别的,就只是喜欢 Helios 这个名字罢了。

那么现在就来具体看一下 Helios 的设计。

路由

路由的设计基本照抄了Heze的设计,用Path: Method: Handler的层级来分发路由。

func routes(app: HeliosApp) -> [String : [HTTPMethod : HeliosHandlerBuilder]] {
    return [
        // Posts list
        "/api/posts/list": [
            .GET: PostsListApi.builder,
        ],
    ]
}

Swift

一个简单的查库 + 渲染的 API:

import Foundation
import Vapor
import Helios

class PostsListApi: HeliosHandler {

    required init() { }

    func handle(req: Request) async throws -> AsyncResponseEncodable {
        let postsList = try await PostsInfoModel.query(on: req.db)
            .sort(\PostsInfoModel.$date, .descending)
            .all()
        for posts in postsList {
            try await posts.preRender(db: req.db)
        }
        return jsonResponse(data: postsList.map { $0.render() })
    }
}

Swift

在 Vapor 4 的数据库驱动中,where, on, order等操作都可以用KeyPath来操作,以写出非常 Swifty 的结构化代码。

在 Vapor 4 中,为了返回符合AsyncResponseEncodable协议的对象进行渲染,需要通过额外定义遵循Content协议的结构来实现。这里引入了一些学习成本,我就直接放代码了:

struct HeliosHandlerResponse<T: Content>: Codable, Content {
    let success: Bool
    let msg: String
    let data: T
}

extension HeliosHandler {

    func jsonResponse<T: Content>(data: T) -> AsyncResponseEncodable {
        let response = HeliosHandlerResponse(
            success: true,
            msg: "nil",
            data: data)
        return response
    }
}

Swift

数据库

Vapor 的数据库驱动还是非常全的。

Heze的数据库驱动是我自己写的,我甚至连事务都懒得实现,但在 Vapor 里这些都是现成的。

模型的注册也是照抄了Heze的设计。

func models(app: HeliosApp) -> [HeliosAnyModelBuilder] {
    return [
        /