Cookpad TechConf 2018 開催報告

こんにちは、技術広報を担当している外村です。

f:id:hokaccha:20180210115823j:plain

2018年2月10日にエンジニア向けのカンファレンス、Cookpad TechConf 2018を開催しました。当日はたくさんの方に参加いただき、活気あるカンファレンスになりました。ご来場の皆様本当にありがとうざいました。

新しい試みとして、当日の司会をAmazon Pollyの音声合成で行なったのですが、こちらもみなさんにお楽しみいただけたようでした。

講演資料・動画

当日の講演資料および動画を公開いたしましたので是非ご覧になってください。

基調講演: 毎日の料理を楽しみにする挑戦をし続けた20年 by 橋本 健太

コーポレート戦略部本部長の橋本による基調講演でイベントはスタートしました。クックパッドはテックカンパニーとしてどのように成長してきたか、グローバル展開をどのように行ってきたか、現在取り組んでいる新プロジェクトについての話などがありました。

講演資料・動画

クックパッドの "体系的" サービス開発 by 新井 康平

会員事業部の新井の講演は、クックパッドではサービス開発の難しさにどのように立ち向かっているか、という内容です。こちらの講演の捕捉記事を公開していますのでこちらもご覧になってください。

"体系的" に開発サイクルを回して "効果的" に学びを得るには - クックパッド開発者ブログ

講演資料・動画

クックパッドクリエイティブワークフロー by 辻 朝也

会員事業部デザイナの辻の講演は、クックパッドにおけるサービス開発のフローについての話です。やるべき施策を決めてリリースし、分析して評価するという一連のサイクルの中で具体的にどういったことをおこなっているのか、というのがよくまとまっていました。

講演資料・動画

What/How to design test automation for mobile by 松尾 和昭

海外事業部にてサービスの品質向上やテストを担当している松尾の講演は、モバイルテストの自動化についての話です。モバイルのテストで重要なトピックをSPLIT(Scope, Phase, Level, sIze, Type)というキーワードにまとめて解説しました。

講演資料・動画

Rubyの会社でRustを書くということ by 小林 秀和

インフラストラクチャー部の小林の講演は、Rustを使ったプロダクト開発についての話です。CookpadはRubyを使ってサービス開発をすることが多いですが、そういった環境でRustを採用した経緯や、実際にRustを導入したプロダクトで得られた知見を紹介しました。

講演資料・動画

cookpad storeTV 〜クックパッド初のハードウェア開発〜 by 今井 晨介

メディアプロダクト開発部の今井の講演は、cookpad storeTVについての話です。cookpad storeTVはスーパーに設置する料理動画を配信するサイネージで、クックパッドがハードウェアの開発から手がけました。今井はその開発を担当しており、実際に発生した問題や具体的な開発フローについて紹介しました。

講演資料・動画

Challenges for Global Service from a Perspective of SRE by 渡辺 喬之

インフラストラクチャー部SRCグループの渡邉の講演は、クックパッドのグローバルサービスのSREとしてどのような取り組みをしてきたか、という内容です。グローバルサービスならではの課題というのはどういったものがあり、それをどう解決したのかという、あまり他では聞くことが少ない興味深い話でした。

講演資料・動画

動き出したクックパッドのCtoCビジネス by 村本 章憲

Komerco事業部の村本からの講演は、クックパッドの新規事業であるKomercoについての話です。Komercoとはどのようなサービスなのか、どのようなチーム・技術スタック・フローで開発しているかということについて紹介しました。

講演資料・動画

Solve "unsolved" image recognition problems in service applications by 菊田 遥平

研究開発部の菊田の講演は、機械学習による画像分析の取り組みについての話です。機械学習をサービスに活かすうえで難しい問題はどういったところにあるのか、それを実際の業務でどのように解決したか、という内容でした。

講演資料・動画

基調講演: Beyond the Boundaries by 成田 一生

