从创建论坛版块开始

Vapor Fluent

预计阅读时间: 30分钟

在这个系列里,我们会实现一个类似论坛的App。通过这个过程,来了解Vapor和数据库进行交互的ORM系统的基本用法。

废话不多说,本着一贯Learn by doing的原则,我们就从创建项目开始。

创建项目

实际上,我们需要两个项目。一个是前端的App,另一个则是服务端的Vapor。显然,我们应该从后端开始。在之前创建的Docker环境里,在projects目录执行vapor new bx-forum新建一个Vapor项目。完成后,由于这次我们要和数据库打交道,因此,在bx-forum/Package.swift中,添加下面的内容:

import PackageDescription

let package = Package(
    name: "bx-forum",
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),

        .package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0"),
        .package(url: "https://github.com/vapor/crypto.git", from: "3.0.0")
    ],
    targets: [
        .target(name: "App", dependencies: ["Vapor", "FluentMySQL", "Crypto"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

和上一个例子相比,我们添加了fluent-mysqlcrypto这两个依赖,前者是Vapor的ORM,让我们可以用Swift的方式使用数据库,后者则包含了常用的加密算法,在处理用户密码的时候,我们会用到它。

完成后,执行vapor xcode -y生成Xcode项目,并用AppCode或者Xcode打开。接下来,我们需要对Vapor进行一些设置。

配置Vapor访问MySQL

打开Sources/App/configure.swift,在configure函数的一开始,找到try services.register(FluentSQLiteProvider()),把它改成:

try services.register(FluentMySQLProvider())

这样我们才可以在Vapor中使用MySQL服务。接下来,在configure函数末尾,找到下面这两行代码:

// Configure a SQLite database
let sqlite = try SQLiteDatabase(storage: .memory)

把这两行一直到文件末尾的代码都去掉,Vapor默认使用的SQLite,而我们要添加MySQL的配置。

首先,创建一个DatabasesConfig对象,我们可以把它理解为Vapor要访问的所有数据库的集合:

var databases = DatabasesConfig()

很显然,现在这还是个空集合。

其次,我们创建一个用于连接MySQL的配置,这是一个MySQLDatabaseConfig对象。要连接的数据库,就是我们之前启动的MySQL容器:

let mysqlConfig = MySQLDatabaseConfig(
    hostname: "mysql",
    port: 3306,
    username: "vapor",
    password: "vapor",
    database: "vapor",
    transport: .unverifiedTLS)

这里:

  • hostname是MySQL服务器的主机名,之所以能设置成mysql,是因为我们在docker-compose.yml中,把MySQL容器的名称设置成了mysql;
  • port是远程连接MySQL时的端口号。要注意的是,这是我们通过Vapor容器去访问MySQL容器,因此,不要使用我们映射到Host上的33060,而是MySQL默认的3306;
  • username / password / database分别是连接数据库时使用的用户名、密码以及要连接的数据库名称;
  • transport:则是连接MySQL容器时使用的安全连接选项。对于MySQL 8,暂时我们只能传递.unverifiedTLS

有了这个连接配置之后,我们用它创建一个MySQLDatabase对象,表示我们要连接的数据库:

let mysqlDB = MySQLDatabase(config: mysqlConfig)

第三,我们把mysqlDB添加到之前创建的databases

databases.add(database: mysqlDB, as: .mysql)

最后,在Vapor里注册这个databases对象:

services.register(databases)

这样,Vapor就可以连接到MySQL数据库了。

创建第一个Model

配置好之后,我们来创建第一个Model,它表示论坛的每一个板块。为此,我们先删掉Sources/App/Model目录中默认的Todo.swift,然后,添加一个Forum.swift,在其中,添加下面的代码:

import Vapor
import Fluent
import Foundation
import FluentMySQL

struct Forum: Content, MySQLModel, Migration {
    var id: Int?
    var name: String
}

这里,有三处是新东西:

  • 首先,是import Fluentimport FluentMySQL,这两个模块是Vapor的ORM,也就是访问数据库的接口;
  • 其次,是MySQLModel,这个protocol约定了把Swift数据结构存储到MySQL必备的接口;
  • 最后,是Migration,它帮助我们在MySQL中存储Swift数据结构进行必要的准备工作。其中最常见的,就是在数据库中创建或修改表;

Forum的定义里,idname分别表示论坛版块的ID和名称。这时,你可能会想了,为什么id要定义成Int?呢?实际上,这是Migration中的一个约束,等我们稍后讨论migrationi的两种不同方式的时候,大家就明白了,现在先这么用就好。

创建第一个Migration

有了Model之后,为了把它保存到MySQL,我们得在MySQL中创建一个与之对应的表。这个从Model到表结构的对应过程,就叫做Migration。为了创建migrations,这里,和大家分享一个我习惯的做法。

首先,在Sources/App中,新建一个Migrations group。我们可以把所有对表的操作,都放在这里,方便统一管理。

其次,在Migrations group中,新建一个18-09-28-CreateForumTable.swift的文件。这个文件名使用了YY-MM-DD-Action-TableName的形式。这样做有两个主要的好处:

  • 用时间开头,可以方便我们回顾数据库的操作过程;
  • 通过文件名的后半部分,可以快速了解每一个migration完成的动作;

例如,之后,我们要修改Forum表,就可以用类似18-10-11-AddTagColumnToForumTable.swift这样的文件名。

完成后,我们在18-09-28-CreateForumTable.swift中,添加下面的代码:

import Fluent
import FluentMySQL

struct CreateForumTable: Migration {

}

这里,创建的struct名称最好和migration文件的后半部分是相同的。可以看到,我们让它遵从了protocol Migration。只要实现它约束的方法,Vapor就可以根据我们指定的Model创建表了。

怎么做呢?

首先,我们要定义一个别名:

struct CreateForumTable: Migration {
    typealias Database = MySQLDatabase
}

MigrationDatabase抽象了数据库类型,而我们要用一个具象的类型来定义它。

其次,实现prepare(on:)方法。在这里,我们编写把数据结构映射成MySQL表的逻辑:

struct CreateForumTable: Migration {
    /// ...

    static func prepare(
        on connection: Database.Connection) -> Future<Void> {
        return Database.create(Forum.self, on: connection) {
            builder in
            builder.field(for: \.id, isIdentifier: true)
            builder.field(for: \.name)
        }
    }
}

它的参数connection表示MySQL数据库的连接,Vapor会传递给我们这个参数。在它的实现里,我们直接使用Database.create()方法创建表。它接受三个参数:

  • 第一个参数,表示建表时要参考的Model,我们当然应该传递Forum.self
  • 第二个参数on表示使用的数据库连接,我们传递connection就好了;
  • 第三个参数是一个closure,这个closure接受一个SchemaCreator类型的参数。我们暂且不用关心这个类型本身,只要知道如何用它定义表规格就好。对于Forum中的每一个属性,MySQL中我们都要用单独一列来存储。为此,我们使用field(for:)方法,把Forum每一个属性的KeyPath传递给它,这样Vapor就会自动推导出字段的类型,并创建类型正确的MySQL表了。另外,对于表中作为主键的字段,调用field的时候,要把它的isIdentifier参数设置成true

这样,Vapor就可以根据Forum定义,生成对应的MySQL表了,而表名同样也是Forum。另外,可以看到prepare返回Future<Void>表示:反正在未来的某个时候,它一定会执行完,我们就不用操心结果了。

最后,我们还要定义revert(on:)方法,它用于撤销prepare完成的操作。对我们的例子来说,就是删掉Forum表:

struct CreateForumTable: Migration {
    /// ...

    static func revert(
        on connection: Database.Connection) -> Future<Void> {
        return Database.delete(Forum.self, on: connection)
    }
}

可以看到,它的参数和返回值和prepare是相同的。在它的实现里,我们直接使用Database.delete方法在connection连接上,删掉和Forum对应的表就好了,很简单。

至此,我们的第一个migration也就创建完成了。

注册migration

完成后,为了可以让Vapor执行migration。我们要在configure函数中添加下面的代码:

var migrations = MigrationConfig()
migrations.add(
    migration: CreateForumTable.self,
    database: .mysql)
services.register(migrations)

从字面上,就很容易理解这段代码的含义。我们先创建了一个MigrationConfig对象,可以把它理解成是Vapor要执行的所有migrations的集合。然后,用migrations.add方法把我们之前创建过的CreateForumTable添加到集合。最后,再用register方法把这个MigrationConfig对象注册到Vapor里。整体上,这个过程和我们一开始注册MySQLDatabaseConfig是类似的。

给Vapor添加fluent命令

接下来,为了可以通过Vapor命令行处理数据库,我们还要在configure函数中注册Fluent中实现的命令,这个过程,和之前注册数据库以及migration是类似的:

var commandConfig = CommandConfig.default()
commandConfig.useFluentCommands()
services.register(commandConfig)

执行Migration

至此,我们所有的准备工作就都结束了。在Vapor容器中,我们执行vapor build编译一下。如果一切顺利,应该不会有错误。由于我们刚才添加了Fluent command,现在Vapor的命令行就多出了两个命令:

  • 一个是vapor run migrate,执行后,Vapor就会按照添加到MigrationConfig中migration的顺序,依次执行每一个migration中prepare方法定义的逻辑;这样,我们的数据库中就有一个和struct Forum对应的表了;
  • 另外一个,是vapor run revert,这是migrate命令的逆操作,用于撤销最近的一次migration。执行后,Vapor会让我们再次确认,输入y,Vapor就会执行最近一次migration中的revert方法,在我们的例子中,之前创建的Forum表就被删除了;

如果我们要撤销所有的migrations,而不是最近的一次,还可以执行vapor run migration -all

What's next?

以上,就是这个论坛应用的开始。我们修改了连接数据库的配置、创建了Model、创建并执行了Migration。继续之前,在下一节里,我们先整理下configure中的代码,把用户名、密码等敏感信息从代码中去掉。

关于我们

想循序渐进的跟上最新的技术趋势?想不为了学点东西到处搜索?想找个伙伴一起啃原版技术经典书?技术之外,还想了解高效的工作流技巧?甚至,工作之余,想找点儿东西放松心情?没问题,我们用4K开发视频,配以详尽的技术文档,以及精心准备的广播节目,让你渴望成长的技术需求,也是一种享受。

Email Address

10@boxue.io

客户服务

2085489246

关注我们

在任何你常用的社交平台上关注我们,并告诉我们你的任何想法和建议!

邮件列表

订阅泊学邮件列表以了解泊学视频更新以及最新活动,我们不会向任何第三方公开你的邮箱!

2019 © All Rights Reserved. Boxue is created by 10 11.