開発コストを最小限にして施策を進める

投稿推進部・ディレクターの中山です。
普段ディレクターはエンジニアとペアを組んでサービス開発をすることが多いですが、エンジニアが別の開発に集中したい時は、ディレクターだけで施策を進めることもあります。エンジニアがいないと動くものができない…と言っていては何もできません。
既に多くのディレクターの方が試していることかもしれませんが、実装に入る前に紙レベルでモノを作ってテストしたり、一般に出回っているツールを活用してみたり…と方法は色々とあります。私がここ数ヶ月で実践してきたことを、おさらいも兼ねてご紹介したいと思います。

方法1:紙でイメージを膨らませる

サービスを考える際、いきなり実装に入ることはまず無いと思います。
当たり前の話かもしれませんが、スピーディにイメージを掴むには手書きが便利。何度も書いたり直したりしつつ頭の中のイメージを具体化し、周囲のスタッフに当ててみることがができます。 f:id:akoakon777:20160614182920p:plain

例えば上記の写真の例は、見やすく書きやすいレシピフォーマットについて考えて書いたもの。普通のノートやレシピ用に売られているカードなどもあり、これらに実際に書いてみたり、スマホサイズに切り取ってみたりして使用感を試します。
右は手書きではありませんが、自分のレシピをカード状に置き直して紙に印刷してみたもの。普通のA4の紙に印刷し余白を折って整えただけのものですが、手に取ってみることで「文字はもうちょっと減らしたいな」「写真サイズはこれくらいが良いな」などの実感を得ることができます。

方法2:一般に出回っているツールを活用

プロトタイプツール

コードが書けなくてもFlintoやProttなどのツールを使えば動きのあるプロトタイプを作れる良い時代です。手書きや紙のコンテンツでイメージが出来たらこれらのツールで動くものを作り、スマホの実機でユーザーテストをすることができます。

ここでちょっと気をつけたいのは、プロトタイプツールが便利なだけに、ついつい作り込み過ぎてしまうことです。細かいデザインや動きは実装段階でデザイナーやエンジニアにお任せすれば良いので、ここではあくまでもユーザーが画面を見た際に、どういう意図をもってどんな動きをしようとしているのか を確かめます。
ユーザーのどんな課題をどうやって解決するツールなのか
を明確にしておき、その意図のようにユーザーが動いているか(或いはどこで躓いているのか)を確認する目的で使います。

SNSを活用

自分でプロトタイプを作らずとも、既に人が集まっていて尚且つ投稿機能を備えた場を借りることもできます。
TwitterやInstagramのようなSNSは非常に便利。
例えば「写真+1行のキャプション」のようなひと纏まりのコンテンツが周囲の人の興味をひけるのか、を試したい場合。そのまま自分で「写真+1行のキャプション」を作成し、コンテンツとしてSNSに投下してみると、「いいね!」やコメントの数で手応えを掴むことができます。あくまでも参考程度ではありますが、ざっくりとした反響を掴むことで、プロダクトの方向性が正しいのかを確かめることが可能です。

サービスの作りそのものを参考にできるだけでなく、こうしたちょっとしたテストにも活用できるので、自社以外の人気のサービスを普段から自分で使い込んでおくことは非常に有益だと思います。

方法3:既存の社内ツールをうまく使う

新しい施策を実施する際、目的に合わせて新たな機能を開発したくなってしまいますが、エンジニアのリソースは限られています。新しく作る前に、既にある仕組みを活用できないか考えてみると良いでしょう。
例えば弊社の場合は、ユーザー向けの汎用的なアンケートツールや連絡先の取得ツールが既にありました。社内で専用のものを内製していない場合でも、一般的に使えるアンケートやメールのような仕組みは色々あるのではないでしょうか。 本当に開発が必要なのか、手持ちのツールを組み合わせて解決できる可能性をまずは考えてみると開発の手間を省けるというのはよくあります。

次に、ここまでにご紹介した手法を組み合わせて今年の春に実施した「母の日のフォトブック企画」の事例をご紹介します。

「母の日のフォトブック企画」

