コンテンツにスキップ

Fluent

Fluent は、Swift 用の ORM フレームワークです。Swift の強力な型システムを活用して、データベースとのインターフェースを簡単に使用できるようにします。Fluent の使用は、データベース内のデータ構造を表すモデルタイプの作成に中心を置いています。これらのモデルを使用して、生のクエリを書く代わりに、作成、読み取り、更新、および削除操作を行います。

設定

vapor new を使用してプロジェクトを作成する際に、Fluent を含めるかどうかを尋ねられたら「yes」と答え、使用するデータベースドライバーを選択してください。これにより、新しいプロジェクトに依存関係が自動的に追加され、例として設定コードも含まれます。

既存プロジェクトへの追加

既存のプロジェクトに Fluent を追加したい場合は、パッケージ に 2 つの依存関係を追加する必要があります:

  • vapor/fluent@4.0.0
  • 使用したいデータベースの Fluent ドライバー 1 つ以上
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-<db>-driver.git", from: <version>),
.target(name: "App", dependencies: [
    .product(name: "Fluent", package: "fluent"),
    .product(name: "Fluent<db>Driver", package: "fluent-<db>-driver"),
    .product(name: "Vapor", package: "vapor"),
]),

依存関係を追加したら、configure.swiftapp.databases を使用してデータベースを設定します。

import Fluent
import Fluent<db>Driver

app.databases.use(<db config>, as: <identifier>)

以下に Fluent ドライバーごとの、より具体的な設定手順を記しています。

Drivers

Fluent には現在、公式にサポートされているドライバーが 4 つあります。公式およびサードパーティの Fluent データベースドライバーの完全なリストについては、GitHub で fluent-driver タグを検索してください。

PostgreSQL

PostgreSQL は、オープンソースで、標準 SQL に準拠したデータベースです。ほとんどのクラウドホスティングプロバイダーで簡単に設定できます。これは、Fluent の 推奨 データベースドライバーです。

PostgreSQL を使用するには、次の依存関係をパッケージに追加します。

.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0")
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver")

依存関係を追加したら、configure.swift で Fluent を使用してデータベースのクレデンシャル情報を設定します。

import Fluent
import FluentPostgresDriver

app.databases.use(
    .postgres(
        configuration: .init(
            hostname: "localhost",
            username: "vapor",
            password: "vapor",
            database: "vapor",
            tls: .disable
        )
    ),
    as: .psql
)

データベース接続文字列からクレデンシャル情報を解析することもできます。

try app.databases.use(.postgres(url: "<connection string>"), as: .psql)

SQLite

SQLite は、オープンソースの組み込み型 SQL データベースです。そのシンプルさから、プロトタイピングやテストに最適です。

SQLite を使用するには、次の依存関係をパッケージに追加します。

.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0")
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver")

依存関係を追加したら、configure.swiftapp.databases を使用してデータベースを設定します。

import Fluent
import FluentSQLiteDriver

app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)

SQLite を設定して、データベースを一時的にメモリに保存することもできます。

app.databases.use(.sqlite(.memory), as: .sqlite)

メモリ内データベースを使用する場合は、--auto-migrate を使用して Fluent を自動的にマイグレートするよう設定するか、マイグレーションを追加した後に app.autoMigrate() を実行してください。

app.migrations.add(CreateTodo())
try app.autoMigrate().wait()
// or
try await app.autoMigrate()

Tip

SQLite の設定では、作成されたすべての接続で外部キー制約が自動的に有効になりますが、データベース内の外部キー設定は変更されません。データベースで直接レコードを削除すると、外部キー制約やトリガーに違反する可能性があります。

MySQL

MySQL は、人気のあるオープンソースの SQL データベースです。多くのクラウドホスティングプロバイダーで利用可能です。このドライバーは MariaDB もサポートしています。

MySQL を使用するには、次の依存関係をパッケージに追加します。

.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0")
.product(name: "FluentMySQLDriver", package: "fluent-mysql-driver")

依存関係を追加したら、configure.swiftapp.databases を使用してデータベースのクレデンシャル情報を設定します。

import Fluent
import FluentMySQLDriver

app.databases.use(.mysql(hostname: "localhost", username: "vapor", password: "vapor", database: "vapor"), as: .mysql)

データベース接続文字列からクレデンシャル情報を解析することもできます。

try app.databases.use(.mysql(url: "<connection string>"), as: .mysql)

SSL 証明書を使用せずにローカル接続を構成する場合は、証明書の検証を無効にする必要があります。たとえば、Docker 内の MySQL 8 データベースに接続する場合などです。

var tls = TLSConfiguration.makeClientConfiguration()
tls.certificateVerification = .none

