GameWith Engineering Blog

GameWith のエンジニア、デザイナーが技術について日々発信していきます。

Firebaseをプロダクトに採用して得た知見 #GameWith #TechWith

こんにちは、GameWith iOSエンジニアの@kyamです。

前回こちらのiOSアプリの開発記事を書いて以来の執筆となります。 tech.gamewith.co.jp

今回のブログ内容と関連するので是非一読していただけると今回の記事がスムーズに読めるかと思います。

なぜFirebaseの記事を書こうと思ったか

Firebaseに関する記事は巷にたくさんあると思いますが改めて記事を書こうと思った理由として、主に2つあります。

  • 記事を書く際に改めてブログで扱う技術について再調査する事が多く、知らなかったことを見つけ、実際に既存のプロダクトに活かすという事がよくある
  • 社内で新規プロダクトや既存プロダクトにFirebaseを採用する事例が増えているので、社内に向けても知見共有になれば良いなと思った

上記の理由からブログを書くことにしたのですが、今回の構成としてはiOSとWeb(管理画面)の開発でそれぞれ取り入れたFirebaseのサービスを順に簡単に紹介しつつ、採用して良かった点や、苦労した点、今後より最適化していきたい点などをまとめていきたいと思います。

前回のブログを書いてから今日までプロダクト改善や新規施作のためにいくつかのFirebaseが提供している機能を活用しました。 それぞれ順に説明しつつ、簡単に実装例などを紹介します。

RemoteConfig

Firebase Remote Config  |  Firebase

一つ目の紹介です。

Firebaseの管理画面でサービス側パラメータ値を変更することで、アプリのデフォルトの動作や外観を変更することができます。内容にもよりますが基本的にアプリ起動時にパラメータ値のfetchを行います。

キャッシュの期間なども設定できますが、開発中はすぐさま管理画面上でのパラメーター値の変更をアプリ側で検知したいため isDeveloperModeEnabled を有効にするのがオススメです。

        config = RemoteConfig.remoteConfig()
        config.configSettings = RemoteConfigSettings(developerModeEnabled: false)
        let expirationDuration = config.configSettings.isDeveloperModeEnabled ? 0 : 3600
        config.fetch(withExpirationDuration: TimeInterval(expirationDuration)) { (status, error) in
            if status == .success {
                self.config.activateFetched()
            } else {
                if let e = error {
                    SBLog.error(e)
                }
            }
        }

A/B Testing

Firebase A/B テスト

A/B Testを作成する事ができる機能です。
上記で紹介したRemoteConfigを用いて、どちらのパターンがサービスにとって最適かを継続率や独自に設定したイベントを元に計測する事ができます。

Mippleでは動画の再生に到達するまでに二つのUIフローを用意し、どちらがより再生されるか、継続率の数値が良いかなどを検証することにしました。

    func checkMenuUIStatus() -> Bool {
        let showNewMenu = config[showNewMenuUIKey].boolValue
        if showNewMenu {
            Analytics.setUserProperty("新UI", forName: UserProperty.MENU_UI.rawValue)
        } else {
            Analytics.setUserProperty("旧UI", forName: UserProperty.MENU_UI.rawValue)
        }
        return showNewMenu
    }

ユーザープロパティをセットしてあげると予めセットしてあるイベントに対しても比較することができるので極力セットしたほうが良いです。 Firebase側は下記の添付画像のように自動でテスト結果を表示してくれます。(※数字は伏せています)

f:id:keeetaka:20181107132822p:plain

f:id:keeetaka:20181107135436p:plain

いつでも比率を増加させる事ができるので、AとBのテストの途中で明らかにBの方が良い数値であれば、アプリのアップデートを待たずしてBを100%のユーザーに適用する事なども管理画面上から実行できます。 RemoteConfigと組み合わせると本当にあらゆるテストが簡単にできるので気軽にエンジニアにこれテストできない?と聞いてみましょう。

Dynamic Links

簡単にディープリンクを作成する事ができるサービスです。
ダイナミックリンクを開くと、ネイティブアプリのリンク先のコンテンツに直接移動し、アプリがインストールされていない場合にはストアページに飛ぶといったものです。 管理画面上でリンクを発行することもできるのですが、Mippleではアプリ内で動的にリンクを作成しています。

Mippleで想定したユーザーの使い方は、

  • ユーザーが動画をシェアしようとする(その際に動的にアプリ内でダイナミックリンクを作成)
  • 実際にシェアされたリンクを別のユーザーがクリック、既にアプリをインストール済みのユーザーは再生画面が開き、そうでないユーザーはストアページに飛ぶ

といったものです。

