Swift製webアプリケーションフレームワーク Vapor を読むやつ #1
Vapor とは SSS(Server Side Swift) のwebアプリケーションフレームワークです。
docs: https://docs.vapor.codes/3.0/
セットアップ
CLI ツールの使用を推奨しているので brew でいれておく。プロジェクトの作成は vapor new
で。Swift 初心者過ぎてよくわかっていないが XCode で開きたい場合は vapor xcode
すると必要なファイルができるっぽい。
$ brew install vapor/tap/vapor $ vapor new HelloVapor # HelloVapor ディレクトリができる $ cd HelloVapor $ vapor xcode
この時点でのフォルダはこんな感じ。最初から git 管理されているので git ls-files
で。
$ git ls-files .gitignore Package.resolved Package.swift Public/.gitkeep README.md Sources/App/Controllers/.gitkeep Sources/App/Controllers/TodoController.swift Sources/App/Models/.gitkeep Sources/App/Models/Todo.swift Sources/App/app.swift Sources/App/boot.swift Sources/App/configure.swift Sources/App/routes.swift Sources/Run/main.swift Tests/.gitkeep Tests/AppTests/AppTests.swift Tests/LinuxMain.swift circle.yml cloud.yml
vapor new
時に --template
(あるいは --web
, --auth
もしくは --api
) を指定することで設定可能です。以下のようなオプションがあることから単純に git clone してきているのだということがなんとなく推測できます。
--branch An optional branch to specify when cloning --tag An optional tag to specify when cloning
template を作るときはコミットは squash して1コミットにしておいたほうがよさそうですね。
XCode から run して動かしてみます ( https://docs.vapor.codes/3.0/getting-started/xcode/ )
以下適当に動作確認。
$ open http://localhost:8080/hello/ $ curl localhost:8080/todos/ [] $ curl -XPOST -d "title=1" localhost:8080/todos/ {"id":1,"title":"1"} $ curl localhost:8080/todos/ [{"id":1,"title":"1"}] $ curl -XPOST -d '{"title": "2"}' localhost:8080/todos/ {"error":true,"reason":"Value of type 'String' required for key ''."} $ curl -XPOST -d '{"title": "2"}' -H 'Content-Type: application/json' localhost:8080/todos/ {"id":1,"title":"2"} $ curl localhost:8080/todos/ [{"id":1,"title":"1"},{"id":2,"title":"2"}]
とりあえずいわゆるふつーのAPIサーバが立ってますね。
テンプレートを読む
デフォルトでは api テンプレートなのでそれにします。
https://github.com/vapor/api-template
エントリポイントっぽいところから。
/// Sources/Run/main.swift import App try app(.detect()).run()
Swift 初心者的には .detect()
がキモいのですが、これは implicit member expression という書き方のようです。今回の場合は Environment.detect()
の省略記法。
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID394
app
関数の定義が Sources/App/app.swift
にあります。
/// Sources/App/app.swift import Vapor /// Creates an instance of Application. This is called from main.swift in the run target. public func app(_ env: Environment) throws -> Application { var config = Config.default() var env = env var services = Services.default() try configure(&config, &env, &services) let app = try Application(config: config, environment: env, services: services) try boot(app) return app }
ここから概念として Config, Service(s) が存在すること、 configure, boot を経て Application のインスタンス app が初期化されて返っていることがわかります。
configure から順に見ていきます。
/// Sources/App/configure.swift import FluentSQLite import Vapor /// Called before your application initializes. public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws { /// Register providers first try services.register(FluentSQLiteProvider()) /// Register routes to the router let router = EngineRouter.default() try routes(router) services.register(router, as: Router.self) /// Register middleware var middlewares = MiddlewareConfig() // Create _empty_ middleware config /// middlewares.use(FileMiddleware.self) // Serves files from `Public/` directory middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response services.register(middlewares) // Configure a SQLite database let sqlite = try SQLiteDatabase(storage: .memory) /// Register the configured SQLite database to the database config. var databases = DatabasesConfig() databases.add(database: sqlite, as: .sqlite) services.register(databases) /// Configure migrations var migrations = MigrationConfig() migrations.add(model: Todo.self, database: .sqlite) services.register(migrations) }
FluentSQLite が ORM ですね。Vapor の内製ライブラリです。middleware の作り方も気になるので後で見ておきましょう。
マイグレーションは明示的に migrations.add
してますけど、これ増えたときどうするんですかね。毎回増やすのかな。アノテーションとかでしれっと書けてほしいような1。
マイグレーション自体は起動時に勝手にやってくれるようです。
[ INFO ] Migrating 'sqlite' database (FluentProvider.swift:28) [ INFO ] Preparing migration 'Todo' (MigrationContainer.swift:50) [ INFO ] Migrations complete (FluentProvider.swift:32) Running default command: ...
具体的には try Application
の中でやっているっぽい。ソース的にはここ。
https://github.com/vapor/vapor/blob/master/Sources/Vapor/Application.swift#L126-L140
今日はここまで。次回boot以降を読みます。
ハマったとこ
sqlite3 の path 問題
sqlite3 の .file("path")
で path はディレクトリを含むと困ることがある。sqlite3側は mkdir などはしてくれないのでこちらでしてやる必要があるが、xcode の debug build が動くのは普段のプロジェクトとは違う場所なので mkdir db; touch db/.gitkeep
とかしても無駄。
レポジトリ眺めてたらあとでこういう issue を発見した。 https://github.com/vapor/fluent-sqlite/issues/5
カラム追加
一回マイグレーションが動いたあとにモデルに属性を追加しても add_column とかはしてくれない。まあそりゃそうだ。どうなってるのが理想的なんだろう。
なお本記事は以下のエントリに触発されて書きました。
https://fiveteesixone.lackland.io/2018/05/06/addicted-to-vapor/
-
Swift 的には attribute↩