app.databases.use(.mysql(
    hostname: "localhost",
    username: "vapor",
    password: "vapor",
    database: "vapor",
    tlsConfiguration: tls
), as: .mysql)

Warning

本番環境では証明書の検証を無効にしないでください。検証に使用する証明書を TLSConfiguration に提供する必要があります。

MongoDB

MongoDB は、プログラマー向けに設計された人気のあるスキーマレス NoSQL データベースです。このドライバーは、バージョン 3.4 以降のすべてのクラウドホスティングプロバイダーおよびセルフホストインストールをサポートしています。

Note

このドライバーは、コミュニティによって作成およびメンテナンスされている MongoDB クライアント MongoKitten によって動作しています。MongoDB は、公式クライアントの mongo-swift-driver と、Vapor との統合 mongodb-vapor をメンテナンスしています。

MongoDB を使用するには、次の依存関係をパッケージに追加します。

.package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.0.0"),
.product(name: "FluentMongoDriver", package: "fluent-mongo-driver")

依存関係を追加したら、configure.swiftapp.databases を使用してデータベースのクレデンシャル情報を設定します。

接続するには、標準の MongoDB 接続 URI 形式 で接続文字列を渡します。

import Fluent
import FluentMongoDriver

try app.databases.use(.mongo(connectionString: "<connection string>"), as: .mongo)

Models

モデルは、データベース内の固定データ構造(テーブルやコレクションなど)を表します。モデルには、Codable な値を格納する 1 つ以上のフィールドがあります。すべてのモデルには一意の識別子もあります。識別子やフィールド、また後で説明するより複雑なマッピングを示すためにプロパティラッパーが使用されます。次の例では、Galaxy(銀河)を表すモデルを示しています。

final class Galaxy: Model {
    // テーブルまたはコレクションの名前
    static let schema = "galaxies"

    // Galaxy の一意の識別子
    @ID(key: .id)
    var id: UUID?

    // 銀河の名前
    @Field(key: "name")
    var name: String

    // 空の Galaxy インスタンスを作成
    init() { }

    // すべてのプロパティが設定された新しい Galaxy インスタンスを作成
    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

新しいモデルを作成するには、Model に準拠する新しいクラスを作成します。

Tip

パフォーマンスを向上させ、準拠要件を簡素化するために、モデルクラスを final としてマークすることをお勧めします。

Model プロトコルの最初の要件は、静的な文字列 schema です。

static let schema = "galaxies"

このプロパティは、モデルが対応するテーブルまたはコレクションを Fluent に示します。これは、データベース内に既に存在するテーブルであるか、マイグレーション を使用して作成するテーブルです。スキーマは通常、snake_case であり、複数形です。

Identifier

次の要件は、id という名前の識別子フィールドです。

@ID(key: .id)
var id: UUID?

このフィールドは、@ID プロパティラッパーを使用する必要があります。Fluent は、すべてのドライバーと互換性があるため、UUID と特別な .id フィールドキーを使用することをお勧めします。

キー名や型をカスタムしたい場合は、@ID(custom:) オーバーロードを使用します。

Fields

識別子を追加したら、追加情報を保存するためのフィールドを必要なだけ追加できます。この例では、追加のフィールドは銀河の名前だけです。

@Field(key: "name")
var name: String

単純なフィールドの場合、@Field プロパティラッパーが使用されます。@ID と同様に、key パラメータはデータベース内のフィールドの名前を指定します。これは、データベースのフィールド命名規則が Swift と異なる場合、たとえば camelCase ではなく snake_case を使用する場合に特に役立ちます。

次に、すべてのモデルには空の init が必要です。これにより、Fluent はモデルの新しいインスタンスを作成できます。

init() { }

最後に、モデルを便利に使用するためのすべてのプロパティを設定できる init を追加できます。

init(id: UUID? = nil, name: String) {
    self.id = id
    self.name = name
}

利便性用の init を使用しておくと、モデルに新たなプロパティを追加した場合に、init メソッドが変更されるとコンパイル時にエラーが発生することが特に便利です。

Migrations

データベースが SQL データベースのように事前定義されたスキーマを使用している場合、モデルの準備をするためにマイグレーションが必要です。マイグレーションは、データベースにデータをシードするためにも役立ちます。マイグレーションを作成するには、Migration または AsyncMigration プロトコルに準拠する新しいタイプを定義します。次に、前述の Galaxy モデルに対応するマイグレーションを示します。

struct CreateGalaxy: AsyncMigration {
    // Galaxy モデルを格納するためのデータベースの準備
    func prepare(on database: Database) async throws {
        try await database.schema("galaxies")
            .id()
            .field("name", .string)
            .create()
    }

