Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What are the problem?
@caesar_cat

Web エンジニアが始める Server-side Swift "Vapor"

はじめに

image.png
2014年の WWDC で電撃発表された Swift は、もはや iOS アプリ開発では定番となっていますが、Web 開発界隈のエンジニアにとっては「何それ美味しいの?」という感じであり、ましてや「サーバサイドで Swift が動く!」とか言われても、どうせ最低限の事しかできず「プレステで Linux が動く!」くらいのお遊びだと思われている方も一定数いらっしゃるのでは無いでしょうか?(シランケド)

とか言う私も、当時の WWDC の Swift 発表は興奮して朝まで眠れなかったくらいですが、最近は Swift からは少し離れて Web アプリ開発にどっぷりです。

そんなわけで、今回は、Web 開発者の目線で、Swift の Web アプリケーションフレームワーク Vapor を触ってみたいと思います。

Swift ってどんな言語?

2014年以前は、iOS や macOS のアプリは Objective-C という変態言語での開発が主流でした。WWDC での突然の発表で世に出た Swift ですが、普及するには4、5年はかかると見られていた予想とは裏腹に、あっと言う間に多くのプロジェクトが Swift に移行していきました。一つの iOS プロジェクト内で Objective-C と Swift を混在して書けるというバイナリ互換が後押ししたというのもあるでしょう。

さらに翌年にはオープンソース化して GitHub で公開したことによって、他のプラットフォームへの移植のハードルが下がりました。今では Linux だけでなく Windows でも動作するようになっています。

じゃあ Linux や Windows でも iOS のようなアプリが作れるのかというとそうではなく、あくまでも言語の基礎部分だけがオープンソース化しているに過ぎず、スマートフォンの画面を構成する UiKit や、マルチメディアな機能を扱う AVFoundation など iPhone 環境に特化した様々な非公開フレームワーク群が備わった環境が iOS SDK としてデベロッパーにのみ提供されており、Xcode でのアプリ開発を可能にしているわけです。

そんな Swift さんですが、Objective-C という技術的負債をバッサリ捨てたことにより、何の障壁もなく様々なプログラムバラダイムのトレンドを実装できたという背景もあって、クロージャーやジェネリクス、Optional 型、タイプセーフな構文などは他のナウい言語と共通する部分もあって学びやすい言語だと思います。

Vapor とは?

Vapor はそんな Swift で書かれた Web アプリケーションフレームワークです。Laravel (Lumen) にインスパイアされて作ったとか作らなかったとか(すみません以前公式に書かれてた気がしましたがソースが見つかりませんでした)。Web アプリケーション開発では一般的な MVC の構成で、DB接続 (PostgreSQL / MySQL / SQLite / MongoDB) はもちろん、ORM やテンプレートエンジンなどの Web に必須となる機能もエコシステムとして提供されています。

Vapor は今のところ Mac と Linux で利用可能です。Mac だと Homebrew、Linux では yum や apt などでパッケージが提供されています。あくまでも実行環境ということなので docker や Homestead を使えば Windows でも開発は可能です。

image.png

余談ですが、Swift 製の Web アプリケーションフレームワークと言えば当初は IBM が開発する Kitura が有名でしたが、調べたら2020年1月に(IBMとして)開発を終了しているようでした。

サーバサイドを Swift で書くメリット

Swift で iOS アプリを開発しているエンジニアにとっては、馴染みの無い PHP や Ruby に手を出してサーバサイドの実装を行うことは大きなハードルになりますが、サーバサイドも Swift で書けるということで、システム全体を得意言語でカバーできることになります。何より頭の中で言語のコンテキストをスイッチすることが無くなると言うメリットは大きいでしょう。Web エンジニア風に言えば、シングルページアプリケーションを JavaScript で書きつつ、サーバ API は node.js で実装するような「JavaScript ボーダレス」な開発環境と言えば分かりやすいんじゃないでしょうか。

では、PHP などの非コンパイル言語での開発を生業としているエンジニアにとって、Swift でサーバサイドのプログラムを実装するメリットは何でしょうか?
Swift を使ったことがなければ答えは「メリットなし」でしょう。餅は餅屋。得意言語で実装するに越したことはないです。ただ、少しでも Swift を触ったことがあれば、(もしくはこれから学ぼうという意欲があれば)、読みやすい、書きやすいで定評のある Swift を使うメリットはあると思います。

個人的には Xcode での開発ができる点もメリットだと思います。Web 開発の IDE は Electron や Javas ベースのものがほとんどで、Web 開発者の多くは、メモリ食い過ぎ問題や動作もっさり問題に悩まされています。 macOS ネイティブで快適に安定動作する IDE は皆無でした。賛否はありますが、Xcode が使えるのは開発効率的にもメリットだと思います。

環境準備

言語比較やIDEの批評をすると石が飛んでくるらしいので早速 Vapor のインストールに入りましょう。
まずは事前に下記をインストール。

Xcode は初回立ち上げ時に License Agreement に同意しないと Vapor のインストールで怒られるので同意しておいてください。Developer Program への登録は不要なので、AppStore からインストールしてください。

インストール

Vapor 本体は Homebrew でインストールします。下記コマンドで一発です。

$ brew install vapor

完了したら vapor コマンドが使用できるので、バージョンが表示されればOKです。

$ vapor --version
framework: 4.36.0