取りをつとめたのはCTOの成田による基調講演でした。クックパッドの技術スタックはどのようなものか、エンジニアの行動指針である「Beyond the Boundaries」とは何なのか、エンジニアが成長できるために具体的にどういった取り組みを行っているか、といった話でイベントを締めました。

講演資料・動画

Lifestyle Product Award授賞式

講演の途中に、昨年開催した2017 Lifestyle Product Award by Cookpadの表彰式をおこないました。今回は優秀賞としてGOKURIが選出されました。

GOKURIは嚥下機能、飲み込みの能力を計測するためのデバイスです。基礎的な研究は数年前から行なわれていたものの、精度が課題となっていました。昨年、深層学習による精度向上によってプロダクトとしてリリースできる水準となり、リハビリ学会などでその成果が発表されました。

感想エントリ

当日参加いただいた方の感想エントリを以下にまとめました。素敵な記事を書いていただき、ありがとうございます。

他にもありましたら@hokacchaまでお知らせください!

まとめ

クックパッドにおけるサービス開発の手法やプロダクト開発の事例、その背景にある技術的なトピックなど、幅広い領域の講演をお届けしました。当日参加いただいた沢山の方に楽しんでいただいたようです。

クックパッドでは引き続き、このようなイベントを開催していきます。ぜひ、楽しみにしていてください!

"体系的" に開発サイクルを回して "効果的" に学びを得るには

会員事業部エンジニアの新井( @SpicyCoffee66 )です。 Splatoon2 で各ルール S+1 以上になるため日々奮闘中のところに MHW が発売されました。 加えて最近ぷよぷよを始めたので、どう考えてもいろいろ計算が合わなくなってきました。

本日おこなわれた Cookpad TechConf 2018 では「クックパッドの "体系的" サービス開発」と題し、社内でどのような点に気をつけて開発サイクルが回されているかをお話しさせていただきました。 動画・発表資料は後日アップロードされる予定ですので、よろしければ合わせてご覧ください*1
今回は、TechConf 2018 での発表内容から、BML ループの運用について、多少の補足や要約を交えながら書きたいと思います。

サービス開発は難しい

まず前提として、サービス開発は難しいです。 その難しさの大部分は、以下の2つの要因からきています。

  • 到達するべきサービスのゴールが明確でない
  • サービスの今いる地点が明確でない

到達するべきサービスのゴールが明確でない

サービス開発においては、ユーザーさんの持つ欲求や、抱えている課題を解決することがゴールとなります。 しかし、この欲求や課題は、ユーザーさん本人を含めて、誰にもわからないことがほとんどです。 また、仮に一度この欲求を捉えたとしても、その後時間とともに変化してしまうことが一般的です。

私たちが何もしなくても、ユーザーさんの使うデバイスはガラケーからスマートフォン、タブレットへと移り変わっていくでしょうし、 個々人のライフステージに関しても、独身だったユーザーさんが結婚し、子どもを持つようになるなど、多くの変化が起こり得ます。 このような状況のもとでは、ユーザーさんが抱えている欲求も、常に変化し続けると考えるほうが理にかなっています。

サービスの今いる地点が明確でない

得てしてサービス開発者は、自分のサービスを正しく理解できていないことがほとんどです。 サービスの価値はこうだ!コアとなる機能はこれだ!と信じていても、 実際にユーザーさんに使ってもらっているところを見てみると、想像と全く違う使われ方をしていた、なんてことは日常茶飯事でしょう。

学びのサイクル

前述したような状況の中でサービス開発に取り組むためには、自分たちの仮説をユーザーさんにぶつけ、その結果からフィードバックを得ることで新たな仮説を立てるという作業を繰り返す必要があります。 そうるすことで、目指すべきゴールや、サービスの今いる地点を確認しながら前に進んでいくわけです。 この「自分たちの仮説をユーザーさんにぶつけ、その結果からフィードバックを得ることで新たな仮説を立てる」という行為を、開発サイクルや学びのサイクルと呼んでいます。

BML ループ