extension DynamicLinker {
    func createDynamicLink(video: Video, completion: ((_ withUrl: URL?) -> Void)?) {
        guard let link = URL(string: "https://mipple.page.link/\(video.videoId)") else { return }

        let dynamicLinksDomain = "mipple.page.link"
        let linkBuilder = DynamicLinkComponents(link: link, domain: dynamicLinksDomain)

        let iOSParameters = DynamicLinkIOSParameters(bundleID: bundleID)
        iOSParameters.appStoreID = appStoreID
        linkBuilder.iOSParameters = iOSParameters
        
        let otherPlatformParameters = DynamicLinkOtherPlatformParameters()
        otherPlatformParameters.fallbackUrl = URL(string: "https://itunes.apple.com/jp/app/id1406664366")
        linkBuilder.otherPlatformParameters = otherPlatformParameters
        
        let socialMetaTagParameters = DynamicLinkSocialMetaTagParameters()
        socialMetaTagParameters.title = video.videoTitle
        socialMetaTagParameters.descriptionText = video.videoDescription
        if let thumbnailUrl = URL(string: video.videoThumbnailUrl) {
            socialMetaTagParameters.imageURL = thumbnailUrl
        }
        linkBuilder.socialMetaTagParameters = socialMetaTagParameters
        
        let navigationInfoParameters = DynamicLinkNavigationInfoParameters()
        navigationInfoParameters.isForcedRedirectEnabled = true
        linkBuilder.navigationInfoParameters = navigationInfoParameters
        
        guard let longDynamicLink = linkBuilder.url else { return }
        
        let options = DynamicLinkComponentsOptions()
        options.pathLength = .short
        linkBuilder.options = options
        
        linkBuilder.shorten { (url, warnings, error) in
            if let shortUrl = url {
                completion?(shortUrl)
            } else {
                completion?(nil)
            }
        }
    }

開発途中のコードになりますが、流れはコードの通りです。
渡された Videoオブジェクトからシェアの際に必要な情報(リンクを作成するのに必要なVideoIDやシェアされた時に表示されるサムネイル情報など)を抜き取り最後に短縮URLにするといった内容です。

ただ、実際には該当するWebページが必要で(Mippleの例でいうとWebの再生画面)、その画面でアプリで開くボタンなどを押した時にアプリ内の該当画面に飛ばすというのが望ましい挙動ですので、一旦Webを用意するまで上記の機能は封印しています。

Cloud Messaging

簡単にPush機能を入れる事ができるサービスです。
配信比率や配信先のグループ(RemoteConfigの対象値で振り分けなど)を指定できるので効果的に運用できれば良さそうです。 Pushサービスは他にもたくさんあるのですが、FirebaseSDKをサービスに導入しているのであれば使って見ましょう。

カスタムデータも送信できるので、アプリ側で値を見てアプリ内の該当ページにディープリンク的に飛ばすなども無料でできます。

Authentication

Firebase Authentication  |  Firebase

管理画面のログイン管理に用いています。 管理画面では任意のメールアドレスとパスワードでのアクセスにしていますが、SNSログインなどにもFirebase Authentication自体は対応しています。

firebase.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
  // Handle Errors here.
  var errorCode = error.code;
  var errorMessage = error.message;
  // ...
});

Database

動画データの管理に Cloud Firestore を採用しています。

今まではアプリ側でYoutubeAPIを叩きMippleアカウントから動画を引っ張ってきていたのですが、今後機能追加などを検討していく中でYouTubeAPIだけでは必要な動画情報が足りないということになりました。 そのため独自にDBを持ち、様々動画データを管理する必要性が出てきました。

AWSのRDSを使って・・API作って・・など色々検討したのですがインフラの構築、管理、API設計などスケールを気にしながら諸々を全部作るのは今のサービスのフェーズ的には時間的にもコスト的にも現実的ではないなと感じました。

そこで以前から興味のあったFirestoreを採用することにしました。 このためFirestoreのDBに動画データを登録し、登録されたデータを確認するための管理画面が必要になったため作成したという流れです。

f:id:keeetaka:20181107182301p:plain

管理画面に関しては Vue.js + Firebase の構成で作ったのですが、なぜ Vue.jsとFirebaseを採用したのかに関してはこちらのFirebase Advent Calendar*1で紹介する予定です!

話は戻り、Firestoreでは単純なクエリ・ソートは可能なものの、複雑なものはできないというのをよく見かけたため、まずデータ構造の持ち方を決め、実際の運用の際にどのようなクエリやソートを行うかを検討し実際にテストしました。

ところがtimeStampでデータをソートしようとしたところデータが取得できないという問題に遭遇しました。

f:id:keeetaka:20181107164211p:plain (一瞬絶望する様子)

2つ以上の別々のフィールドを組み合わせてソートする場合は予めインデックスを作成しておかないと機能しません。 結果的に問題なくプロダクトに使う事ができたのですが、自分たちのサービスでしたい要件をFirestoreが満たすことができるかは投入前に確認するべきだなと感じました。

実装例

動画を日付順に並び替えて10件ずつ取得するケースだと以下のようになります。

        let db = Firestore.firestore()
        var query: Query!
        if dataSource.isEmpty {
            query = db.collection("videos")
                .order(by: "postedDate", descending: true)
                .limit(to: 10)
        } else {
            query = db.collection("videos")
                .order(by: "postedDate", descending: true)
                .start(afterDocument: lastDocumentSnapshot)
                .limit(to: 10)
        }
        isLoading = true
        query.getDocuments { (snapshot, error) in
            if let err = error {
                self.isLoading = false
                completion(err)
            } else {
                guard let snapshot = snapshot else { return }
                snapshot.documents.forEach { doc in
                    let data = doc.data()
                    let jsonData = JSON(data)
                    let video = Video(dataObject: jsonData)
                    self.dataSource.append(video)
                }
                if let lastDocument = snapshot.documents.last {
                    self.lastDocumentSnapshot = lastDocument
                    self.hasMore = true
                } else {
                    self.hasMore = false
                }
                self.isLoading = false
                completion(nil)
            }
        }