    // 必要に応じて、prepare メソッドで行った変更を元に戻します
    func revert(on database: Database) async throws {
        try await database.schema("galaxies").delete()
    }
}

prepare メソッドは、Galaxy モデルを格納するためにデータベースを準備するために使用されます。

Schema

このメソッドでは、database.schema(_:) を使用して新しい SchemaBuilder を作成します。その後、ビルダーに 1 つ以上の field を追加して create() を呼び出してスキーマを作成します。

ビルダーに追加される各フィールドには、名前、タイプ、およびオプションの制約があります。

field(<name>, <type>, <optional constraints>)

Fluent の推奨デフォルトを使用して @ID プロパティを追加するための便利な id() メソッドがあります。

マイグレーションを revert すると、prepare メソッドで行った変更が元に戻されます。この場合、Galaxy のスキーマが削除されます。

マイグレーションが定義されたら、それを configure.swiftapp.migrations に追加して Fluent に知らせる必要があります。

app.migrations.add(CreateGalaxy())

Migrate

マイグレーションを実行するには、コマンドラインから swift run App migrate を実行するか、Xcode の App スキームに migrate を引数として追加します。

$ swift run App migrate
Migrate Command: Prepare
The following migration(s) will be prepared:
+ CreateGalaxy on default
Would you like to continue?
y/n> y
Migration successful

Querying

モデルを正常に作成し、データベースをマイグレートしたので、最初のクエリを実行する準備が整いました。

All

データベース内のすべての Galaxy の配列を返す次のルートを見てみましょう。

app.get("galaxies") { req async throws in
    try await Galaxy.query(on: req.db).all()
}

route 関数内で Galaxy を直接返すには、Content に準拠させます。

final class Galaxy: Model, Content {
    ...
}

Galaxy.query を使用して、モデルの新しいクエリビルダーを作成します。req.db はアプリケーションのデフォルトデータベースへの参照です。最後に、all() はデータベースに格納されているすべてのモデルを返します。

プロジェクトをコンパイルして実行し、GET /galaxies をリクエストすると、空の配列が返されるはずです。次に、新しい銀河を作成するためのルートを追加しましょう。

Create

RESTful の慣例に従い、新しい銀河を作成するには、POST /galaxies エンドポイントを使用します。モデルは Codable なので、リクエストボディから Galaxy を直接デコードできます。

app.post("galaxies") { req -> EventLoopFuture<Galaxy> in
    let galaxy = try req.content.decode(Galaxy.self)
    return galaxy.create(on: req.db)
        .map { galaxy }
}

Seealso

リクエストボディのデコードに関する詳細は、コンテンツ → 概要 を参照してください。

モデルのインスタンスを取得したら、create(on:) を呼び出してモデルをデータベースに保存します。これにより、保存が完了したことを示す EventLoopFuture<Void> が返されます。保存が完了したら、map を使用して新しく作成されたモデルを返します。

async/await を使用している場合、次のようにコードを書くことができます。

app.post("galaxies") { req async throws -> Galaxy in
    let galaxy = try req.content.decode(Galaxy.self)
    try await galaxy.create(on: req.db)
    return galaxy
}

この場合、async バージョンは何も返しませんが、保存が完了すると返されます。

プロジェクトをビルドして実行し、次のリクエストを送信します。

POST /galaxies HTTP/1.1
content-length: 21
content-type: application/json

{
    "name": "Milky Way"
}

応答として、作成されたモデルが識別子付きで返されるはずです。

{
    "id": ...,
    "name": "Milky Way"
}

次に、再び GET /galaxies をクエリすると、新しく作成された Galaxy が配列の中に入って返されるはずです。

Relations

銀河には星が欠かせません! 次に、Galaxy と新しい Star モデルとの間に 1 対多のリレーションを追加することで、Fluent の強力なリレーショナル機能をざっくり見てみましょう。

final class Star: Model, Content {
    // テーブルまたはコレクションの名前
    static let schema = "stars"

    // この Star の一意の識別子
    @ID(key: .id)
    var id: UUID?

    // 星の名前
    @Field(key: "name")
    var name: String

    // この星が属する銀河への参照
    @Parent(key: "galaxy_id")
    var galaxy: Galaxy

    // 空の Star インスタンスを作成
    init() { }