学びのサイクルを実現するフレームワークに、BML ループと呼ばれるものがあります。 これは、リーン・スタートアップの中で提唱されているフレームワークで、以下の 3 フェーズから成ります。

  1. 仮説からプロダクトを作成する Build
  2. プロダクトをリリースしユーザーの利用状況を計測する Measure
  3. 得られたデータから知見を抽出し、新たな仮説を構築する Learn

f:id:spicycoffee:20180210144116p:plain

これらのフェーズの頭文字を取って BML ループと呼ばれているわけです。 このループを数多く回しながら、その都度学びを得ていくのがサービス開発では重要になってきます。

よくある失敗とその対策

しかしながら、サービス開発ではサイクルを回しながら学びを得るのが重要であるということがわかったところで、実際に BML ループを回そうとすると大抵の場合どこかのフェーズで失敗します。 具体的には、各フェーズで

  • Build
    • プロダクトが不必要に大きくなって実装に時間がかかる
    • 検証したい仮説と完成したプロダクトの機能が噛み合っていない
    • そもそも仮説に考慮漏れがある
  • Measure
    • いざ計測しようとするとログが埋まっていない
    • 複数の A/B テストが衝突して計測結果に影響が出る
    • 集計 SQL に間違いがあり、最悪の場合それに気がつかない
  • Learn
    • 出てきた数字をどう解釈すればいいかイマイチわからない
    • 数字は動いたがその原因がわからない、再現性が取れない
    • 得られた知見が属人的になる、あるいは闇に消える

といったような失敗がよく起こります。

f:id:spicycoffee:20180210144443p:plainf:id:spicycoffee:20180210144447p:plainf:id:spicycoffee:20180210144451p:plain

※ 巷にあふれる失敗例

このような失敗をなるべく減らすために、社内では 「最初に BML ループ全体を設計する」 ということが意識されています。 そうすることで「手戻りの防止」や「効率的な学び」を実現することが可能になります。

最初にサイクル全体を設計する

手戻りの防止

BML ループを、Build が終わってから Measure、Measure が終わってから Learn といったように、逐次的に実行した場合、大きな手戻りに繋がる可能性があります。

たとえば、Build が終わって Measure のフェーズに入ったタイミングで、ログが取れてないなかったことが発覚した場合、もう一度 Build のフェーズに立ち返ることになります。 Learn のフェーズに入って知見を抽出しようとしたタイミングで、数字の解釈がよくわからないといった状況に陥ってしまうと、もはや手戻りをすることすら難しく、何の学びも得られないままサイクルを回し終えてしまうこともありえます。

しかし、よく考えると、前のフェーズが終わらなくても、次のフェーズで何をやるかについては考えることが可能です。 むしろ、BML ループ自体を一つのプロジェクトと考えると、各フェーズを前から順番に実行していくようなやり方よりも、 最初に全体を設計することが自然に思えてきます。 BML ループにおいて、最初に全体を設計するというのは、仮説が立った段階で、各フェーズで必要になりそうなことを明確にしておくことになります。 実際にサイクルを回しだす前に、各フェーズでの要件を明確にした上で、それに沿って Build・Measure・Learn と施策を進めていくことで、手戻りの原因となりうる事故を事前に察知 し、それを防ぐことが可能になるのです。

効率的な学び

最初にサイクル全体を設計することは、効率的に学びを得るためにも必要なことです。 "学び" は定義しにくい概念ではありますが、その一つの重要な要素として、 サービスに対する理解と現実とのギャップ があげられます。

これをもう少し具体化すると、サービスに対する理解は、今のサービスに対して施策を打ったときの、施策結果に対する予想と考えることができます。 それに対応する現実は、実際に施策を打った結果です。 この2つを比較することで、思ったより結果が良かった/悪かったといった事実が出てきます。 その事実について「それはなぜか?」という点を考えることで、自分たちがサービスに対して抱いている理解のズレ・勘違いが明確になり、それが大きな学びになるのです。

f:id:spicycoffee:20180210144500p:plain