limitafterDocument を合わせて使うことで一般的なAPIクライアントで必須のページングの機能を導入することができます。 これによって読み込みの数を制限することで、ロードを早くし、余計なアクセスを減らすことができます。

Storage

動画サムネイルの管理に Cloud Storage を採用しています。 Cloud Storage 用の Firebase SDK があるので、簡単に扱うことができます。

最初動画の登録のためAPI経由でアップロードしていたのですが250KB以上の画像がAPI経由でアップロードに失敗すると言う問題に遭遇しました。 現状サムネサイズはそれを越えるものがないのですが、ここに関しては調査しようと思います。

また実装して気付いたのですが、Cloud Storageからダウンロードリンクを経由してサムネイルをアプリ内で用いると、アプリ内キャッシュがない場合の初期表示が露骨に遅いです。

改善策

初めは新しく用意したサムネイルのサイズが原因かなと思いサイズの削減なども行ったのですが、明らかにそこまでのサイズではないため勿論結果は変わりませんでした。 そこで画像読み込み部分を改善しようと思い、今までは KingFisher をアプリ内で使っていたのですが、これを機に少しでも速度を早めようと表示速度が速いと評判の Nuke を導入してみました。

ですが、体感ではあまり分からないレベルです。

そもそもの問題はGoogle Cloud Storage側にあるのではないかと思いコンソールで設定など色々調べていたところ、バケットのロケーションがUSになっていました。

f:id:keeetaka:20181107183119p:plain

どうやらデフォルトでFirebase側のプロジェクトの設定と紐づくようで、GCP側でバケットを手動で作らない限り自動的にUSになります。 試験的にアジアのバケットに画像データを用意したところスピードが劇的に向上しました。 途中でバケットのロケーションを変更することはできないので、既に本番稼働中のプロジェクトに関しては新しくバケットを作り既存のバケットからデータを転送する作業*2が必要となりますので注意してください。

Cookpadさんのエンジニアブログで先日公開された記事*3でも、Firebase Cloud Storageからの画像取得を改善した例が紹介されていました。 データの持ち方なども工夫されていましたが根本の原因はやはり「US」だったようです。

多くの方がFirebaseとCloud Storageを採用した時に一瞬詰まるポイントかもしれません。

Hosting

管理画面の公開にFirebase Hostingを利用しました。

独自のドメインを設定することもでき、当該ドメインの SSL 証明書が自動的にプロビジョニングされ安全にコンテンツを配信する事ができます。

今回の場合Vueを使っているので、npm run build でdistフォルダに公開用のビルドファイルが生成されます。 後は firebase deploy などといったコマンド一つで簡単にデプロイすることができます。

Functions

Cloud Functions for Firebase  |  Firebase

Cloud Storageにファイルをアップロードした時やFirestoreにデータが登録された時など、任意のタイミングでJSの関数を実行する事ができます。 例としては下記の用途が公式ページで紹介されています。

  • ユーザーにフォローされた際に(Firestoreにフォロワー情報が保存される)、Cloud Messagingでユーザーに通知を送る関数を実行する
  • コメントで不適切なコメントがあった際に、ワードをチェックする関数を実行し、コメントを削除するなど

f:id:keeetaka:20181107131835p:plain

今はFirebaseのSDKが進化しており単体でそれぞれの機能が使いやすいのでFunctionsを使わずともサービス運営も可能だとは思いますが、 Functionsを部分的に採用する事でより効率的なサービス運営が可能になると思います。

Cloud Storage を Cloud Functions で拡張する  |  Firebase

終わりに

Firestore以外は過去にも利用した事が何度かあったのと、プロダクトの構造に大きく影響を与えるものはないため懸念はなかったのですが、Firestoreに関しては正直不安でした。 既存サービスをFirestoreを使ったものにリプレイスするというのは特別な理由がない限り必要ないと思いますが、新規サービスなどバックエンドにあまり工数を割かずスピード重視で開発したいという場合には十分に期待に応えてくれるものだと思いました。

Firebaseのサービスは今回紹介した以外にもいくつかあります。 いずれも導入が容易で使いやすさが特徴なので是非一度試してみてください。

今後

Firestoreを採用したことでアプリで扱うデータを自由に持つことができたので、現在は動画配信部分のインフラを整えている最中です。 これでアプリ・管理画面・動画配信基盤といったベースが完成するので、後はAndroid版の開発をしたり、アプリの質を上げたり、色々チャレンジ出来る事が増えそうです。

会社のStyleとして 新たな挑戦を恐れてはならない というものがありエンジニアの技術的なチャレンジを会社として全面的にサポートしてくれます。
少しでも話を聞いて見たいと思った方は是非Wantedlyで会いに来てください!

www.wantedly.com

今回の記事が少しでも多くの方の参考になれば幸いです。
ここまで読んで頂いてありがとうございました。

参考

以下の記事はプロダクトにFirebaseを導入するにあたって非常に参考になりました。