考えた企画は、
ユーザーさんが「母の日」というイベントを前に、お母さんの思い出の味をレシピにしてをクックパッドにのせる
→自分はいつでも料理を再現できて便利になり、お母さんには記念のフォトブックとともに感謝の気持ちを届けることができる
  というもの。

手作りの試作品でテスト

まず、本当にこの企画がユーザーの心に響くのか、をテストするため、私自身がクックパッドにのせている母親のレシピを紙に印刷し、手作りのフォトブックを作成。 f:id:akoakon777:20160614183020p:plain

写真は粗いし作りも雑でお恥ずかしいレベルですが、この段階でのクオリティは気にしない。とは言え、これでも手に取ってみるとなかなかの達成感があります。※1

次に、実際にこの手作りの試作品を遠方に住む自分の母親に予告なしで送りつけてみて、電話で感想をきいてみました。
いきなり送りつけられた母親はとても驚いていましたが、電話の向こうで涙ぐむほど喜んでいて、送ったこちら側はガッツポーズできるほどの達成感。同時に、伝わりきらなかった部分のヒアリングもでき、テストとしては十分な手応えを掴むことができました。

本番の企画を実施

上記のテストのフィードバックを踏まえ、企画を本格的に実施すべく動かし始めました。

企画を立てた当初は、ユーザーが自分のレシピを選んでサクッと応募できる仕組みを開発するつもりでいました。
しかし、スケジュール的にも開発リソース的にも無理がある。そこで既存の社内のツールを色々調べてみると、アンケートフォームと住所取得フォーム、確認のメールを直接やり取りできる仕組みなどがありました。
これらを組み合わせれば、なんとかできないこともなさそうです。
もちろん、既存のアンケートフォームの仕様に縛られるので、ユーザーが入力するテキスト量が多くなり、手間をかけさせてしまう部分もあります。
そこで、事故を防ぐために気をつけたのは以下の2点です。

  • ユーザーさんから新たに受け取る情報を最小限にする
  • 仕様をシンプルにしてユーザーさんの考えるコストを減らす

具体的には、レシピのフォーマット内に既に書かれている内容をそのままフォトブックに採用。これで応募時のテキスト入力の手間やミスが減ります。また、フォトブックの仕様は「写真入りレシピ5品」という1パターンのみに統一し、余計な選択するための思考コストをなくしました。
一方で、試作の段階では自分(娘)が母親のために作った世界で1冊のフォトブックである、という部分が伝わりきらなかったので、冒頭の1ページ目にお母さんへのオリジナルメッセージを入れる仕様に。ここで皆さんがお母さんへの思いを伝えられるようにしました。

このように既存のフォームを活用してなんとか応募の裏側の仕組みを整え、表側は1枚の告知ページだけを用意することで、実装のコストを最小限に抑えることができました。

スマートな応募フォームを用意できるに越したことはありませんが、最終的なゴールはユーザーが満足できるフォトブックを作成してお届けすること。今回の企画のように、スケジュール内でその目的を達成するため、多少の使い勝手が下がっても実現可能な方法を選択したほうが良い場合もあります。
結果的に大きな事故もなく、皆様に素敵なフォトブックを作成してお届けすることができました。※2 応募してくださった方からはメールやブログなどでの好意的な反響が通常の6倍ほどもあり、大変ご満足いただけたという印象です。

まとめ

何か施策を進めようとする時、まずはエンジニアのリソース確保…と考えてしまいがちですが、ディレクターだけでできることは色々あります。紙や既存のツール、SNSやリアルな人間関係などを駆使すればある程度のテストも可能。
エンジニアには本当に必要な開発に集中してもらえるよう、今後もこれらの手法を常に意識して取り組んでいきたいです。

※1 社内のプリンターでA4用紙に印刷したものをハサミで切ってビニール製のポケットブックに入れ、マスキングテープを貼って綴じただけのもの。
※2 本企画でユーザーの皆様にお届けしたものは専門業者さんに製本してもらった素敵なものです。ご安心ください。

インフラ新卒研修と社内ISUCONのはなし

インフラ部の荒井(@ryot_a_rai)です。