したがって、サービスに対する理解を事前に固めておくことは非常に重要なことになります。 今のサービスに施策を打ったとき、ユーザーさんはどのような体験をして、その結果どういう指標がどのくらい動きそうかということを事前に予想しておくことが大事なのです。 これはすなわち、事前に Measure や Learn のフェーズで出てくる結果について、考えを巡らせておくことになります。 こういった観点からも、実際にサイクルを回しだす前に、先のフェーズの設計をおこなっておくことが重要になってきます。

各フェーズの設計

この節では、BML ループの各フェーズについて、何を設計すればよいのか、つまりは、具体的にどのようなことを事前に決定しておけばよいのかについて書いていきます。

Build の設計

Build の設計では、以下のようなことを決定・確認しておきます。 とはいっても、このフェーズは仮説立案から距離が近いため、特に意識せずとも施策立案の段階で合わせてやっていることも多いでしょう。

  • 絶対に検証したい仮説の明確化
  • 検証背景の整理
  • 検証内容・注意点などの整理

Measure の設計

Measure の設計では以下の様なことを決定・確認しておきます。

  • 計測手法
    • A/B テストでいいのか?
    • A/B テストでいいとして、全ユーザーを対象にしてもいいのか?
  • KPI
    • 施策をどういう数字で評価するのか
    • 他に影響を与える指標はないか?
  • ログの確認、SQL の実行
    • 取りたいデータに必要なログは、現在集計されているか?
    • SQL を叩いた結果が概ね正しそうか?

特に KPI 周りの設計については注意が必要です。 指標というのはそれ単体で存在することは珍しく、大抵は相反する指標、影響を与え合う指標が同時に存在します。 これらの指標を見落としてしまうと、たとえば

TOP ページにプロトタイプとして会員登録の導線を置いたら、ある程度の会員登録が認められた
→ この施策を採用してプロダクトにリリースした
→ しばらく経ってみると、別のページ(検索結果ページ等)からの会員登録数が減っていた

といったような事故が発生します。 このような事例を防ぐために、予め関連する指標としてどのようなものがありそうかリストアップしておくことが必要です。

Learn の設計

Learn の設計では以下の様なことを決定・確認しておきます。

  • 指標の解釈
    • この数値が高くてこの数値が低いときは、ユーザーさんはどのような体験をしているのだろうか?
  • 結果の想定
    • 測定指標がどのくらいの数字になったら施策を採用するか
    • そこまではいかなくても、どのくらいの数字になったら再度議論するか

この2つの項目について事前に考えておくことを、社内では「成功のイメージを共有する」といったような言葉で表現することが多いです。 施策が成功したときに、ユーザーさんがどのような体験をして、その結果どういった指標がどの程度まで上がっているだろうか といったことを事前に想定しておきます。 そうすることで、結果から意味のある知見を抽出しやすくなりますし、効果の薄い施策を採用してしまう可能性も減らすことができます。

まとめ

上述したように開発サイクルを回していくやり方は、既に当たり前の方法となっています。 しかし、当たり前の方法だからこそ、自分たちの中で注意するポイントをしっかりと定めて運用することで、より大きな効果を期待することができます。 組織の特性や置かれている状況によって、注意するポイントは変わってくると思いますが、一つのベースとして、この記事がみなさんの参考になれば幸いです。

*1:こちら のページにアップロードされました。

Cookpad の新規事業と Firebase

国内事業開発部 iOS エンジニアの三浦です。私は17年新卒で入社したのですが、それ以来複数の新規事業の開発に携わってきました。 現在開発中のアプリでは、バックエンドに Firebase を用いた開発を進めています。 この記事ではなぜ Firebase を使っているのかと、そこで得られた知見についてまとめようと思います。

なぜ Firebase

みなさんご存知かと思いますが、Cookpad のレシピサービスでは主にバックエンドに AWS と Ruby on Rails が使われています。 なぜ新規事業ではその構成ではなく Firebase を使うのかということですが、以下のような理由があります。