プロジェクトの作成

では早速 Web アプリケーションを作成しましょう。ここでは ApiTest というプロジェクトを作成します。vapor new コマンドでプロジェクト名を渡します。

$ vapor new ApiTest
Cloning template...
name: ApiTest
Would you like to use Fluent? (--fluent/--no-fluent)
y/n>

Fluent を使用するかどうかを聞かれるので、y か n を入力。
Fluent は Laravel で言うところの Eloquent のようなライブラリみたいです。とりあえず y で入れておきます。


Which database would you like to use? (--fluent.db)
1: Postgres (Recommended)
2: MySQL
3: SQLite
4: Mongo

Fluent を使用すると言うことはその後ろにデータベースがあるわけですので、次でデータベース製品を選択します。PostgreSQL が Recomended なので、1 を入力。

Would you like to use Leaf? (--leaf/--no-leaf)
y/n>

次は Leaf の追加。こちらは Laravel で言うところの Blade テンプレートシステムです。もちろん y を入力。

すると完了メッセージと共に下記のようなアスキーアートが表示されます。

image.png

アスキーアートと言えば、昔アドベントカレンダーで書いた自分の記事を思い出しました。全然関係ないけど。

Xcode の起動

プロジェクトディレクトリに移動して下記コマンドで Xcode が起動します。

$ cd cd ApiTest
$ vapor xcode

初回起動時は依存パッケージがダウンロードされるのでしばらく待って、左上のビルドボタン(再生ボタン)がアクティブになるまで待ってください。
image.png

依存バッケージのダウンロードが完了したら、ビルドを実行してみましょう。ショートカットキー「⌘ + R」で実行できます。
初回は時間がかかりますが、完了すると Xcode のコンソールに下記のようなメッセージが表示されます。

[ WARNING ] No custom working directory set for this scheme, using /Users/shigeta/Library/Developer/Xcode/DerivedData/ApiTest-awtfxnyehqlqwxclpfyylflkbavb/Build/Products/Debug
[ NOTICE ] Server starting on http://127.0.0.1:8080

ちょうど npm run dev したような感じですね。ではブラウザでアクセスしましょう。

image.png

軽く構成を眺める

初期構成のファイル構成を軽く見てみましょう。
image.png
既視感があると言うかなんと言うか、もうフォルダ名のファイル名で大体わかっちゃいますよね。主要なファイルをさくっと紹介。

Resources/Views/index.leaf

Leafのテンプレートファイルです。Webエンジニアであれば説明は不要でしょう。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">

  <title>#(title)</title>
</head>

<body>
  <h1>#(title)</h1>
</body>
</html>

Sources/App/Controllers/TodoController.swift

こちらはコントローラーです。
予め TODO の CRUD 遷移が実装されています。

import Fluent
import Vapor

struct TodoController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let todos = routes.grouped("todos")
        todos.get(use: index)
        todos.post(use: create)
        todos.group(":todoID") { todo in
            todo.delete(use: delete)
        }
    }

    func index(req: Request) throws -> EventLoopFuture<[Todo]> {
        return Todo.query(on: req.db).all()
    }

    func create(req: Request) throws -> EventLoopFuture<Todo> {
        let todo = try req.content.decode(Todo.self)
        return todo.save(on: req.db).map { todo }
    }

    func delete(req: Request) throws -> EventLoopFuture<HTTPStatus> {
        return Todo.find(req.parameters.get("todoID"), on: req.db)
            .unwrap(or: Abort(.notFound))
            .flatMap { $0.delete(on: req.db) }
            .transform(to: .ok)
    }
}

Migrations/CreateTodo.swift

マイグレーションファイルもありますね。Swift 知らなくても Laravel や Rails 使ってる人であれば直感的に理解できるんじゃないでしょうか。

import Fluent

struct CreateTodo: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("todos")
            .id()
            .field("title", .string, .required)
            .create()
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("todos").delete()
    }
}

Models/Todo.swift

モデルクラスです。

import Fluent
import Vapor

final class Todo: Model, Content {
    static let schema = "todos"

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

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

    init() { }

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

Sources/App/routes.swift

URLルート定義ファイル。URLのマッピングをここに記載。これも Web アプリケーションフレームワークではおなじみですね。

import Fluent
import Vapor

func routes(_ app: Application) throws {
    app.get { req in
        return req.view.render("index", ["title": "Hello Vapor!"])
    }

    app.get("hello") { req -> String in
        return "Hello, world!"
    }

    try app.register(collection: TodoController())
}

まとめ

Vapor はざっと見た感じ Rails や Laravel に近い構成なので、Web アプリケーションエンジニアにとっては取っ付きやすいのでは無いでしょうか。

Swift は言語的にもとてもスマートで、本当に洗練された無駄の無い言語なので、iOS アプリだけで使うのは勿体無いですが、とは言え、PHP などの軽量言語のエンジニアにとってはまだまだ敷居が高いです。特にブラウザ上で動作するメイン言語がまだまだ JavaScript 全盛なので、Web アプリエンジニアにとってはあまりメリットが感じられないかもしれません。

そうなると願いは一つですね。

いつの日か、Swift がブラウザ上で動作する時代が来ますように!

告知

猫会社 猫会社で有名な qnote は今年もアドベントカレンダーに参加しています!
応援よろしくお願いします!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What are the problem?