今年の4月、弊社には11名の新卒エンジニアが入社しました。そして現在、3ヶ月間の研修を受けています。ビジネスマナーから技術研修まで幅広く行われていますが、その中で5月下旬におこなったインフラ研修とその後の社内ISUCONについてご紹介します。

インフラ研修(講義)

f:id:ryotarai:20160610152008j:plain

インフラ研修はインフラ部に配属されるエンジニアに限らず、全新卒エンジニアが参加する研修です。日常業務でコードを書いてサービスを開発していくうえで知っておいてほしい、インフラに関する基礎知識や共通言語を獲得することを目的としています。合計3日間をインフラ部の@kani_bと分担して講義しました。研修内容の内容はざっくりと以下のようなものです。

1日目

  • インターネットとは
    • ブラウザでウェブサイトを閲覧する際になにが起きているのか
    • IPからHTTPまでざっくりと
  • Webインフラアーキテクチャ概観
    • 三層アーキテクチャ(Webサーバ層、Webアプリーケーション層、データベース層)の話
    • スケールイン/アウト
  • Vim超入門
  • アプリケーションサーバ
    • Rack, Ruby on Railsの仕組み、役割
    • WEBrickやUnicornなどのHTTPサーバの話

2日目

  • データストア
    • MySQL
      • スロークエリ, explain, インデックス
      • トランザクション, ロック
      • レプリケーションとスケールアウト
    • Memcached
      • slab allocator, consistent hashingなど使う上で知っておきたい知識
    • Redis
      • Memcachedに比べた利点や使いどころについて
    • 全文検索
      • Solr, Elasticsearchの役割や利点について
  • Webサーバとプロキシ
    • nginx
      • 静的ファイルの配信
      • Unicornなどアプリケーションサーバの前段に配置する意味

3日目

  • その他の構成要素
    • キャッシュ
      • Railsキャッシュストア
      • Varnishによるレスポンスのキャッシュ
    • CDN
      • CDNの役割や利点について
    • バッチ処理
      • バッチ処理とはなにか
      • 弊社のバッチ処理環境について
      • バッチを書く際に気をつけるべきこと
  • Infrastructure as Code
    • なぜコードで記述するか
    • Itamae, Serverspecによるサーバプロビジョニング
    • これまでの手作業を自動化してみる
  • いわゆる”クラウド”について
    • AWSなどいわゆるクラウドサービスの利点と各種サービスについて

全体として自分で触って覚えられるようにハンズオンを多くおこないました。ひとりひとりVirtualBox( + Vagrant)でVMを構築し、その中で演習をできるようにしています。例えば、自分で実装したRackアプリケーションをWEBrickやUnicornで動かしたり、nginxを前段に挟んでベンチマークを取って効果を確認したりしました。

段階グランプリ(社内ISUCON)

4日目は新卒研修のフィナーレとして段階グランプリ(社内ISUCON)を開催しました。ISUCONは年に一回開催されているパフォーマンスチューニングコンテストで、段階グランプリはその社内版です*1。段階グランプリは新卒研修の一環ですが、せっかくなら新卒以外の社員にも参加してもらおう、ということでエンジニア全体で参加者を募り開催しました。参加者は新卒11名(4チーム)、新卒以外23名(9チーム)となり大盛況でした。

準備

今回の段階グランプリの準備・運営はインフラ部の3名(@mirakui, @kani_b, 私 @ryot_a_rai)でおこないました。具体的には以下の準備をしました。

  • 参加者ポータルサイト(mirakui)
    • ベンチマークを実行したり、ベンチマーク結果を見たり
    • 社内の参加者以外からも見えるようにして、お気に入りのチームを応援できるようにしました
    • 素敵なドメイン(段階.jp)でアクセスできるようにしました
  • 参考実装(ryot_a_rai)
    • 複数言語実装は用意せず、Ruby on Railsでの実装のみを用意しました(弊社で最も多く利用されている言語・フレームワーク)
    • 新卒研修で学んだことを活かせるよう、N+1クエリ、スロークエリなど日常でお目にかかるような消耗ポイントを用意しました
    • テーマは「雑実装なクックパッド」でした
  • ベンチマーク(ryot_a_rai)
  • サーバの準備(kani_b)
    • 参加者の環境やベンチマーカなどを用意
    • 今回はAWS EC2上で1チームにつきc4.largeを3台(io1 EBS 100IOPS)を用意しました
      • gp2を使っていないのはバーストを防ぐため
    • メインのAWSアカウントとは別のアカウントを利用していたため、インスタンス数の制限に引っかかって、急いで緩和申請を上げました…

