Server Side Swiftを使ってiOSアプリの言語をSwiftで統一してみる

公開:2021/02/16
更新:2021/02/16
6 min読了の目安(約5400字TECH技術記事

株式会社ウォールオブデスのmasatojamesです。
普段は主にインフラにいるのですが、Swiftも大好きなのでちょうどiOSアプリを作る時に言語をSwiftで統一してみました。

Rocket for bands II


それでできたのがこのアプリ「Rocket for bands II」です。ざっくり言えばロックバンドの宣伝活動を支えるiOSアプリです。
開発期間はだいたい2ヶ月ほどで、機能はなんら変哲のないSNSのプロトタイプて感じです。

ぜひ触ってみて、Swiftを堪能してください。

https://apps.apple.com/jp/app/rocket-for-bands-ii/id1550896325

iOSアプリをSwiftで統一するとは

つまりは「Server Side Swiftを使う」ということになります。
Server Side Swiftといえば、Amazon Prime Videoに使われているAmazon社の「Smoke」というフレームワークがとても有名ですね。

https://github.com/amzn/smoke-framework

まだまだ事例は圧倒的に少ないですが、Swiftのことが大好きでServer SideもSwiftで書きたいよ〜〜〜という人はたくさんいると思うので事例を作ってやろうと思い、導入に踏み切りました。

Server Side Swift "Vapor"

今回採択したのはVaporというフレームワークです。

https://vapor.codes/

VaporはGitHubを見ての通りスター数20kと一番多く、アップデートも頻繁に行なわれています。

ちょうどVapor4.0がリリースされ、ORM, ログ, SwiftNIOなどの機能が充実しており、これは本番環境でも行けると思い採択しました。

インフラ

このAPIをどう運用していこうかということですが、今回はEKSを採用しました。
普通に前回ECS使っててつらいポイントが多すぎて嫌になったからです。EKSもまあまあつらいポイントはありましたが、トータルで見ればECSより導入コストは低く、kubectlやfluentdのおかげで管理もとても楽になったと思います。CodePipelineでdocker buildしたものをデプロイするCI/CDもつくってネットワークの波に放ちました。

嬉しいポイント

フロントエンドとバックエンドの言語をSwiftで統一するということは本当に嬉しいです。具体的には

言語の統一によって

  1. コードを再利用できる
  2. コードベースで仕定様義ができる
  3. 技術スタックを統一できる

Swiftを使うことによって

  1. 安全性が高い
  2. 抽象度が高い
  3. テスト性が高い
  4. Xcodeのデバッグ機能を最大限に利用できる

というメリットを実感しました。いくつか例を挙げます。

まず、考えてみるとServer SideとiOSで再利用できるコードがいくつか見つかります。これをgit submoduleを用いてそのままiOSにパッケージとして入れることができます。

Domain Entity = Model

例えば、Domain Entityは完全にiOS側でModelとしての役割を果たします。

public struct User: Codable, Identifiable, Equatable {
    public typealias ID = Identifier<Self>
    public var id: ID
    public var name: String
    public var biography: String?
    public var thumbnailURL: String?

    public init(
        id: ID, name: String, biography: String?, thumbnailURL: String?
    ) {
        self.id = id
        self.name = name
        self.biography = biography
        self.thumbnailURL = thumbnailURL
    }
}

これだけでも
「え、Userにこのプロパティないの?」
「あれ、型違くない?」
「これOptionalなんかーーい」
みたいなコミュニケーションコストが減ることが分かりますね。本当に嬉しい。

EndpointProtocolの再利用

同様にEndpointもProtocolとしてSwiftコードで管理して、そのままiOSで使い回すことができます。

import CodableURL

// HTTPメソッド
public enum HTTPMethod: String {
    case get = "GET"
    case put = "PUT"
    case post = "POST"
    case delete = "DELETE"
}

// エンドポイントを抽象化したもの。リクエスト・レスポンス・URI・HTTPメソッドが定義されている。
public protocol EndpointProtocol {
    associatedtype Request: Codable
    associatedtype Response: Codable
    associatedtype URI: CodableURL

    static var method: HTTPMethod { get }
}

public struct Empty: Codable {
    public init() {}
}

// Userを取ってくるGETエンドポイント
public struct GetUserInfo: EndpointProtocol {
    public typealias Request = Empty // request bodyの型
    public typealias Response = User // responseの型
    public struct URI: CodableURL {
        @StaticPath("users", "get_info") public var prefix: Void // pathの定義。query string parameterやdynamic pathも定義できるので詳しくはGitHub見て。
        public init() {}
    }
    public static let method: HTTPMethod = .get // HTTPメソッドの型
}

iOSで呼び出すとき

import Endpoint

let req = Empty()
let uri = GetUserInfo.URI()

var request = URLRequest(url: uri.encode(baseURL: "http://hogehogeapi.com"))
request.httpMethod = GetUserInfo.method.rawValue
         
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    ...
    guard let data = data else { return }
    do {
        let response = try JSONDecoder().decode(GetUserInfo.Response.self, from: data)
        print(response) // ✔ { id: "xxxx-yyyy", name: "hogehogoe", biography: nil, thumbnailURL: "https://hogehoge.com/hogehoge.png" }
    } catch let error {
	fatalError(String(describing: error))
    }
}
task.resume()