基盤サービスが豊富

Firebase には RealtimeDatabase、FireStore といった Database を始めとして、CloudMessaging(Push通知基盤)、Authentication(認証基盤)といった開発のためのツールがあります。 これらの機能はサービス開発において大抵必要不可欠なものですが、サービスリリースまでの間ではメインの機能に時間を取られ、あまり時間を割くことができない部分になります。

これらの基盤部分が開発開始時から品質が担保された上で提供されていることで、本来のサービス開発に時間をかけることができ、開発スピード、アプリケーションの品質を高くすることができます。 また作っては壊しといったことを繰り返すリリース前の段階での開発においては、修正の範囲がクライアントのみで済むため非常にコストが低くアプリケーションの改修を行うことができます。

実際にどれくらい楽に実装できるか、簡単な iOS でのサンプルコードを載せます。

認証

ユーザーモデルを定義して Firebase に Facebook ログインするサンプルは以下のように書くことができます。 FireStore のモデルフレームワークとして Pring を利用し、モデルでは facebook のユーザーIDと名前をプロパティに持つとします。

// User model
import Pring

class Firebase { }
extension Firebase {
    class User: Object {
        @objc dynamic var name: String?
        @objc dynamic var facebookUserID: String?
    }
}

コントローラーから sign in するときにはこのような処理で実現できます。 実際は ViewModel や Helper を利用して処理を分割するのですが、サンプルなので1つのメソッドで処理を完結させています。

import UIKit
import Firebase
import FacebookLogin
import FacebookCore

class SignInViewController: UIViewController {
    private func signInWithFacebook() {
        let loginManager = LoginManager()
        // Facebook へログイン
        loginManager.logIn(readPermissions: [.email, .publicProfile, .userFriends], viewController: self) { result in
            switch result {
            case .success(_, _, let token):
                // Facebook からユーザー情報を取得
                GraphRequest(graphPath: "me").start { (response, result) in
                    switch result {
                    case .success(let response):
                        let userID: String = response.dictionaryValue!["id"] as! String
                        let username: String = response.dictionaryValue!["name"] as! String
                        let credential = FacebookAuthProvider
                            .credential(withAccessToken: token.authenticationToken)
                        // Firebase への認証
                        Auth.auth().signIn(with: credential) { (user, error) in
                            if let error = error {
                                // error handling
                                return
                            }
                            let user = Firebase.User()
                            user.name = username
                            user.facebookUserID = userID
                            // User モデルを FireStore に save
                            user.save { (_, error) in
                                if let error = error {
                                    // error handling
                                    return
                                }
                                // 認証成功
                            }
                        }
                    case .failed:
                        // error handling
                        break
                    }
                }
            case .cancelled: // Facebook へのログインがキャンセルされた
                break
            case .failed: // Facebook へのログインが失敗した
                break
            }
        }
    }
}

電話番号や、Twitter、メールアドレスとパスワードによる認証に関しても同じような処理で実装をすることができます。

通知

次は Firebase Cloud Messaging を利用して Push 通知を受け取れるようにします。

User の Model に Token を管理できるようにようにプロパティを増やし、現在のログインユーザーを取得するメソッドを追加します。

// User model
import Pring

class Firebase { }
extension Firebase {
    class User: Object {
        typealias DeviceID = String
        typealias Token = String

        @objc dynamic var name: String?
        @objc dynamic var facebookUserID: String?
        // DeviceID をキー、FCMToken をバリューに持つ Dictionary
        @objc dynamic var fcmTokens: [DeviceID: Token] = [:]

        static func current(_ completion: @escaping (Firebase.User?) -> Void) {
            guard let authUser = Auth.auth().currentUser else {
                return completion(nil)
            }
            self.get(authUser.uid) { user, _ in
                guard let user = user else {
                    completion(nil)
                    return
                }
                completion(user)
            }
        }
    }
}