余談ですが、新卒研修準備、社内ISUCONの開発合宿に利用したヴィラージュ伊豆高原がよかったです。会議室や部屋によっては大きめのテーブルがあり夜中まで開発をやっていけますし、いい感じの温泉がありました。

結果

f:id:ryotarai:20160610151941p:plain

最終結果は上のようになりました。本家ISUCON本選出場勢(@sora_h, @eagletmt)が大人げないスコアで優勝しましたが、新卒チームも初期スコアの4, 5倍のスコアを出し、新卒研修の成果が見てとれました。参加者の感想もおおむね好評で今後も定期的に開催していきたいと考えています。

まとめ

以上、今年のインフラ研修についてご紹介しました。今年は講義形式や社内ISUCONが初回だったこともあり、準備も大変でしたが、今年の経験を来年以降にも繋げられるといいと思っています。こんな新卒研修や社内ISUCONに参加したい、主催したいというあなた、ぜひ一緒にやっていきましょう!

*1:本家ISUCONについてはこちら

API クライアントを書きつつ Swift らしいコードを考える

こんにちは、技術部モバイル基盤グループの茂呂(@slightair)です。

クックパッドは Garage という RESTful Web API 開発を楽にする Rails のためのライブラリを作り、内部通信やモバイルアプリケーションのためのAPIサーバの開発に利用しています。

過去の Garage の紹介記事はこちらです。

この Garage を使って実装された Web API を iOS アプリから気軽に呼べるように、 Swift で Garage のクライアントを実装してみました。

この記事では、GarageClientSwift の紹介をしつつ、これを作りながら Swift らしいコードってどんなコードなんだろうと考えたことをつらつらと書いていきたいと思います。

Garage

Garage は RESTful Web API 開発のためのライブラリです。OSSとして公開しています。 https://github.com/cookpad/garage

今回はクライアントサイドの話をしたいので Garage 自体の説明は過去の記事にまかせます。

記事で紹介されているサンプル実装を使ってクライアントの開発・動作確認を行います。 手元で動作を確認しながら読みたい場合は、リポジトリからコードをチェックアウトして動かしてください。 https://github.com/taiki45/garage-example

クライアントアプリケーションの動作確認時には、サーバアプリケーションのアクセストークンが必要になるので、過去の記事の手順にしたがって取得してください。

GarageClientSwift

GarageClientSwift はその名の通り、GarageClient の Swift による実装です。 https://github.com/slightair/GarageClientSwift

GarageClientSwift は僕が趣味でなんとなく書いたものなので、クックパッドのアプリでもうバッチリ使っているぜ!…というわけではありません。ただ基本的な機能はそろっているのではないかと思います。

GarageClientSwift は HimotokiAPIKit というSwiftのライブラリに依存しています。 これらのライブラリについては後述します。

GarageClientSwift の使い方

GarageClientSwift は Carthage でプロジェクトに導入できます。 詳しくは README.md を読んでください。 この記事では GarageClientSwift 1.1.0 の実装を使った例を出します。

GarageClientSwift の workspace に Demo.playground を同梱しているので、コードを触りながら動作を確認したければこれを利用できるでしょう。 Demo.playground を動かす際は一度 GarageClient iOS の scheme でビルドしてから playground ファイルを開いてください。

この節で説明するものは、この playground ファイルに記述されているものです。

リソースのモデルを定義する

Web API Client を使うということは、なんらかのリソースを取得したいと考えているはずです。 ここでは User リソースを取得することを考えます。 以下のように User 構造体を定義します。 リソースモデルは Himotoki の Decodable に準拠するようにします。

struct User: Decodable {
    let id: Int
    let name: String
    let email: String