Firebaseを活用したiOSアプリ開発事例 - クックパッド開発者ブログ
Firebaseでバックエンドエンジニア不在のアプリ開発 クックパッドが体感した、メリットと課題 - エンジニアHub|若手Webエンジニアのキャリアを考える!
Cloud Firestoreの勘所 パート1 — 概要 – google-cloud-jp – Medium

iOSアプリにSiri Shortcutsを実装して露出アップを狙ってみた #GameWith #TechWith

GameWithのiOSアプリエンジニアの chuymaster です!会社ではチャイと呼ばれています。インドのミルクティーのアレです。

今回はGameWithアプリに、Siri Shortcutsっていう機能を実装したので、その裏話を話したいと思います!

Siri Shortcutとは

Siri ShortcutsはiOS 12からできた機能で、Siriを通して音声でいろいろなアクションをさせることができる機能です。

f:id:gwchai:20181102110231p:plain

実装がとても簡単で、Siriだけではなく、Spotlight検索からもアクションを出現させることができて、アプリの露出を増やすのに役に立つ機能です。

また、頻繁にアクションが行われると、検索するまでもなく、Spotlightを出したときやロックスクリーンにショートカットが出現するので、アプリの起動回数アップも狙えます。

Siri Shortcutsの最小実装方法

Siri Shortcuts用のこんなクラスを作りました。

class SiriShortcutsService {
    
    static func createActivity(activityType: String,
                               title: String,
                               keywords: Set<String> = [],
                               contentDescription: String = "",
                               suggestedInvocationPhrase: String = "") -> NSUserActivity {

        // NSUserActivityを作成して付帯情報を設定
        let activity = NSUserActivity(activityType: activityType)
        activity.title = title
        activity.keywords = keywords
        
        let searchItemAttributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
        searchItemAttributeSet.title = title
        searchItemAttributeSet.contentDescription = contentDescription
        activity.contentAttributeSet = searchItemAttributeSet
        
        // Spotlight検索とSiri Shortcutsを有効化
        activity.isEligibleForSearch = true
        if #available(iOS 12.0, *) {
            activity.isEligibleForPrediction = true
            activity.persistentIdentifier = activityType
            activity.suggestedInvocationPhrase = suggestedInvocationPhrase
        }
        return activity
    }
}

これで、Siriに登録するショートカットを作成できます。 ショートカットの付帯情報はコードから分かるように

  • title: タイトル
  • keywords: 検索キーワード
  • contentDescription: ショートカットの説明
  • suggestedInvocationPhrase: Siriで起動するフレーズの提案

が設定できます。

次にショートカットを ViewController で登録する必要があります。

    override func viewDidLoad() {
        super.viewDidLoad()
        
        userActivity = SiriShortcutsService.createActivity(activityType: "jp.co.gamewith.xxxxxx",
                                                           title: "モンストのマルチに参加",
                                                           keywords: ["モンスト", "マルチ", "モンスターストライク"],
                                                           contentDescription: "モンスターストライクのマルチ募集中のクエストに参加できます")
        userActivity?.becomeCurrent()
    }

ViewController が持っている userActivity プロパティにショートカットを設定し、 userActivity?.becomeCurrent() を呼ぶことでショートカットを登録します。これで登録完了です。

ここで登場した activityType パラメータは、ショートカットごとに一意の文字列を設定します。 Siri Shortcutsでアプリが起動された際のハンドリングするための認識記号として持ちます。

ハンドリング処理は、 AppDelegateapplication(_:continueUserActivity:restorationHandler:) でこのようにします。

func application(_ application: UIApplication,
                     continue userActivity: NSUserActivity,
                     restorationHandler: @escaping ([Any]?) -> Void) -> Bool {

        // activityTypeの文字列を取得
        switch userActivity.activityType {
        case "jp.co.gamewith.xxxxxx":
            
            // モンストのマルチ参加画面を開く
            Route.multiParticipate()
            return true
            
        default:
            return false
        }
    }

留意点

Siri Shortcutsはあくまでも、ユーザーがよく使っているアクションを登録して、簡単に呼び出せるようにする機能なので、 実際にそのアクションをしたときにショートカットを追加するのがAppleのガイドラインに沿って適切です。

Don’t make donations for actions that the user has not completed in your app; if the user never places an order for soup, you should never donate a shortcut for the order soup action.

Donating Shortcuts | Apple Developer Documentation

Siriへの登録方法

GameWithアプリのモンスト攻略にはマルチ掲示板機能があり、 そこでマルチ募集かマルチ参加をした方なら、Siri Shortcutsを使うことができます。

  • まずは設定を開くf:id:gwchai:20181102115743p:plain

  • Siriと検索を選ぶ f:id:gwchai:20181102115812p:plain

  • GameWithを選ぶ f:id:gwchai:20181102115827p:plain

  • ショートカットを選ぶ f:id:gwchai:20181102115846p:plain

  • 使えるショートカット一覧から、「モンストのマルチに参加」を選ぶ f:id:gwchai:20181102120028p:plain

  • Siriで起動するフレーズを登録する f:id:gwchai:20181102120118p:plain

  • 「マルチ参加」で登録したショートカットが表示される f:id:gwchai:20181102120134p:plain

  • Siriに「マルチ参加」と話しかける f:id:gwchai:20181102120208p:plain f:id:gwchai:20181102120215p:plain

  • GameWithアプリのモンストマルチ掲示板が起動する f:id:gwchai:20181102120241p:plain

  • Spotlightからもショートカットが出現するようになる f:id:gwchai:20181102120305p:plain