    // すべてのプロパティが設定された新しい Star インスタンスを作成
    init(id: UUID? = nil, name: String, galaxyID: UUID) {
        self.id = id
        self.name = name
        self.$galaxy.id = galaxyID
    }
}

Parent

新しい Star モデルは Galaxy と非常に似ていますが、新しいフィールドタイプ @Parent が追加されています。

@Parent(key: "galaxy_id")
var galaxy: Galaxy

親プロパティは、別のモデルの識別子を格納するフィールドです。参照を保持するモデルは「子」と呼ばれ、参照されるモデルは「親」と呼ばれます。この種類のリレーションは「一対多」とも呼ばれます。プロパティに渡される key パラメータは、データベース内で親のキーを格納するために使用されるフィールド名を指定します。

init メソッドでは、$galaxy を使用して親の識別子が設定されます。

self.$galaxy.id = galaxyID

親プロパティ名の前に $ を付けることで、基になるプロパティラッパーにアクセスします。これは、実際の識別子の値を格納する内部の @Field にアクセスするために必要です。

Seealso

プロパティラッパーに関する詳細については、Swift Evolution の提案 [SE-0258] Property Wrappers を参照してください。

次に、Star を処理するためのデータベースを準備するためにマイグレーションを作成します。

struct CreateStar: AsyncMigration {
    // Star モデルを格納するためのデータベースの準備
    func prepare(on database: Database) async throws {
        try await database.schema("stars")
            .id()
            .field("name", .string)
            .field("galaxy_id", .uuid, .references("galaxies", "id"))
            .create()
    }

    // 必要に応じて、prepare メソッドで行った変更を元に戻します
    func revert(on database: Database) async throws {
        try await database.schema("stars").delete()
    }
}

これは、主に Galaxy のマイグレーションと同じですが、親の Galaxy の識別子を格納する追加のフィールドがある点が異なります。

field("galaxy_id", .uuid, .references("galaxies", "id"))

このフィールドは、データベースにこのフィールドの値が「galaxies」スキーマの「id」フィールドを参照していることを伝えるオプションの制約を指定します。これは外部キーとも呼ばれ、データの整合性を確保するのに役立ちます。

マイグレーションが作成されたら、それを CreateGalaxy マイグレーションの後に app.migrations に追加します。

app.migrations.add(CreateGalaxy())
app.migrations.add(CreateStar())

マイグレーションは順番に実行され、CreateStar が銀河のスキーマを参照しているため、順序が重要です。最後に、データベースを準備するために マイグレーションを実行 します。

新しく Star を作成するためのルートを追加します。

app.post("stars") { req async throws -> Star in
    let star = try req.content.decode(Star.self)
    try await star.create(on: req.db)
    return star
}

次の HTTP リクエストを使用して、先に作成した Galaxy を参照する新しい Star を作成します。

POST /stars HTTP/1.1
content-length: 36
content-type: application/json

{
    "name": "Sun",
    "galaxy": {
        "id": ...
    }
}

一意の識別子付きで新しく作成された Star が返されるはずです。

{
    "id": ...,
    "name": "Sun",
    "galaxy": {
        "id": ...
    }
}

Children

次に、Fluent の eager-loading 機能を活用して、GET /galaxies ルートで銀河の星も自動的に返す方法を見てみましょう。Galaxy モデルに次のプロパティを追加します。

// この Galaxy に存在するすべての Star
@Children(for: \.$galaxy)
var stars: [Star]

@Children プロパティラッパーは、@Parent の逆です。for 引数として子の @Parent フィールドへの key-path を受け取ります。その値は子モデルの配列で、ゼロ個以上の子モデルが存在する可能性があります。このリレーションを完了するために、Galaxy のマイグレーションに変更を加える必要はありません。

Eager Load

リレーションが完了したら、クエリビルダーの with メソッドを使用して、銀河と星のリレーションを自動的にフェッチしてシリアライズできます。

app.get("galaxies") { req in
    try await Galaxy.query(on: req.db).with(\.$stars).all()
}

@Children リレーションへの key-path が with に渡され、Fluent が自動的にこのリレーションをすべての結果モデルでロードするよう指示します。ビルドして実行し、再び GET /galaxies にリクエストを送信します。Star がレスポンスに自動的に含まれるようになっているはずです。

[
    {
        "id": ...,
        "name": "Milky Way",
        "stars": [
            {
                "id": ...,
                "name": "Sun",
                "galaxy": {
                    "id": ...
                }
            }
        ]
    }
]

Query Logging

Fluent ドライバーは、デバッグログレベルで生成された SQL を記録します。FluentPostgreSQL のような一部のドライバーでは、データベースを設定するときにこれを設定することができます。

ログレベルを設定するには、configure.swift(またはアプリケーションを設定する場所)で次のコードを追加します。

app.logger.logLevel = .debug

これにより、ログレベルがデバッグに設定されます。次にアプリをビルドして実行すると、Fluent が生成した SQL ステートメントがコンソールに記録されます。

Next steps

おめでとうございます! はじめてのモデルとマイグレーションを作成し、基本的な作成と読み取り操作を実行できました。これらの機能に関する詳細な情報については、Fluent ガイドの該当セクションを参照してください。