#attefes atteの開発の裏側を語る atte FeS【Go・Swift開発編】に参加してきたまとめ
メルカリのグループ会社である、ソウゾウからリリースされた地域コミュニティアプリ「メルカリ アッテ」のリリースまでの裏側を語る「atte FeS」のGo・Swift開発編に参加してきたので、そのまとめ。
atte開発の技術 Golang と Google Cloud Platform
鶴岡 達也様
- GoとGAEは非常に有力なWebアプリ開発手段
- PaaSの時代が本格的に始まってきた
- herokuなどあったが、GAEは突き抜けて優秀なのではないか
アプリケーションの要件
- 機能的な側面
- 作ろうとしているアプリを洗い出した
- JSON API、静的コンテンツ配信, 動的コンテンツ生成、DB、キュー処理
- キーだけの検索ではなく、全文検索、位置情報、予測変換などの検索
- 画像をアップロードして配信、メール配信、Push通知、データ分析
- 非機能的な側面
- グローバル:複数リージョンでDBはグローバルに一つだけにしたサービスを展開したい
- 大規模アプリケーション:DAUで数千万人規模を扱えるアーキテクチャを考えたい
- ハイスケーラブル:立ち上げ期間から最後まで一貫したアーキテクチャにしたい
- メルカリを作った時はphp/linux/mysqlで作ったが、ハイエンドサーバ1台に全てをつめ込むような作りだった→その後、1〜2ヶ月でサーバを増やすことになった
- 現在はSREというチームがベストなものに直していっている
メルカリではなくソウゾウ
- わざわざ会社をわけるようにしたので、メルカリの縛りを受ける必要がなくなった
- あえて同じ技術を使わない選択をした
- メルカリ初期:保守的に堅牢に考えて作られた
- ソウゾウ:メルカリだけでは生み出せない新しい価値を生み出し、3年後にもエンジニアにとって魅力的な場所であるために多様性と技術の開拓をテーマに作っている
Googleの動き
- メルカリと同じAWSを考えていたが、去年の10月くらいにGCPのコンテナがいいというお話を聞いて担当者を紹介してもらって1ヶ月位検証してみた
- きめ細かくサポートしてくれる体制を作ってくれていると感じた
- ちゃんと人間がいてサポートしてくれる
- GCPのコンソールを触っていると表に出てくるアップデート情報だけからはわからないアップデートが見れる
- 微妙なUIの変化が2日に1回位の頻度である。
- 最初は英語メッセージだったのが日本語メッセージになっていたりする
プロダクト開発へフォーカス
- プロダクトの開発に集中できるのが理想という観点でフォーカス
Go
- パフォーマンスが高い
- Webの生産性は謎だったが、ミドルウェアの生産性は高いのでおそらくWebでも高い生産性を出せる
- 長い目で見ると生産性は高く感じる
- 型やコンパイルの影響で生産性は高いと思われる
- 途中で構造を変えたとした場合でも、変えた後にコンパイルエラーが起こるが、ひとつひとつ直すとコンパイルが通ればちゃんと動いてくれる
- チーム開発がしやすい
- フォーマットが言語の一部になっている
- パッケージの順番もA~Zの並びになっていったりする
- 変数名で揉めることも少ない
- goでコードを書いてgo documentを生成する
- masterからpullして常に最新のドキュメントをみれるようにしている
- APIを使う人はGo Documentをみればいいようにしている
- iOSエンジニアもAndroidエンジニアもGoDocumentをみてくれるのでAPI Documentを書かずに済む
- フォーマットが言語の一部になっている
GAE
- 1つのGCPにひとつのAppEngineがはいる
- データベースのDatastoreはプロジェクトに紐づく
- モジュールとバージョン
-
- 複数の環境にアプリをデプロイできる(60個)ので、Blue/Greenデプロイが簡単にできる
- バージョンに対しては生成されるURLをたたけばアクセスできる
- blueを選んで「全トラフィックのルーティング」をするとgreenからblueにアクセスを切り替えることができる
- インスタンスを暖める必要がない
- インスタンスのオートスケール
- リアルタイムのログ機能
- tailできないが、GUI上で確認ができる
- ログの検索もできるが、設定するだけでBigQueryにストーリーミングされるのでそのままBIチームなどに調査を頼める
- トレース
- Googleが勝手にサンプリングしてトレースしてくれる
- 1リクエストの中でどどんなリクエストが流れてるかが見えて、どれくらい処理時間かかっているのかが見える
分散システムのためのDatastore
- RDBでのMaster/Slaveモデル
- 実績十分
- 0から立ち上げてスケールさせるのは高度な知識が必要
- 大規模なデータになるとRDB本来の持ち味は活かせなくなる
- データを分割するとJOINが限定的になったりなど
- ユーザの上限がわかっている場合はこれで問題ない
- 日本だけなら5000万人を上限にして設計するなど
- GCPではCloudSQLが提供されているがオススメはしない
- NoSQLモデル
- GCPではDatastore
- 実用的なACIDはできないが、極めて高いスケーラビリティがある
- まちがいなくDatastoreを選択すべき
- NoSQLはふわっとしていて扱うのは難しいが、GmailやYoutubeのようなサービスのデータはRDBではなくBigTableに保存されているくらいに、NoSQLはGoogleが実証済み
- グローバルで大規模ならNoSQL!
Datastoreの特徴
- BigTableの上に構築されている。BigTable自体は単純なKVS
- メリット
- スケーラビリティが高い
- データがどれだけふえても取得と追加の速度は劣化しない
- 同一エンティティでなければ制限なしに並列書き込みできる
- ダウンタイムなしの高い可用性
- デメリット
- かなりプリミティブ
- 限定された条件でしかACIDトランザクションがサポートされない
- JOINや集約関数がない
- 使う時に考える事
- トランザクションなしで複数の値を一貫性を持たせて更新
- Datastoreの機能だけでオートインクリメントはできない
- 分散しているのでMySQLのような仕組みでやろうとしていると、そこが単一のアクセス集中になるので、分散に対応したインクリメントにしないといけない
- TwitterのSnowflakeを参照
Webアプリケーションのアーキテクチャ
- 複数個のモジュールで構成される
- モジュールはそのままデプロイ単位になる
- 多くのコードは共通なので単一のリポジトリで管理している
- APIはJSONRPC2.0
- アプリケーション層、ドメイン層、UI層、インフラ層の4層にわけている
- GAEへ強依存されるが、GAEの機能をフル活用することでスケーラビリティや可用性の恩恵を受けれる
- Goの考え方に従う
- 細かいことも決まり通りに書くと楽
- 1.4系なのでパッケージ管理はベストな方法はない
- 全部リポジトリにつっこんで管理しているのでそれにならった
- 重厚なフレームワークはいれない
- x/net/contextとnet/httpパッケージさえあればできる
- 1つのリポジトリにすべてをいれる(monorepo)
- APIをいじったらコンパイラがWebでもエラーを教えてくれたりしてくれる
FAQ
- Googleへの依存によるリスクはどう考えているか?
- Googleがダメならうちもダメだなと考えている
- 避けようとするとGoogleよりもうまくやらないといけないのでそのほうがリスクが高いのではないか
- GoogleとAmazonが競合している間はどちらかを使っていけばいい
- ダウンタイムや復旧の速さは?
- ダウンしたことがない
- エスカレーションのルールが決まっていて、なんでダウンしたのかはすぐに報告される仕組みは整っている
- 言語の仕様がコンパクトか?複雑か?学習コストは?
- Atteを作るにあたって関わったエンジニアは全員Goははじめて触った
- Go未経験者でも立ち上げができるという経験を作るために、意図的に助けを借りずにやった
- 助けを借りないとできないのなら、Goが世の中に広まっていかないのではないかと思った
- アプリを習得するまではすんなり習得できると思われる
- チーム開発のしやすさで、どういう分担でやっているか
- Atteのエンジニアは10人で、内5人はiOS
- サーバサイドはGoDocumentをベースにしてサーバサイドを考える
- Documentを明示的に作らなくてもiOSエンジニアの人たちはあっさり読める模様
- サーバサイドのエンジニア数は?
- 今は5人だが、リリース直後は1.5人
- microserviceな開発も検討されたとおもうが、リソース観点以外でのmonorepoの選択理由を教えて
- メルカリでリポジトリをわけることでいろいろ影響があったことを見てきた
- FBエンジニアと話した時、リポジトリは1個しかないといっていた
- gitだとおそいので改造してやっているくらい1つのリポジトリでやっている
- GoogleとFBの真似をしてみた
Swift と RxSwift
大庭 慎一郎様
- アッテはswift2.2で作成
なぜiOS8以降か
- 8と7ではSDKが大きく違う
- 実装スピード優先
- Cocoapodsは嫌いなのでCarthageを使いたかった
なぜswiftか
- 長らくメンテナンスするコードになる
- 型安全でOptional,enumなど使える
- APIKitはJSON-RPC2.0と相性が悪かったので、不採用。しかし後ほどAPIKit開発者が開発に合流してくれることに。
JSONRPCKit
- レスポンスの型を指定できる
- 型安全で使える
なぜリアクティブプログラミング
- もともとReactiveCocoaをヘビーに使っていた
- ストリームにそって宣言的にかける
- イベントや文字列の入力、ネットワーク通信などなんでもストリームにできる
- ストリームの加工をオペレータを用いて加工する
- 実例:テキストフィールドの変更をラベルに自動反映させる
- UITextFieldの変更を宣言的に反映できる
- 実例:条件を満たすまでボタンを押せないようにする
- APIロード中やテキストが空の場合はdisableにしたりする
- 実例:インクリメンタルサーチ
- textfieldのストリームに対して、最後の入力から0.5秒立ったらテキストを取得し、前回のテキストと別だったら処理する
- 実例:画像アップロード後の投稿
- 画像のサイズによって帰って来る時間が違うが、combineLatest()オペレータを使って、最後に入ってきた時に入れた順番にひとまとめにできる
- 実例:一部の画像アップロード後の編集投稿
- photosという配列に対して、mediaIDがあれば同期的に返し、なければアップロードして非同期に返す。その後combineLatestオペレータで受け取り、処理する
Swiftにおけるリアクティブプログラミング
- メリット
- どの言語でもほぼ同じ仕様なので約5年分資産がある
- 他言語でもRxが活かせる
- デメリット
- 学習コストが高い
- 設計に大きく影響出る
- ライブラリも巨大
- 開発が止まったらどうしよう・・・?→開発止まっても自分で何とかするつもり
- 便利すぎてプログラミング能力が衰えそう
チームへの浸透
- ドキュメントやコードを読み込んでもらう
- 実装パターンをmerbleなどで共有する
- 良い実装には絵文字で表現
- Rxに触れる機械を増やしている
- チャンネル設置
- 勉強会への参加
- ズンドコキヨシを実装したり…
FAQ
- Rxを触ってみたいと思っているが、設計に影響をあたえると言っていたが、どこから触り始めればいいか?
- data-bindingのところでUIの変更を反映させたりするのが閉じることができる
- 複数の処理を待ってからやるのはプロセスの代わりにやったりするといい
- MVVMアーキテクチャはどういうふうに実現しているか?
- この後の発表でお話します
アッテiOSの設計と開発フローの変遷
石川 洋資様
- 後から入ってきた人がどうやって設計に影響を与えたかのお話
同じ構造の実装の一元化
- ページネーション、フォームのバリデーションなどは同じ実装を使うことが多い
- 同じ構造を担当するのはViewModel
- UIVCの表示ロジックをわけたのがMVVM
- UIイベントと表示値の間をRxのストリームでつないでいる
- UIViewControllerの入れ替えをすげ替えやすい構造になっている
- 例えばTimelineのViewControllerがあったらTimelineViewModelがAPIClientと通信してデータをやり取りしている
- それぞれのページごとにViewModelを作っていくと同じ構造のものが増えていく
- ViewModelの部分をまとめてPaginationViewModelというGenericでやるようにした
- 何をパラメーター化するか?
- 表示するエレメントとは別にリクエスト自体も型パラメータとしてとるようにしている
- リクエストには型制約をもたせている
- Genericsをうまく使う
- どう実装するか?
- プロトコルで定義したインターフェースだけで実装するようにする
- ページネーションの実装側にはページのパラメータを持っていて、レスポンス型には次のエレメントがあるかどうかのプロパティをもつといったのをプロトコルで定義する
- ページネーションの変わらないところだけをプロトコルでもち、画面専用のものだけ別途作るようにする
- 実装の一元化で得たものとして高速で安全な作りになった
- ページネーションなども、違うところだけ型パラメータとして渡してあげるようにするという作りにできる
キャッシュ戦略の変遷
- 当時はRealmでキャッシュを取っていた
- レスポンスはRealmのオブジェクトとして表現していた
- やっていくうちに何もかもRealmのObjectになってしまった
- 何が永続化するのか、何が永続化されないのかが型だけでは判別できなくなってきた
- Realmは複数の画面で一つのデータを扱うことになっているので、他の画面でデータを使っていると困る。削除タイミングの決め方がとても複雑になる
- 全部がRealmオブジェクトになるとマイグレーションが大変
- Realmが担っていたものは?→レスポンスの永続化と画面感の変更の共有
- 捨てていいものはパージ前提の機構で保存することにした
- 基本的に大事なデータはサーバで持っているはず
- JSONをLRUの機構で作るようにした
- 削除ポリシーの心配がなくなった
- 保存されたJSONと受信したのが違えば捨てればいいということでマイグレーションの問題もなくなった
- レスポンスを表す型の制限がなくなった
- ObjectMapperは初期化以降にJSONエラーが波及してしまう問題があった
- 現在はHimotokiに移行済み
- JSONのバリデーションと方のインスタンス化が一致するようになり、コントローラーにエラーが波及しなくなった
- グローバルなSubjectで共有
- 同期は同期が得意なRxSwiftにお任せした
- Realmは今でも使っています
- サーバーに届いているデータはLRUディスクキャッシュでやっているが、届いていないデータに関してはRealmで保存するようにしている
- サーバに届いていないデータは送信中のメッセージやコメント、入力中のフォームなど
デプロイ
- 各種モチベーション
- PRがマージされたら誰でも動作確認できるようにしたい
- QAが完了したらすぐに審査に出せるようにしたい
- 誰かの環境に依存せずにデプロイできるようにしたい
- アッテではTravisCIを使っている
- ポリシー
- git pushされたらアーカイブ+デプロイ
- ビルド番号はTracisCIがagvtoolを使ってふってあげる
- 手元でのアーカイブは基本的にはしない
- デプロイ環境はDeployGateとTestFlight
- gitブランチごとに分けている
- feature:テスト
- master: テスト+Development
- release: テスト+Development+Production
- ビルド時間
- xcode7.3はCI時間が半分になった
- リアルアーカイブは7min 15secくらいだが遅いのはアップルのサーバが遅いせい
- 自動化により
- チケットを切った人に対してこのビルドバージョン以上で確認してくださいとすぐ動作確認できるようになった
- ビルドしてくださいと言われなくなった
- releaseブランチさえきれば自動的に上がるので申請が楽で、誰でも申請できるようになった
まとめ
- 楽に速く開発できるように設計を考えている
- ミスが起こりやすい設計は避けている
- 誰でも気軽にデプロイできるようにしておく
FAQ
- 誰でもデプロイできるようにしているとのことだが、どのビルドにどの問題が解決されているかわかりづらくないか?
- ビルド番号をCIでふっているので、どのコミットがどれにはいっているかはdeploygateで見れるようになっている
- 追うとおもえば追える
- 複数のブランチで機能ごとにわけていたりしているか?
- ブランチの数は絞っていて、master, feature, releaseの3つしかない
- 確認して下さいというのはmasterなのであっちにあってこっちにないといった問題はおきらない
- 送信中のメッセージなどはrealmに保存するということだが、送信完了したら消しているのか?
- サーバに届いたら消していいというポリシーなので消すようにする予定
@mogmetの所感
GAE+Goの環境はAWSよりもより気軽にかつ楽にハイスケーラブルな環境を作りやすいと感じたので、次のプロダクトを作る際は是非試してみようと思えるような魅力的なものでした。
Rxや設計について色々と刺激を受けたとてもいい勉強会でした!