    static func decode(e: Extractor) throws -> User {
        return try User(
            id: e <| "id",
            name: e <| "name",
            email: e <| "email"
        )
    }
}

リクエストを定義する

次にリソースを得るためにどのようなリクエストを投げるか定義します。 /users に GET リクエストを送信してユーザーの一覧を取得しましょう。 このようなリクエストを表現する構造体を定義します。

struct GetUsersRequest: GarageRequestType {
    typealias Resource = [User]

    var method: HTTPMethod {
        return .GET
    }

    var path: String {
        return "/users"
    }

    var queryParameters: [String: AnyObject]? {
        return [
            "per_page": 1,
            "page": 2,
        ]
    }
}

なんとなくやりたいことがわかると思います。 APIKitを知っている人はそのまんまだと感じていると思います。

Garage の設定を定義する

次にGarageアプリケーションへ接続するための情報を用意します。 GarageConfigurationType というプロトコルがあるので、それに準拠する構造体かクラスを定義してそのインスタンスを作ります。ここでは単純にGarageアプリケーションのベースURLとアクセストークンをただ保持している構造体を作りました。実際にはアクセストークンを認可サーバから取得してそれを返してくれるような認証・認可機能を実装したクラスになると思います。

struct Configuration: GarageConfigurationType {
    let endpoint: NSURL
    let accessToken: String
}

let configuration = Configuration(
    endpoint: NSURL(string: "http://localhost:3000")!,
    accessToken: "YOUR ACCESS TOKEN"
)

リクエストを送信する

あとはリクエストを送信するだけです。 GarageClient のインスタンスを作って、sendRequest メソッドでリクエストを送信します。 リクエストのコールバックには Result.Success.Failure が引数に渡されるので結果に応じた処理を記述します。 .Success の場合には、取得したリソースやページングのための件数などの情報を含む GarageResponse 構造体を取得できます。

let garageClient = GarageClient(configuration: configuration)
garageClient.sendRequest(GetUserRequest()) { result in
    switch result {
    case .Success(let response):
        debugPrint(response)

        let users = response.resource
        debugPrint(users)
    case .Failure(let error):
        debugPrint(error)
    }
}

以上が GarageClientSwift を使ったリクエスト送信までの流れです。

Himotoki

Himotoki はJSONをデコードしてモデルにマッピングするためのライブラリです。 https://github.com/ikesyo/Himotoki

この記事では Himotoki 2.0.1 の実装を使った例を出します。

Himotoki を使って以下の様な JSON を User構造体にマッピングするにはこのように記述します。

JSON

{
  "id": 2,
  "name": "bob",
  "email": "bob@example.com"
}

User.swift

struct User: Decodable {
    let id: Int
    let name: String
    let email: String

    static func decode(e: Extractor) throws -> User {
        return try User(
            id: e <| "id",
            name: e <| "name",
            email: e <| "email"
        )
    }
}

e <| "id" のような見慣れない構文が登場しますが、これは Himotoki の Extractor のためのオペレータです。JSONから指定したキーの要素を期待通りの型で取り出すための工夫です。

Himotoki の実装をのぞいてみる

<| はどのような実装になっているのか見てみましょう。

https://github.com/ikesyo/Himotoki/blob/2.0.1/Sources/Operators.swift

infix operator <| { associativity left precedence 150 }

/// - Throws: DecodeError or an arbitrary ErrorType
public func <| <T: Decodable>(e: Extractor, keyPath: KeyPath) throws -> T {
    return try e.value(keyPath)
}

Swift ではオペレータを定義することができるので、その結合の仕方と優先度、処理を定義しています。 <| は Extractor の e.value(keyPath) を呼んでいることがわかりました。

https://github.com/ikesyo/Himotoki/blob/2.0.1/Sources/Extractor.swift

private func _rawValue(keyPath: KeyPath) throws -> AnyJSON? {
    guard isDictionary else {
        throw typeMismatch("Dictionary", actual: rawValue, keyPath: keyPath)
    }

    let components = ArraySlice(keyPath.components)
    return valueFor(components, rawValue)
}