実装した結果

Siri Shortcutsをリリースして1週間経ったとき

ショートカットを経由してアプリを起動してくれたユーザーがなんと・・・

・・・

・・

40名弱いました!

Siri経由とSpotlight経由を含めた数字です。

アプリの利用者の数から考えると、とてつもなく少ないです。

あまり普及しないSiriを使う機能なので、利用者が少ないのは予想通りとはいえ、少し凹みました(笑)

しかし、GameWithが掲げるビジョン「世界のゲームインフラになる」を実現するには、 こうしたユーザー体験の改善を地道に積み上げることが必要であり、 今後も試行錯誤しながらサービスを作っていきたいと思います。

最後に

社内では、Siriのことをもっと勉強して、Intentsという機能でSiriと会話して何かできないかも検証しています。

Siri Shortcutsのように、新しい技術をエンジニアから積極的に提案して実装できるのも、GameWithの社風ならではです。

そんなGameWithは、ゲームが大好きで、新しい技術をどんどん使っていきたいという方を大募集中!

Wantedly でもよいので是非お気軽にお声がけください!

www.wantedly.com

builderscon tokyo 2018 に参加してきました! #GameWith #TechWith

GameWithのエンジニアの @syque と tiwu です!

少し時間が経ってしまいましたが、今回はbuilderscon tokyo 2018に二人で参加してきたので簡単ですがレポートを書いていきたいと思います!

builderscon.io

GameWith は今回、builderscon tokyo 2018にスポンサーとして協賛させていただきました。

GameWithはバッテリーをノベルティとして提供させていただきました。

f:id:tiwu_gamewith:20181019144151j:plain

9/7 金

@syque タイムテーブル

Electronによるアプリケーション開発事情2018
Building and operating a service mesh at mid-size company
The state of the art of architecting Kubernetes-based infrastructure -Towards Maximum Security and Usability-
Istio: Weaving a Secure Service Mesh
Understanding Microservices with Distributed Tracing
lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話

9/7のセッションで特に面白かったのが、「 lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話」だったのでピックアップしたいと思います。

lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話

https://www.youtube.com/watch?v=vWmqBp3uRsw

発表内容は以下の通りでした。

  1. lldの紹介
  2. 開発を始めた経緯
  3. 速くてシンプルなコードを書くためのポイント
    1. 良いデータ構造にする。データが先、コードが後
    2. 2回書く 1度目の経験を2回目に活かす
    3. 最適化する箇所を最小にとどめる。大半のコードは読みやすさを重視する
    4. プロジェクトのオーナーが責任を持ってコードをキレイに保つ
  4. 具体的な高速化のテクニック

発表内容については、自分はリンカを一度も作ったことのない畑違いなエンジニアであるにも関わらず非常に感銘を受けました。基本的にはリンカについての説明やlldのプロダクトについて成り立ちや具体的なテクニックについてを発表されていたのですが、それぞれのフェーズでのアプローチがとても適切で、正にエンジニアらしい問題を適切に捉えた仕事の仕方であるなと感じたためです。 例として以下のようなことを話されていました。

  • リンカのプロジェクトに携わったのはいいけれど、資料もコードもなくて手かがりがほとんどなかった
  • オブジェクトファイルを結合して空の実行プログラムを作成するところまでを手探りでやった(これだけで3ヶ月)
    • Hello World がリンクできるまでに数ヶ月、大きなプログラムがリンクできるまでは1年かかった
  • クロスプラットフォームなリンカ(Windows, Unix, ...)を作ろうとすると抽象化のためのコードが非常に多くなる
    • でも別々のリンカの機能が使われることはあり得ないので、これは無意味だと思ったので作り直しを決めた
    • メーリングリストでは喧々囂々になったが、結果としてコードがすっきりし、動作も10倍速くなった

内容について1つ1つは当たり前のことだけれど、規模の大きいプロジェクトにも関わらず自分の姿勢を崩さずに問題に対して適切な手法、アプローチで解決を図ろうとしていた植山さん自身の人となりが発表によく現れていて、同じエンジニアとして強く共感しました。 この発表でファンになってしまったので懇親会でも思わず話しかけに行ってしまいました(お邪魔してしまいすみませんでした...!)

tiwu タイムテーブル

Envoy internals deep dive
ランチセッション E
開発現場で役立たせるための設計原則とパターン
Webサービスにて200週連続で新機能をリリースする舞台裏 
実録!ある担当者がみた「謎ガジェット」開発1年史 
ブロックチェーン(DApp)で作る世界を変える分散型ゲームの世界

9/7のセッションで特に面白かったのが、「開発現場で役立たせるための設計原則とパターン」だったのでピックアップしたいと思います。

開発現場で役立たせるための設計原則とパターン

