新規iOSアプリ開発でServer Side Swift使ってみた

こんにちは。お久しぶりです。[株式会社ウォールオブデス](https://wall-of-death.com)代表のmasatojamesです。

この度、ロックに特化した機能を詰め込んだiOSアプリ[「Rocket for bands II」をリリース](https://prtimes.jp/main/html/rd/p/000000001.000074352.html)しました。
創業期にしては技術要件がけっこう面白いのでご紹介させてください。
![サムネイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243995/770d2c10-6201-6294-0473-d09fa1c6b6e6.png)
ためしにアプリも触りながら読んでみてください↓
https://apps.apple.com/jp/app/rocket-for-bands-ii/id1550896325

# 技術要件

## フロントエンド

- Swift 5.2
- UIKit
- MVVM
- Combine

## バックエンド

### アプリケーション
<font color="DeepPink">
- Swift 5.2
- Vapor 4.0
- Clean Architecture
- Fluent ORM
- SwiftNIO
</font>

### インフラ
<font color="DeepPink">
- Docker
- Kubernetes
- Amazon EKS on EC2
- Mackerel
</font>


## DB
- RDS Aurora for MySQL

# 主な特徴

## Server Side Swift
なによりバックエンドで<b>Server Side Swift Vapor</b>を使用しているが特徴的だと思います。スタートアップの新規開発において導入している企業は日本初かもしれません。

世界的に使われている事例としては[Amazon Prime VideoのSmoke Frameworkというフレームワーク](https://github.com/amzn/smoke-framework)があります。今回弊社では、一番スター数も多く、機能も充実していたVaporを採択しました。

## EKS
このServer Side SwiftをAmazon EKS(Elastic Kubernetes Service)というコンテナオーケストレーションサービスに載せて世に放ちました。EKSに載せたことによって、Kubernetesのエコシステムをフルに活用でき、運用にグッと安定感が増しました。

MVPでここまでやる要件は本当に少ない(大企業向けのSaaSくらいだと思います)し、このアプリに関してはどう考えてもtoo muchです。
僕が今まで培った技術スタックと、このサービスは今後10年スパンでスケールしていくことを見越して採択しました。あとは完全に趣味です。

# Swift × Swift
↑HUNTER × HUNTERを意識しています。

さて。Swiftはとても素晴らしい言語です。値型中心の静的型付け言語として、とても多くの抽象化のための機能、型安全とパフォーマンスを兼ね揃えた言語仕様を持っています。

Swiftの良さがパッとしていない方はこれを読んでほしい↓
https://heart-of-swift.github.io

さてさて。そんなSwiftがアプリケーションの端から端まで跨っていることを想像してみてください。(嬉しすぎて発狂しますよね)

初期において特に嬉しかったことは
1. いくつかのコードをフロントエンドとバックエンドで共有できた
2. API仕様のためのドキュメントがいらなくなった
の2つです。2は1の副産物とも言えます。

## Domain Entity(バックエンド) = Model(フロントエンド)

まず嬉しかったのは、フロントエンドでModelを用意する必要が全くなくなった点でした。バックエンドのDoain Entityとして使用しているコードをそのまま使うことができたからです。

例えば、以下のようなUserを定義するDomain Entityがあるとします。

```Swift:User.swift
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?
    ...
}
```

これはそのままフロントエンドのModelも`User`としての役割を果たします。
というか、これらの型の整合性は保たれなければならないので使い回すのが望ましいんですよね。あっち変えてこっちも変えてっていう作業を人間がやるとバグの元になるし、みんな普段よーーく注意してやっているんだと思います。

フロントエンド書いてて、
「うーんUser.nameがバックエンドに足りてないなあ」
「あれ、User.biographyってoptionalじゃなかったっけ？」
「User.thumbnailURLってURL?型だっけ？String?型だっけ？」
みたいな不思議を抱いて、バックエンドエンジニアに確認し、場合によっては修正を依頼した経験がありますよね。

## 使い回せてAPI仕様定義にもなるEndpoint

我々はもうひとつ`Endpoint`という層を責務分離し、バックエンドとフロントエンドでコード共有しています。これは何かというと、それぞれのエンドポイントを定義するレイヤです。

例として、以下のような`GetUserInfo`というEndpointを用意します。

```Swift:UserAPI.swift
// 自身のアカウント情報を取ってくる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メソッドの型
}
```
このエンドポイントは`$ curl -H GET 'http://api.hogehoge.com/users/get_info' ...`という風にして叩くことができます。
ではこの`struct　GetUserInfo`を読み解いていきたいと思います。
読めば分かる通り、この裏にはあるProtocolが用意されている必要があります。それを読めばOKです。

```Swift:EndpointProtocol.swift
import CodableURL

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

// エンドポイントを抽象化したProtocol
public protocol EndpointProtocol {
    associatedtype Request: Codable // リクエストボディ
    associatedtype Response: Codable // レスポンスボディ
    associatedtype URI: CodableURL // パス

    static var method: HTTPMethod { get } // HTTPメソッド
}

// 空だよーて時に使う
public struct Empty: Codable {
    public init() {}
}
```

この`pubic protocol EndpointProtocol`を読むと、以下の型が必要とされていることがわかります。
- Request
- Response
- URI
- method

そう、これらはそのままエンドポイントに必要なリクエストボディ、レスポンスボディ、パス、HTTPメソッドを表しています。
偉い人はこういうのを最初にSwaggerや任意のドキュメントで用意するだろうと思います。僕たちは、このEndpointレイヤーを通じてこの仕様定義を直接することができます。

さらに、これをiOSでこのように使い回すができます。

```Swift:HogeHogeViewModel.swift
import Endpoint // バックエンドで定義されたEndpointレイヤー

let req = Empty() // リクエストボディ
let uri = GetUserInfo.URI() // パス

var urlRequest = URLRequest(url: uri.encode(baseURL: "http://hogehogeapi.com"))
urlRequest.httpMethod = GetUserInfo.method.rawValue
         
let task = URLSession.shared.dataTask(with: urlRequest) { (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()
```

`struct GetUserInfo`で定義されているものをそのまま参照できていることがよく分かると思います。これがあればフロントエンドとバックエンドの整合性が不足することは絶対にありません。

## その他の嬉しいポイント
😁
1. 技術スタックを統一できる
2. 安全性が高い
3. 抽象度が高い
4. テスト性が高い
5. Xcodeが使える

# 逆に困ったことは？

これも大事ですね。いくつかあります。パッと挙げると
### ビルド時間が長い
何よりマルチコンテナビルドでもだいたい10分~15分くらいかかってデプロイが大変でした。

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

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

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

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

これくらいですね。ただ、絶望的なものはなく、まあ耐えれるなあという所感でした。

# 最後に

いかがでしょうか。今すぐServer Side Swiftを書きたい衝動に駆られているのではないでしょうか。ぜひ[Vaporの公式Tutorial](https://docs.vapor.codes/4.0/)から始めてみてください。

弊社では、そのうちwebもSwiftで書いたり、AndroidもSwiftで書いたり、インフラのIaCもSwiftで書いたりすると思います。Swiftで統治された幸せな世界に興味ある方は[Twitter](https://twitter.com/masatojames)まで気軽にご連絡ください。もちろんServer Side Swiftの経験が無くとも大歓迎です。ゆるくカジュアルに話しましょう。
![エンジニア求人.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243995/41268938-ccd5-c5bc-d69c-516a91cb96a0.png)

その他ご質問などもお気軽にお待ちしています。