/// - Throws: DecodeError or an arbitrary ErrorType
public func value<T: Decodable>(keyPath: KeyPath) throws -> T {
    guard let rawValue = try _rawValue(keyPath) else {
        throw DecodeError.MissingKeyPath(keyPath)
    }

    do {
        return try T.decodeValue(rawValue)
    } catch let DecodeError.MissingKeyPath(missing) {
        throw DecodeError.MissingKeyPath(keyPath + missing)
    } catch let DecodeError.TypeMismatch(expected, actual, mismatched) {
        throw DecodeError.TypeMismatch(expected: expected, actual: actual, keyPath: keyPath + mismatched)
    }
}

value メソッドはつまり、与えられた keyPath で Dictionary から要素を取り出し、返り値の型の decodeValue を呼びだして値を返しています。TypeConstraints を使って Decodable プロトコルに準拠していることを制限に課しているので、e <| "id"の返り値が Decodable に準拠する型でないといけません。

上記のJSONの "id" 要素は数値なので Int になることを期待します。Int や String のようなよく使う型に対しては Himotoki ですでに Decodable に準拠するための実装が extension で追加されています。 https://github.com/ikesyo/Himotoki/blob/2.0.1/Sources/StandardLib.swift

さて、User 構造体の decode メソッドは以下のように実装していました

static func decode(e: Extractor) throws -> User {
    return try User(
        id: e <| "id",
        name: e <| "name",
        email: e <| "email"
    )
}

User 構造体では、 id は Int、name と email は String と型宣言してあるので、コンパイラが e <| "id"Inte <| "name"String が返ると推論します。型推論がうまく働いてくれるのですっきりした記述になるわけです。

Decodable プロトコルはどのような定義になっているのでしょうか。 https://github.com/ikesyo/Himotoki/blob/2.0.1/Sources/Decodable.swift

static func decode(e: Extractor) throws -> Self を定義していることを要求しています。 これが、先ほど見つけた decodeValue メソッドから呼ばれます。

Swift の Protocol にはデフォルト実装を Protocol extension で追加できます(Swift2 から) なので Decodable に準拠している構造体は decodeValue メソッドを実装していなくてもデフォルトの実装が使われます。

Himotoki の Decodable プロトコルに準拠していれば、JSON から作られたDictionaryを以下のようにしてモデルにマッピングできます。

let user: User? = try? decodeValue(JSON)
let users: [User]? = try? decodeArray(ArrayJSON)

便利ですね! 期待した型とJSONの要素の型が一致しない場合は例外が投げられマッピングに失敗します。

Himotoki は Generics と型推論、Protocol をうまく使った例だと思います。

APIKit

APIKit はリクエストとレスポンスを抽象的に表現できて使いやすいAPIクライアントライブラリです。 https://github.com/ishkawa/APIKit/

リクエストを表す構造体を定義し、それに対応するレスポンスをモデルで受け取れるのが特長です。 リクエストに渡すパラメータの型を明示できます。 リクエスト結果は、成功と失敗のどちらかの状態を表現する Result 型で受け取れます。Result には成功時に目的のオブジェクトを、失敗時にエラー情報を含めることができるので、Optional な変数を用いることなくリクエスト結果を受け取ることができます。

この記事では APIKit 2.0.1 の実装を使った例を出します。

使い方を見てみましょう。 https://github.com/ishkawa/APIKit/blob/2.0.1/Documentation/GettingStarted.md

まずはリクエストを定義します。 サンプルは GitHub API の Ratelimit を取得する API を実行するようです。 RequestType プロトコルに準拠した RateLimitRequest とそのレスポンスを表すモデル RateLimit を定義します。

struct RateLimitRequest: RequestType {
    typealias Response = RateLimit

    var baseURL: NSURL {
        return NSURL(string: "https://api.github.com")!
    }

    var method: HTTPMethod {
        return .GET
    }

    var path: String {
        return "/rate_limit"
    }

    func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) throws -> Response {
        guard let dictionary = object as? [String: AnyObject],
            let rateLimit = RateLimit(dictionary: dictionary) else {
                throw ResponseError.UnexpectedObject(object)
        }

        return rateLimit
    }
}