あとは通知を登録したいところで Tsuchi(https://github.com/miup/Tsuchi) いう私の開発したライブラリを利用して Token を取得することができるので、 その Token をユーザーに DeviceID と共に保存します。

import Tsuchi

func saveFCMToken(_ token: String, completion: (()-> Void)?) {
    Firebase.User.current { user in
        guard let user = user else { return }
        user.fcmTokens[UIDevice.current.identifierForVendor!.uuidString] = token
        user.update { _ in completion?() }
    }
}

Tsuchi.shared.didRefreshRegistrationTokenActionBlock = { token in
    saveFCMToken(token)
}
Tsuchi.shared.register { granted in
    if !granted {
        // ユーザーが登録を拒否
        return
    }
}

通知を受け取ったときの処理に関しても Tsuchi を利用して以下のように書くことができます。

import Tsuchi

// payload の型
struct FCMNotificationPayload: PushNotificationPayload {
    var aps: APS?
    // custom payload data
}

// payload の型と通知受取時の処理を渡して push 通知を subscribe する
Tsuchi.shared.subscribe(FCMNotificationPayload.self) { result in
    switch result {
    case .success(let notification):
        let (payload, _) = notification
        print(payload)
    case .failure(let error):
        // error handling
        break
    }
}

// ログアウトなどで通知の受取を終了するとき
Tsuchi.unregister {
    Firebase.User.current { user in
        guard let user = user else { return }
        _ = user.fcmTokens.removeValueForKey(UIDevice.current.identifierForVendor!.uuidString)
        user.update()
    }
}

Growth もしっかりしている

リリース後に関しても Analytics や Crash Report が用意されていること、 ユーザーの行動を元に機械学習でユーザーのセグメント分けをしてくれる Prediction 機能、さらにそれを利用してABテストを行う事もできるなど、サービスのグロースに関する部分でもかなり強力なツールが揃っているため、将来的にも有用だろうということで技術選定をしました。

外部サービスとの連携

ここまで Firebase の利点についてお話してきましたが、当然 Firebase では用意されていないサービスも多くあります。

例えば現在開発しているサービスで必要なものだと、全文検索や決済機能などがあります。 それらの機能はすでに外部 SaaS が用意されているため、私たちは図のように Firebase の FaaS (Function as a Service) である CloudFunctions を利用してそれらのサービスとの連携を行っています。

f:id:MiuP:20180209102151p:plain

CloudFunctions では DB への変更をトリガーにして関数を発火することができるため、変更のあったオブジェクトから外部サービスへ渡すデータを構成し渡すだけの必要最低限の実装で外部サービスとの連携が行なえます。 実際にどのように連携を実装しているかという部分に関しては自分が Firebase.Yebisu #1 での登壇でまとめてありますので、こちらの記事も一緒に読んでいただければ詳しい部分についても理解できると思います。

Firebase コミュニティへのコミット

Firebase をフルでバックエンドに置くサービスは世界的にもあまり多くはありません。そのため開発において、壁に当たることも多くあります。私達のチームではそれらの問題と解決法に関して外部にアウトプットしていくことを積極的に行っており、Firebase.Yebisu といったイベントや、サンプルコードでもいくつか登場しましたが、ライブラリを OSS として Github 上で公開するなど、コミュニティへの貢献も進めています。 実際に私達のチームのメンバーが開発した Firebase 関連のライブラリは以下です。

  • Salada (Realtime Database model framework)
  • Pring (Cloud Firestore model framework)
  • Tsuchi (Firebase Cloud Messaging helper)
  • Lobster (RemoteConfig helper)

もし Firebase を利用した開発をする場合は、よろしければ一度使用してみてください。

まとめ

AWS + Ruby on Rails の会社だと思われがちな Cookpad ですが、社内外向けを問わず新規アプリケーションではサービス毎に特徴を考慮し様々なフレームワーク、言語を用いた開発が行われています。 先程も述べましたが Firebase をフルで利用しているサービスは業界でもそれほど多くはないと思いますので、今後もいろいろな形で経過を報告していけたらと思います。