セッションの内容は、最高の設計というものは無く結局ケースバイケースという話をよく聞くけど、じゃあどうしたら問題に対して最適な設計を選べるのか?という話で、詳細を書くと

  • 設計とはごちゃごちゃとひとかたまりになった問題を分割し構造化するアクティビティ
  • だが、どうやって分割するか?テーブルごと?CRUDごと?
  • そこで、設計原則とデザインパターンが武器になる
  • デザインパターンは、手法のカタログ(選択肢が増える)
  • 設計原則は、良いかどうか判断する指針(選択肢を選べる)
  • こういった設計のほうが良いというのを、言語化できないと暗黙知になる
  • 単一責任原則,開放閉鎖原則,凝集度と結合度はとても重要な設計原則
  • 設計をしたらこの設計原則をもとにレビューしてよりよい設計にブラッシュアップしていく
  • (余談)FizzBuzzEnterPriseEditionという最強のFizzBuzzのリポジトリがある

といった内容でした。

エンジニアは全員それぞれ頭の中で良い設計を考えていて、それをコード化したりしていると思いますが 頭の中で考えているものなのでどうしても、レビューの際で上手く伝わらなかったりすることがあると思います。

しかし、こういった設計原則とデザインパターンという共通の言語で会話ができれば互いに良いコミュニケーションができ より、自信を持ってコード化もできるなと感じました。

9/8 土

@syque タイムテーブル

ファミコンエミュレータの創り方
高集積コンテナホスティングにおけるボトルネックとその解法
Lunch Session (VOYAGE GROUP)
遠いようで身近なサウンドエンジニアリング
Using Chrome Developer Tools to hack your way into concerts
Extending Kubernetes with Custom Resources and Operator Frameworks
LT

tiwu タイムテーブル

「Web とは何か?」 - あるいは「Web を Web たらしめるものは何か?」
証券トレーディング業務におけるExcel依存を脱却するプロジェクトで直面した技術的選択とプロジェクト運営の失敗
なぜエンジニアはパフォーマンス計測しないのか
Webアプリケーションエンジニアが知るべきDNSの基本
LT

9/8のセッションで特に面白かったのが、「「Web とは何か?」 - あるいは「Web を Web たらしめるものは何か?」」だったのでピックアップしたいと思います。

「Web とは何か?」 - あるいは「Web を Web たらしめるものは何か?」

セッションの内容は、webの歴史とこれから未来についての話でした。

まとめると

Webが生まれた当初はドキュメントを共有するプラットフォームだったが、最近はアプリのプラットフォームになっている。
Gmailとかがまさにそうで、Windows上で動くアプリからWebで動くアプリに変革していった(スプレッドシートも同じ流れ)
では、次はどこに向かうのか?
それは、「OS」である。
PCのローカルファイルを操作したり、USBにアクセスしたりできるAPIが現在開発中である。
ただ、一番の問題はPermissionで、実はセキュリティとか無視して開発を進めているらしい。
今丁度ターニングポイントで、このまま進めばWebはOSとなる未来がやってくる・・・?

といった内容でした。

PWA、AMPでワクワクしていた自分ですが、それを超える何かを感じました(個人的に)。

今後のWebの動向は要チェックです!

最後に

GameWithではエンジニアを積極的に募集しています。ゲームが好きな方大歓迎です!

(現在、関東圏にお住まいでない方もお気軽に!)

ご興味のある方は、Wantedly でもよいので是非お気軽にお声がけください。

www.wantedly.com

古民家を貸し切ってWebチーム合宿をしてきた話

はじめまして、GameWithのWeb面のディレクターをしているやまぐちです。

だいぶ間が空いてしまいましたが、8/30にWebチームのメンバー8名で合宿を行いました。 第一回なのでゆるい感じですが、その様子をお送りします。

続きを読む

ゼロベースからPHP 7.2 + Elasticsearch 6.2 を使って検索機能をリリースするまで

GameWith のサーバサイド兼フロントエンドエンジニアの めもりー (@m3m0r7) です。

8月のブログリレーに参加したかったのですが、表題の検索機能のリリースに追われてて参加できませんでした 😥

開発に約1.5ヶ月ほど要して、大きな一つの機能である「検索機能」をまだアプリのみですが、先日、先行リリースしました 👏

この開発はサーバサイドが主だったため、フロントエンドが恋しくなってしまいました。React触りたい…。

f:id:m3m0r7:20180919172644j:plain

開発メンバー

開発メンバーは弊社の開発部部長が主にコードレビュー担当、インフラの細かいチューニングを行ったのがインフラエンジニアです。

私自身は主に検索機能のAPI周りの設計、アプリケーションの開発、大まかなインフラ設計・構築などを行っていました。

開発の技術スタック

GameWith としては新しい技術を使用した試みをいくつか行いました。

  • Dockerを使用してみた。
  • Laravel 5.6 を使用してみた。
  • PHP 7.2 を使用してみた。
  • Elasticsearch 6.2Sudachi のシステムフルディクショナリSudachiのユーザディクショナリ を使用してみた。
  • Amazon SQS を使用してみた。
  • PSR-2を厳守するため、 PHP_CodeSniffer を使用してみた。
  • 負荷テストのためGatlingを使用してみた。