struct RateLimit {
    let limit: Int
    let remaining: Int

    init?(dictionary: [String: AnyObject]) {
        guard let limit = dictionary["rate"]?["limit"] as? Int,
            let remaining = dictionary["rate"]?["limit"] as? Int else {
                return nil
        }

        self.limit = limit
        self.remaining = remaining
    }
}

RateLimitRequest 構造体には API の baseURLmethodpath などのリクエストを構築するために必要な情報を記述します。 また、レスポンスをどのようにモデルにマッピングするかを responseFromObject メソッドに記述します。

リクエストの定義ができたらそれを使ってリクエストを投げます。 コールバックには Result<T, Error> が渡されるのでそれに応じた処理を記述します。 .Successの場合はレスポンスをマッピングしたモデルが含まれているので、後は好きなように扱えばよいでしょう。 RateLimitRequestのレスポンスはRateLimitと定義してあるので、resultResult<RateLimit, Error> であり、.Success<RateLimit> が渡されるわけです。なので limitremaining のプロパティにアクセスできます。

let request = RateLimitRequest()

Session.sendRequest(request) { result in
    switch result {
    case .Success(let rateLimit):
        print("limit: \(rateLimit.limit)")
        print("remaining: \(rateLimit.remaining)")

    case .Failure(let error):
        print("error: \(error)")
    }
}

APIKit の実装をのぞいてみる

APIKit の RequestType プロトコルの実装を見てみましょう。 https://github.com/ishkawa/APIKit/blob/2.0.1/Sources/RequestType.swift

RequestType はリクエストを表現する構造体が準拠すべきプロトコルでした。 baseURLmethodpathqueryParametersheaderFields などなど様々なプロパティがありますがほとんどにデフォルト実装が用意されており、オプションのパラメータはリクエストを定義する際に指定したいものだけ実装すれば良いようになっています。

受け取ったレスポンスをパースしたオブジェクトをどのようにモデルにマッピングするかを以下のメソッドに記述します。デフォルトではレスポンスに JSON を期待しています。dataParser プロパティを指定すれば JSON 以外も受け付けることができます。

func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) throws -> Response

他にも以下の様なメソッドが宣言されています。

func interceptURLRequest(URLRequest: NSMutableURLRequest) throws -> NSMutableURLRequest
func interceptObject(object: AnyObject, URLResponse: NSHTTPURLResponse) throws -> AnyObject

これらのメソッドをリクエストの構造体に実装することで送信する URLRequest に追加の情報を付与したり、レスポンスに応じて独自のエラーを投げてエラーレスポンスを処理することができるようになっています。 これらのメソッドも必要でなければデフォルト実装が利用されるので定義を省略することができます。

APIKit は Swift の Protocol をうまく利用していると思います。

次に Session を見てみましょう。 https://github.com/ishkawa/APIKit/blob/2.0.1/Sources/Session.swift

Singleton の Session オブジェクトを持っているので、通常の利用範囲であればクラスメソッドの Session.sendRequest メソッドを使えば良いようになっていることがわかります。

sendRequest メソッドは TypeConstraints で引数 request の型 RequestRequestType に準拠しているべきと制約を課しています。

