VaporアプリケーションでAPIを作る(SQLite編)

はじめに

初めて出会ったFutureに面食らって足踏みをしてしまいましたが、気を取り直して進んでいきます。
いよいよMongoかと思わせておいて、SQLiteです。焦らしているわけではないですが、少しずつ深みにハマっていく方向で。泳ぎでもまずは水に慣れて浮き輪をつけてとかやるではないですか?あれと同じ理屈です。

パッケージの導入

VaporでSQLiteを使うにはパッケージを導入する必要があります。なんと!ありがたいことに最初の状態で導入されています。

Package.swiftDependencies

.package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0")

があればOKです。

VaporでのSQLiteはfluent-sqliteというのですね〜へぇ〜:D

データモデルを作る

データモデルを作っていきます。今回もJSONデータを扱いたいのでContentプロトコルに準拠した構造体かクラスを作ります。ただし、今回はSQLiteを使うので、importとextensionが少々必要です。

import Vapor
import FluentSQLite
struct Book: Content {
    var id:Int?
    var title: String
    var publish: String
    var price: Int
    init(title:String, publish:String, price:Int) {
        self.title = title
        self.publish = publish
        self.price = price
    }
}
extension Book:SQLiteModel {
    static let entity: String = "books"
}
extension Book:Migration {
}

MigrationプロトコルとSQLiteModelプロトコルが居ますが、「とりあえず準拠しました」的な感じで申し訳程度に添えています。荒々しく粗っぽい理解ですが、Migrationプロトコルについてはモデルが変わったりしたときに使うんでしょう。多分。Realmでよく見るマイグレーションと似た感じと理解しています。今はいらないでしょう多分。

SQLiteModelプロトコルはその名前の通りSQLiteで扱うデータモデルを表すものだと思います。上記のコードでは空っぽですが、Contentプロトコルのくくりに入れているプロパティ群を入れると結局のところ、SQLiteModelのプロパティを決めていることになります。SQLiteを使っていなかったこれまでの流れ上、Contentプロトコルのくくりに入れておくほうがわかりやすいなと思った次第です。extensionで別れているのでややこしくなっているだけです。説明もややこしい…… 伝わるかな?

entitiyはデータベースで使うエンティティ名です(小並感)。どうでもいいですが、エンティティと入力したつもりが、エンチチィになっていることがあります。これから見かけるかもしれませんが、チチィはサラッと流しておいてください。

SQLiteへ接続するための設定

これまたありがたいことに、初期状態でSQLiteに接続するための設定を行うメソッドが用意されています。これを少し修正します。

データの保存先を変更する

let sqlite = try SQLiteDatabase(storage: .memory)

SQLiteはファイルベースのデータベースです。ですが、初期状態ではメモリ上に展開することになっています。このままではアプリケーションをRunして動いている間はメモリ上にデータが保持されていますが、終了してしまえばデータが消えてしまいます。

なので、.memory.file(path: "book.sqlite")に書き換えます。ちなみにbook.sqliteになっていますが、任意のファイル名で構いません。

マイグレーションの設定を加える(直訳)

var migrations = MigrationConfig()
migrations.add(model: Book.self, database: DatabaseIdentifier<Book.Database>.sqlite)
services.register(migrations)

これはなんでしょうかね?
見た感じですが、ここでフレームワーク側にマイグレーションをよしなにしてねとお願いしているようですね。BookはMigrationプロトコルに準拠させているので、必要に応じてそのプロトコルで設定したマイグレーションを実行してくれるのでしょうおそらく。

ところで、トリガーは一体なんでしょうか?Realmのときはバージョンを指定して自動的にマイグレーションしてくれましたが。必要なときに調べてみます。

新しい本の情報を追加してみる

この段階で実行してみると、スッピンのデータベースファイルができました。なんてお手軽。早速、本のデータを追加してみます。

router.post(Book.self, at: "api/book") { (req, book) -> Future<Book> in
        return book.save(on: req)
}

BookがSQLiteModelプロトコルに準拠しているため、何もしなくてもsaveメソッドが使えます。このRouteにJSONをPOSTするOnlyです。にじみ出るルーさん感。

ここでPostmanの出番です。グダグダ書くより見たほうが早いですね。

HeaderのKeyにContent-typeを追加し、Valueにapplication/jsonを指定する
Headerを指定します
BodyにJSONデータ(本の情報)を記述しSendボタンをクリックします。このときプロトコルはPOSTにしておきます。
本の情報をJSONで渡す

HeaderにはJSONを送りますよということを記述しておきます。JSON本体には送りたいデータを記述します。必要な情報が記載できればプロトコルをPOSTにしてSendボタンをクリックするだけです。

あれ?idは?

大丈夫です。FluentSQLiteがよしなに割り当ててくれます。親切。試しにもう一冊追加してみます。本当によしなにしてくれるならばidには2が割り当てられるはずです。

2つ目をPOSTするとidが自動的にインクリメントされて割り当てられる図
2つ目をPOSTすると、idが自動的に割り当てられる

素晴らしい!

SQLiteデータベースファイルの中身を見てみると、ちゃんとデータがあるのがわかります。

追加したデータがちゃんと入っているのを確認した
データがちゃんと入っている

参考までに実験

SQLiteのデータベースファイルの中身を見てみると、何もしていないのにidPRIMARY KEYになっています。FluentSQLiteが裏でよしなにやってくれるのはありがたいのですが、何処まで親切にやってくれるのか若干不安になってきます。そこで少し実験してみました。

id以外の名前にしてみる

var bookCode:Int?

結果はダメでした。なんとSQLiteModelプロトコルに準拠していないというエラーが表示されました。id以外の名前は受け付けてくれないみたいです。

Type '(作ったモデルの名称)' does not conform to protocol 'SQLiteModel'

idを他の値の中に埋めてみる

今度は順序を変えてvar id:Int?を他のプロパティの中に埋めてみます。

これは特段問題が無いみたいです。要するにプロパティの中にidというInt?型のものがあれば良いようです。

int?以外にしてみる

Type '(作ったモデルの名称)' does not conform to protocol 'SQLiteModel'

デスヨネー

プロトコルとそれに対応するPRIMARY KEYの型があるようです。

プロトコルPRIMARY KEYの型
SQLiteModelInt?
SQLiteStringModelString?
SQLiteUUIDModelUUID?

ちなみに、どのプロトコルにしてもPRIMARY KEYとなるプロパティはidにしないといけません。

まとめ

まずはSQLiteデータベースにデータを投入することができました。コマンドラインから中身の確認ができたので今度はAPIを発行して取得してみます。取得できたらデータの更新と削除で一段落ですね。

いくつか気になるところがありますが、ひとまず必要に応じて調べることにして先に進んでいきましょう。初めてのことや新しいことを始めるのは骨が折れますが、その分楽しいですね〜

タイトルとURLをコピーしました