パッと思いつくだけでも7個くらいの新しい挑戦をしました。GameWithとしてのプロダクションでは全てが初の試みなのでリリース大丈夫かなぁという漠然とした不安感があったのですが、いざリリースしてみると、動いてる!すごい!ってなりました(笑)

それぞれの技術スタックの選定ですが、弊社の本体のシステムは5年前から動いていることもあり、ところどころレガシーなのですが、 新しく創るものは、モダンにしたいよねということで選定していきました。ゼロベースだからこそできることですね (笑)

※ Laravelに至っては先日 5.7がリリースされて、 PHPは7.3がリリースされたので、近々アップグレードしていこうと思います。

PHPからElasticsearchとの通信はElasticの公式でSDKが配布されていますが、整形処理だとか、細かいことをしていきたい話になっていたため、GuzzleHttpを使って自前で用意しました。

ちなみに、この1.5ヶ月のコントリビューション数です。

f:id:m3m0r7:20180920134414p:plain

大まかな構成

大まかですが、現状は下図のような構成になっています。

f:id:m3m0r7:20180919182403p:plain

クライアントとの通信は Laravelが設置してある Application 側で行い、Application 側Elasticsearch Master Node と通信をして、受け取ったレスポンスを整形してクライアントへ返します。 設計図のRDSはGameWith本体サイトから記事情報を取得するために、本体のRDS利用しています。 またSQSは、攻略部の方が更新した記事・追加した記事の処理を積んでいくために用意しています。

ちなみに弊社では、Elastic-HQというElasticsearchの状況をブラウザで確認できるものがあるので利用しています。

f:id:m3m0r7:20180920134828j:plain

ページネーションでいうと、弊社の場合リアルタイムで記事が追加される場合があり、通常のナンバリングでのページネーションをすることができないため、 ElasticsearchのScroll機能を利用しています。

www.elastic.co

ちなみに、アプリケーションサーバー側から下記がレスポンスとして返ってきます。

f:id:m3m0r7:20180919183738j:plain

弊社の攻略記事は結構な数があるのですが、Elasticsearchを使用することにより瞬時に検索することができます。

リリース

テスト環境ではミニマムであったため、リリース前はプロダクション向けのインフラの構成などしていました。また実際にGameWith上の記事データ全てをElasticsearchに投入してみて、問題なく検索できるのかのチェックも行ったり。

また、実際にアクセス負荷に耐えられるのかを負荷テストを行ったところ、攻略記事が大量に追加されながらでも、アクセス数が多くても全然問題ないよねという結果となり、無事リリースしました 👏🎉 ご興味がある方はぜひアプリを触ってみてください ✌

play.google.com

改善点

今回の検索のスコア算定には時事の記事だとか、アクセス数が多いだとかの指標を入れていないので、検索精度向上のために入れていきたいなとやんわり考えてます。

また、キーワードのサジェストも出るといいよねという話があったりもするので、それをどう形にしていくかとか。

この検索機能はいずれGameWith本体でもリリースする予定ですので、お楽しみにしていてください 🎉 (あくまで予定です)

最後に

いかがでしたでしょうか。検索機能をもっとよくしたいなと考えています。私自身大学時代に自然言語処理(NLP)を主とした研究室にいたことがあるので、今回の実装は個人的に中々楽しかったなと思っています。 フロントエンドを触れていなかったので今は反動でフロントエンドがメインのタスクをやりつつ、検索機能の開発に携わっています(笑)

GameWithはサーバーサイドエンジニアとして一緒に働いていただける仲間を探しています!弊社のサイトの運営がやりたい!とか、NLPに興味があって検索機能の開発に携わってみたい方、ご興味があればぜひご連絡いただけると嬉しいです 👍 Wantedlyのリンクですが恥ずかしい方は、ツイッターで私に直接お声がけくださるのでも大丈夫です!

www.wantedly.com

CircleCI 2.0 への移行の軌跡 - Sunsetting 1.0 -

ご無沙汰しております。GameWith でエンジニアマネージャーをしている @serima です。

8 月は弊社エンジニアが代わる代わる技術ブログを執筆してくれたので、出番が少なかったです。(とても嬉しい…)

まだ、読んでない方は是非こちらの記事もご覧ください。

CircleCI のレクイエム

さて、いよいよ 2018 年 8 月 31 日を迎えましたが、本日は何の日か覚えてますでしょうか?

はい、そうです。CircleCI 1.0 の終了日です。

f:id:serimaryo:20180831121832p:plain

circleci.com

こちらのサイトでは、CircleCI 1.0 のビルド終了までの時刻がカウントダウンされています...。

ちなみに GameWith には約 50 個のプライベートリポジトリが存在するのですが、プロジェクトで利用している CircleCI は先週ですべて CircleCI 2.0 への移行が完了しました。

もちろん全プロジェクトで CircleCI を使っているわけではないので、移行作業自体はそこまで大変ではありませんでした。

続きを読む

GameWithアプリ と GameWithアプリチームについてのご紹介

はじめまして!

GameWithアプリのiOS版の開発を担当をしている @peka3 と申します。

blogは初投稿となります。

最近は、ドラクエ10で夜な夜な聖守護者の闘戦記に通い続けています。 聖守護者の闘戦記ではバトル面で今までの常識を覆すパラダイムシフトが起きました。 ああいうのにすぐに対応出来る人は、エンジニアに向いているのではないかなぁ、と思ったりしてます。