public func sendRequest<Request: RequestType>(request: Request,
                                        callbackQueue: CallbackQueue? = nil,
                                              handler: (Result<Request.Response, SessionTaskError>) -> Void = {r in})
                                              -> SessionTaskType? {
...

RequestType には以下の様な記述がありました。

public protocol RequestType {
    /// The response type associated with the request type.
    associatedtype Response
...

これは Protocol の Associated Types という機能で定義するプロトコルに関連する型を指定できるものです。 以下のように RateLimitRequest の Response 型を typealias キーワードで指定することができます。

struct RateLimitRequest: RequestType {
    typealias Response = RateLimit
...

これにより先ほどの sendRequest メソッドの handler 引数にある Result<Request.Response, SessionTaskError> の記述が、RateLimitRequest の場合は Result<RateLimit, SessionTaskError> に定まるわけです。 こうして、リクエストとそれに対応するレスポンスのモデルの型を明示できるようになっています。

このようにして APIKit はリクエストとレスポンスを表現するモデルをわかりやすく定義できるように作られています。 僕のお気に入りのライブラリです。

GarageClientSwift の実装

GarageClientSwift はこれまで説明してきた Himotoki と APIKit を組み合わせて作ったライブラリです。 すでに利用例で見せたように、Himotoki を使った Decodable なリソースのモデルを用意し、APIKit のようにリクエストを表現してリクエストを送信します。

やっていることは APIKit をラップして、Garage アプリケーションの認証に必要なアクセストークンをリクエストに付与したり、Garage のレスポンスに共通で含まれるページング等の情報を持った値を表現する GarageResponse を返すようにしています。

少し工夫したところはリソースの型に User[User] のようにモデルの配列も指定できるようにしたところです。

GarageClient にふたつの sendRequest を定義しています。 https://github.com/slightair/GarageClientSwift/blob/1.1.0/Sources/GarageClient.swift

public func sendRequest<R: GarageRequestType, D: Decodable where R.Resource == D>
    (request: R,
     handler: (Result<GarageResponse<D>, SessionTaskError>) -> Void = { result in })
    -> SessionTaskType? {
        let resourceRequest = RequestBuilder.buildRequest(request, configuration: configuration)
...

public func sendRequest<R: GarageRequestType, D: Decodable where R.Resource: CollectionType, R.Resource.Generator.Element == D>
    (request: R,
     handler: (Result<GarageResponse<[D]>, SessionTaskError>) -> Void = { result in })
    -> SessionTaskType? {
        let resourceRequest = RequestBuilder.buildRequest(request, configuration: configuration)
...

リクエストの ResourceDecodable または Decodable を要素に持つ CollectionType を受け付けています。

RequestBuilder にもふたつの buildRequest を定義しており、それぞれ SingleResourceRequestMultipleResourceRequest を作ります。 https://github.com/slightair/GarageClientSwift/blob/1.1.0/Sources/RequestBuilder.swift

struct RequestBuilder {
    static func buildRequest<R: GarageRequestType, D: Decodable where R.Resource == D>
        (baseRequest: R, configuration: GarageConfigurationType) -> SingleResourceRequest<R, D> {
        return SingleResourceRequest(baseRequest: baseRequest, configuration: configuration)
    }

    static func buildRequest<R: GarageRequestType, D: Decodable where R.Resource: CollectionType, R.Resource.Generator.Element == D>
        (baseRequest: R, configuration: GarageConfigurationType) -> MultipleResourceRequest<R, D> {
        return MultipleResourceRequest(baseRequest: baseRequest, configuration: configuration)
    }
}

SingleResourceRequestMultipleResourceRequest の違いは、中で呼んでいる Himotoki のメソッドが decodeValuedecodeArray かの違いです。 ともに ResourceRequest プロトコルに準拠しており、このプロトコルは APIKit の RequestType を継承しています。 前述した GarageRequestType は APIKit の RequestType 風のプロトコルですが、実際には APIKit の sendRequest に渡す ResourceRequestRequestBuilder が作り GarageRequestType から値を取っていたのでした。 https://github.com/slightair/GarageClientSwift/blob/1.1.0/Sources/GarageRequestType.swift

今回のような範囲では Class の継承ではなく Protocol を使うとすっきりと書けます。 Swift の Protocol は Protocol extension によるデフォルト実装の提供が強力で、継承ができない struct であっても Protocol の組み合わせで拡張していくことができます。 このような Protocol を組み合わせていくプログラミング手法を Apple は Protocol Oriented Programming として提唱しています。

まとめ

GarageClientSwift というライブラリを紹介しつつ、このライブラリの実装に利用した Himotoki、 APIKit と GarageClientSwift 自身の実装を読み、Protocol や Generics を使った実装例の説明をしました。 Swift は新しい言語であり、おもしろい機能や新しいプログラミング手法を提供してくれます。単なる Objective-C の置き換えでアプリケーションを楽に記述するための言語とは捉えずに、 Swift の言語機能を使ってより柔軟で安全なコードを記述して素敵なアプリケーションを作りましょう。