みんな頑張ってスワッガーとかでやってるエンドポイントの定義がコードでできて、それを型としてそのまま扱うことができます。
iOS上でこのEndpointProtocolに沿ってbodyやquery string parameterを用意してあげればいいわけです。
こういうことやると本当にコーディングのリズムがいいんですよね。ホイホイ補完に出てきてくれるので。

つらいポイント

  1. ビルド時間が長い
    何よりマルチコンテナビルドでもだいたい10分~15分くらいかかってデプロイが大変でした。

  2. 実例が少ない
    少ないんですよねー

  3. バグがあったりなかったり
    マンパワーで解決した部分があったりなかったり

  4. プロトタイプには向かない
    スタートアップが2週間~1ヶ月でサクッとプロトタイプつくるのには絶対に向かないと思います。少なくとも現状では。これは完全に趣味の領域。遊び。楽しい。

  5. 殆どの処理をSwiftNIOのEventLoopFutureモナドに包む必要があって、flatMapとかオペレータをつなげていくと型推論が届かなくなることがある

今後の展望

  1. パフォーマンスの検証
    iOSの方を触ってみてけっこう速いなと感じていますが、まだちゃんとどれだけ性能にもアドがあるかは分かりません。これがけっこう導入の決め手になる気もするので、負荷試験をちゃんとやっていきたい。

  2. webもSwiftで書く
    本当は弊社HPもSwiftWASMで書こうとしたんですが、普通にマークアップがつらくてやめました。もう少しサクッとできるようになってくれたらwebもSwiftに移行しようと思います。

  3. 機械学習もSwiftで書く
    アプリの展開としてバンドやファンのビッグデータの活用などをやっていきたいと思っているのでぜひ機械学習モデルは運用しようと思っています。Amazon SageMakerがいい感じなので、それとSwift for TensorFlowとかを組み合わせて基盤つくりたいねいと思っています。だから死なないでほしい。

  4. インフラもSwiftで書く
    AWS CDK for Swiftができたら起こしてくれ✋。
    https://github.com/aws/aws-cdk/issues/549

  5. AndroidもSwiftで書く
    SwiftUIがんばってくれ。そう遠くないことを祈る。

まあこんな感じでSwiftで統治された世界を創り上げていく所存です。

最後に

このServer Side Swift Vaporは主に@kateinoigakukunが書いてくれました。
今回紹介したもの以外にもたくさんの神ポイントがあります。

Swiftに囲まれた幸せな世界の構築に興味ある方を募集しています。お話だけでも大歓迎なのでこちらにDMください。