本題に移ります!

最近は部内で情報をオープンに伝える動きがあるのですが、GameWithアプリに関しては全然オープンにできていませんでした。 今回はGameWithアプリが、どういう環境で、どういうプロセスで開発されているのかをお伝えできればと思います。

iOSアプリエンジニアなので、iOS寄りの観点になってしまいますが、ご了承ください 🙇

GameWithアプリ について

f:id:peka3:20180829120038p:plain:w200

GameWithをより便利に使えるアプリとなっています。

メインとなる機能としては

  • 攻略情報や新作ゲーム情報を閲覧するためのブラウザ
  • 攻略情報、イベント情報のpush通知
  • SNS機能

があります。

詳しくはぜひ AppStore からインストールして使っていただきたいです!

GameWithアプリのミッション

GameWithアプリは、ゲームユーザーの第一想起を取る、ということをミッションに掲げてやっています。

この第一想起を取っている状態がどんな状態なのか、というのはチーム内で議論の末、

攻略情報を見るのもゲームを探すのも、真っ先にGameWithアプリを使ってくれる状態

という共通認識でやっています。

GameWithアプリ チーム構成

2018年8月23日現在のチーム構成ですが、

  • PO兼デザイナー: 1名
  • iOSアプリエンジニア: 2名
  • Androidアプリエンジニア: 1名
  • サーバサイドエンジニア: 1名

となっています。

またiOS、Androidそれぞれ1名ずつ業務委託でお手伝いしていただいています。

施策によってはWeb面への影響もあったりで、関係者に話を通しに行くことはありますが、基本的には施策を生み出すところからリリースまで、全てをこのチームで行っています。

開発プロセス

軽めのスクラム開発を行っています。

リファインメント等のセレモニーはセオリー通りですが、朝会や作業時間の記録などはしていません。 今のコミュニケーションの取り具合からすれば、特に時間を決めて朝会をしたり、細かく作業ログをつける必要はないだろうという判断からです。

POが描いたKPI達成のための大きめのロードマップを、チーム内で揉んで具体的な施策として形にしてプロダクトバックログに落とし込み、スプリントごとにプロダクトバックログの優先順に計画をして開発しています。

カンバン等のツールにはJIRAを使っています。

f:id:peka3:20180829124307p:plain

毎週金曜日にスプリントレビューとKPTを行ってます。まだまだスピード感を出してどんどん新しい施策をやっていきたいので、どこかがネックになっていないかなどを洗い出して、トライに繋げていきます。

今は各エンジニアの技術領域がはっきりと分かれてしまっていて、APIの進捗がクライアントに影響するような状態です。

今後は学習コストを適切に払いつつ、複数領域触れるエンジニアを増やしていきたいと思っています。

施策の生み出し方

ロードマップはあくまでざっくり案なので「○○機能強化」などざっくりしたものも多いです。

そういったものはチーム内でブレストを行って案出しをし、それぞれ効果と工数のマトリックスにマッピングして、やることを決めます。

決まったものはプロダクトバックログに追加されていきます。

エンジニアの思いつきからでも、実際に形になり、アプリに反映されることが多くあります。

働き方

エンジニアは裁量労働制なので、勤務時間等は基本的に個人の裁量に任せられています。

自分の場合は大体1日8時間勤務で見てます。周囲もそのようにしている人が多いようです。

またリモートワークの導入も進めていて、iOSアプリエンジニアは週2回までリモートワーク可能となっています。

iOSアプリのアーキテクチャ

レイヤードなMVVMで行っており、各層はRxSwiftのObservableで連携しています。

大雑把に書くと、

View -> ViewModel -> Repository/UseCase -> Infrastructure

という構成になっています

RepositoryとUseCaseを同一レベルの層にする(ViewModelがどちらにも依存しうる)のは議論があったのですが、現状のアプリの機能であればここを切り離して別の層にしても、メリットよりも一層間にかます作業工数のほうが取られるだろう、という考えで同一層で取り扱うことになりました。

また、この大まかなアーキテクチャの方針は iOS / Android とも共通にしています。

どの層にどういった実装が必要か?といった会話をする際に、共通認識を得やすくするのが目的です。

開発環境

PCは選択制なのですが、自分はMacbook Pro13インチがいいと入社時に伝えたら、なぜか iMac Pro と Macbook Proが支給されました。

Xcode は重いので iMac Proでの作業が最高に捗っています!

終わりに

ざっと広く浅く、GameWithアプリがどういう風に作られているのかを書いてみました。

個人的にはチームの心理的安全性が担保できてれば、大体のことはうまく回るんじゃないかなぁと思っています。(それが難しいのですが)

事業的な成長にあわせて人員も増えているため、最近は開発部内で「エンジニアリング組織論への招待 ~不確実性に向き合う思考と組織のリファクタリング」という本をテーマにした社内勉強会も行うなど、皆で組織やチームをより良くしていこうとしています。

gihyo.jp

もしGameWithアプリチームに興味を持ってくださった方、もっと詳しい話を聞いてみたい方がいましたら、下記からご連絡いただけるとありがたいです🙇

www.wantedly.com