シンクロ・フード エンジニアブログ

飲食店.COMを運営する、株式会社シンクロ・フードの技術ブログです

iOSDC Japan 2018 に参加してきました

こんにちは、アプリチームの増山です。
8月30日(木) から 9月2日(日) にかけて開催された、iOSDC Japan 2018 というカンファレンスに参加してきました!
今回は参加したセッションの中でいくつか印象に残ったものをご紹介させていただきます。

f:id:hofzzy:20180904152246j:plain

Playground 駆動開発のすすめ

Playground で View をテストすることで、ビルド時間を短縮しデザイン調整のサイクルを高速に回そうという趣旨の内容でした。

Kickstarter でも提唱されている手法です。

Playground で View をテストするためには View を Embeded Framework 化する必要がありますが、既存プロジェクトに導入する場合は View の依存周りの解決が難しそうだなと感じました。
逆に新規で開発するプロジェクトに関して言えばこの Playground 駆動開発は検討する価値は十分にあるなと思いました。

MicroViewController で無限にスケールする iOS 開発

コンポーネント毎に ViewController を定義して画面を構成することで、ViewController のコードをスリム化し開発効率を向上させようという趣旨のセッションでした。

1 画面 1 ViewController という構成で開発をしていると、ViewController がすぐ肥大化したり、また複数人で開発している場合はコンフリクトやオーバーヘッドが発生します。
セッションで紹介されたこの方法は、これらの問題を根本から解決するような手法で非常に興味深く、また実際に会場からも「お〜」といった声が様々なところからあがっていました。

因みにこちらのセッションは今年のベストトーク賞に選出されていました。

差分計算アルゴリズムを用いた高速な UITableView 描画

Myer 法、Wu 法、Heckel 法といった差分計算アルゴリズムの紹介と、それらの計算アルゴリズムを使用した場合と reloadData で更新した場合とのパフォーマンスの比較について話されていました。

パフォーマンスの比較は全体の 10% を DELETE・INSERT するパターンと、30% を DELETE・INSERT するパターンとで比較を行っていました。
結果的には Heckel 法を使用した差分更新が一番パフォーマンスが高いという結果になっていましたが、Cell のレイアウトがシンプルな場合は reloadData した場合とであまり差はありませんでした。

実際弊社プロダクトでも UITableView の更新はほとんどを reloadData による更新を行っていますが、パフォーマンス上いまのところは問題は起きていません。
最初から差分計算を考えるのではなく、パフォーマンスに問題が起きたら差分計算を検討するくらいでいいのかなと思いました。

In-App Purchase 再考 -サーバーサイドエンジニアの運用経験と他決済手段との比較を添えて-

決済ゲートウェイの観点から In-App Purchase と他決済手段との比較を行い、その上で In-App Purchase を本当にプロダクトに導入すべきか?という内容に関するセッションでした。

In-App Purchase は他決済手段と比べてトランザクション周りの制御の複雑さやデバッグのしづらさなど、実装においてややハードルの高い印象がありますが、UX の観点から見ると決済完了までステップが簡潔なため、コンバージョンに直結しやすいというメリットがあります。
しかし、運用面やビジネス面の観点から In-App Purchase を考えたときには以下のデメリットが存在するということを話されていました。

  • 顧客から問い合わせに対して丁寧な対応ができない (iTunes Connect 上からユーザーの情報を照会することができないため)

  • 他決済手段と比べて手数料が高額である (In-App Purchase の場合は 30% の手数料を Apple へ支払う必要がある)

また、この他にも In-App Purchase という決済手段が iOS でしか利用できないという点も、マルチプラットフォームで運用しているサービスにとっては大きな足枷となるということも話されていました。

iOS で決済手段を導入するなら In-App Purchase と安易決めつけるのではなく、それぞれのメリット・デメリットを考えた上で検討をするのが良さそうです。

デバイス・OS バージョンの依存が少なく、メンテナンスしやすいビューを作る

なぜレイアウトが崩れるのかという問題に対する考察から、View を作る上での Good Practice・Bad Practice について話されていました。

レイアウトが崩れることとそれを未然に防ぐことが難しい理由として岸川さんは以下の三つを挙げていました。

  • アプリ内で管理する状態が多い

  • レイアウトが動的に変化する

  • 実行するまで分からない

しかしこれらの問題を解決するための銀の弾丸はなく、色々な方法を試しつつその中でその人にとっての Good Practice を選定していくのがベストだと仰っていました。
セッションでは岸川さんが実際のプロダクトで活用しているいくつかの Tips を紹介されていましたが、その中で IBDesinable / IBInspectable を活用した View の作り方が非常に参考になりました。
レイアウトをコードから定義することを極力減らし、また Storyboard のライブレンダリングを活用することでシミュレータの実行前にテストできる範囲を広げています。

また、これらの Tips を実践したサンプルアプリが以下のリポジトリに公開されています。

個人的にはアプリの View の作り方に関する正に教科書とも言えるような内容で一番印象に残ったセッションでした。

iOS アプリの開発速度を 70% 高速化したデバッグノウハウ

開発速度を向上させるための 5 つの Tips について紹介されていました。

個人的に印象に残ったのは LLDB を活用したデバッグ方法と動作確認の自動化でした。

前者については今までコードをデバッグするのにブレークポイントを貼り直してはリビルドを繰り返していたので、コールスタックを遡るという方法は目から鱗でした。

後者に関しては要するにテストを書くということと、そのためどういう設計をしていくべきかということでしたが、これについては弊社でも以前から色々試行錯誤しつつ取り組んでいた内容だっため非常に参考になりました。
発表では時間の都合上概要だけで詳細はほぼ割愛となっていましたが、公開されているスライド上には具体的にどのように導入していくのか、コードも交えて詳細に書かれていました。

また、サンプルアプリも以下のリポジトリに公開されており、テストをあまり書いたことのない人でも明日からテストを書きたくなると思えるようなセッションでした。

感想

どのセッションも内容の濃いものが多く、非常に勉強になりました。iOSDC 自体は初参加で勝手がわからず結構あたふたしていたのですが、ランチタイムやセッションとセッションの合間などに他の iOS エンジニアの方と情報交換を行ったりと、初参加ながらも 4 日間充実した時間を過ごせた気がします。来年も機会があれば是非参加したいと思います!


最後に現在開発しているプロダクトについて宣伝をさせて下さい。
現在弊社アプリチームでは、飲食店向け発注ツール PlaceOrders の iOS 版を開発しております。

f:id:hofzzy:20180904164727p:plain

iOS 版 PlaceOrders は今年の 7 月にリニューアルを行ったのですが、その際にデザインを刷新し Human Interface Guidelines に準拠することで直感的な UI を提供するようにしました。Human Interface Guidelines - Design - Apple Developer
また、iOS の独自の機能である State Restoration などに対応することで、途中で発注作業をストップしてしまったとしてもすぐに現状復帰できるような快適な操作性を追求しました。

今後も PlaceOrders は幅広く機能の追加を行っていきます。
皆様の周りで飲食店を経営されている方がいましたら是非 PlaceOrders を宣伝していただければと思います。

RubyKaigi 2018 に参加してきました (@n0_h0)

開発部サービスチームの日下(@n0_h0)です。

今回、初めて RubyKaigi に参加してきました!
この記事では、印象に残ったセッションを3つご紹介いたします。

弊社からは2人参加しており、もう1つのレポート記事は以下のリンクになります。

セッション紹介

bancor: Token economy made with Ruby

@kurotaky さんによる Bancor Protocol に関するセッションです。

暗号通貨等のトークンは、マイナーなものほど売りたい時に買手が見つからない流動性リスクという問題があります。
そこで、トークンの価格決定を自動化することで流動性リスクを解決しようとしているのが Bancor Protocol です。

より詳細な説明に関しては、スライドにも挙げられていました以下の記事に書かれています。

しかし、Bancor Protocol を実際にブロックチェーン上で試してみるにはインフラの問題や学習コストの高さ等のハードルがあります。
そこで、Bancor Protocol はただのプロトコルですので、シミュレーションを行える bancor gem を作成されており、デモも行われました。
実装を読んでみると、結構シンプルだったのが興味深かったです。

Hijacking Ruby Syntax in Ruby

@joker1007 さんと @tagomoris さんのお二人によるセッションです。

BindingTracePointRefinements 等を使用したメタプログラミングの力を紹介されていました。

特に、TracePoint と Binding を使用する事で任意のコード中の変数を上書きすることが出来るというのは強烈だと感じました。
また、メタプログラミングの力を行使するだけではなく、Refinements を使用することで安全に利用できる gem を作られていたのは本当に凄いと思います。

個人的には、1番 Ruby の大いなる力を感じることのできる発表で、とても面白かったです。

How to get the dark power from ISeq

発表資料

@youchan さんによる ISeq の可能性を探求するセッションです。

ISeq とはパーサー&コンパイラ、RubyVM の中間に位置するものです。
Ruby のコードはパーサーによって解釈されて ISeq にコンパイルされ、RubyVM によって実行されます。
ISeq は内部APIとして扱われており、ドキュメント化されていません。

しかし、ISeq の仕様をドキュメント化する事ができれば、他の言語で作成されたコードを ISeq バイナリ形式に変換することで、RubyVM で動かすことができるようになります。

実際、@youchan さんが ISeq Builder というものを作成されており、brainfxck コンパイラのデモが行われました。
有効に使うことで Ruby の方言を作れるのではないか等、とても面白い提案をされており聞いていてワクワクするセッションでした。

おわりに

Rubyist の熱気を肌で感じる3日間でした。
同時に、自分の力不足による危機感も感じ、まだまだ勉強していきたいです。


シンクロ・フードでは RubyKaigi は出張として扱っており 参加費、交通費、宿泊費の会社負担、土曜日の休日出勤扱い を行っております。
Rubyist を絶賛募集しておりますので、ご興味のある方は以下よりご連絡ください!

RubyKaigi 2018 に参加してきました (@fohte)

基盤チームの川井 (@fohte) です。

2018 年 5 月 31 日 (木) 〜 6 月 2 日 (土) に仙台で開催された RubyKaigi 2018 に業務として参加してきました。
弊社はスポンサー枠ではありませんが、出張として宿泊費や交通費を負担していただいて参加しました。

今回は、印象に残ったセッションと、個人的に興味があった型にまつわるセッションについてご紹介します。

セッション紹介

Day 1 Keynote「箴言 Proverbs」

Matz ことまつもとゆきひろさんのセッションです。 今回は「ことわざについて話す」ということで、プログラミングをことわざで例えた話が中心でした。

初めに紹介されていた「名は体を表す」は、つまり命名は重要であるということです。
物事を十分に理解すれば命名が容易になることがある、ということを話されていて、なるほどと感心しました。

また、Ruby 2.5 で導入された Kernel#yield_self は何がしたいのかを表現しておらず、悪い例であると話されていました。
(これは考え直しましょうというチケット #14594 があり、alias として then を追加する変更が Matz によって発表前日に commit されたそうです。5 年ぶりの commit らしい。)

質疑応答の際に Ruby の型に関する質問があり、型があることで助かるケースはあるが、Ruby の言語仕様として型宣言が入ることはないと名言されておりました。

A practical type system for Ruby at Stripe.

Stripe 社内部で用いられている Sorbet という Ruby の type checker についてのセッションでした。

Sorbet は記事執筆時点では公開されていませんが、Web 上で実際に試せるページが用意されています。

Sorbet は C++ 製なため非常に高速で、社内では好評らしいです。 また、近々 OSS として公開する予定であり、公開時には 公式ブログ で通知するとのことでした。

Ruby Programming with Type Checking

発表者の @soutaro さんが製作されている Steep という Ruby の type checker についてのセッションでした。

Steep は Sorbet とは異なるアプローチをとっており、Sorbet は Ruby コード上で型を宣言しますが、Steep は専用の構文を用いた型宣言ファイルを用意するかコメントで型注釈をします。
また Steep は Ruby で実装されているため Sorbet よりも低速ですが、Ruby 2.6 で導入される予定の MJIT で高速化することを期待していると話されていました。

既存のプロジェクトに導入する際などに便利な型定義ファイルの雛形を生成する steep scaffold コマンドも用意されています。

しかし、ライブラリの型宣言はまだ自分で書いていく必要があったり、Hash の構造の型宣言ができなかったり、動的に定義されるメソッドは型チェックを行えないなどの問題があるとのことでした。

Type Profiler: An analysis to guess type signatures

Ruby commiter の一人である @mametter さんによる Ruby 3 の型システムについてのセッションでした。

既存の型システムとしては Steep, RDL, contracts.ruby, dry-types, RubyTypeInference, Sorbet などがありますが、代表として RDL という半静的 type checker について紹介されていました。

RDL は型検証を行うタイミングを指定でき、静的型チェックを無視して動的型チェックのみを行うようにすることもできます。
また、メタプログラミングもサポートしており、動的に生えるメソッドの型宣言もできます。

Steep のように型宣言がファイルで分かれているほうが良いが、メタプログラミングは難しいということで、RubyTypeInference のように型情報を推定する Type Profiler という構想が生まれました。
この推定された型情報は厳密なものではなく、推定結果を人間がチェックすることを想定した設計になっています。

型情報をプロファイリングするには、void 型の推定が困難なことや、TracePoint API を用いて動的に解析しようとすると非常に遅くなるという問題があると話されていました。

感想

RubyKaigi は今回が初参加でしたが、レポート記事や実況ツイートではわからない会場の雰囲気を味わえて非常に良かったです。
特に、今回は型システムにまつわる発表が多く、個人的に今非常に興味があるので、Ruby 3 に向けた型システムの構想を聞いたり懇親会などの場で参加者と話をできたりして面白かったです。

ぜひ来年も参加したいと思っています。


シンクロ・フードでは Ruby に詳しいエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!

EC2上のMySQLからAuroraへの移行作業で失敗・想定外だったこと

こんにちは、pubgで全然ドン勝できない、開発部の大久保です。
今回は弊社が調査含め半年ほどかけて実施した、Aurora移行のお話しをしようかと思います。
ただ、Auroraへ移行するための情報自体は世の中に溢れていると思うので、今回は移行する際に失敗したことや想定外だったことをご紹介しようと思います。 具体的な失敗や想定外をお伝えすることで、移行を考えている方のお役に立てれば幸いです。

移行の概要

とはいえ、さすがに大まかにでもどのように移行をしたかをご紹介しないと、失敗も共有できないので、簡単に共有します。
今回弊社が行った移行は、EC2上にホストしているMySQLをAuroraに移行する、というものです。RDSからAuroraに移行する、というものではありません。

では、ものすごく簡素化した移行の図で、移行の全体像をお伝えします。

移行前

f:id:synchro-food:20180513205218p:plain

なにも行っていない状態はこんな感じでした。
バックアップ用のEC2インスタンスはバックアップや開発用のデータの元となるdumpデータを取るサーバです。当時はOregonリージョンに立てていました。これは別リージョンにもリアルタイムのバックアップをとっておく目的で存在しています。
このサーバ、今回の想定外の1つなので、敢えてご説明しています。

移行準備

f:id:synchro-food:20180513204804p:plain

移行予定のAuroraインスタンスを立て、そこをSlaveとしてレプリケーションをすることでデータを移行します。

移行後

f:id:synchro-food:20180513204844p:plain

移行当日に、AuroraをMasterに昇格させ、アプリケーション側のDBの向き先を一気にAuroraに変更しました。 その他、バックアップ用のサーバが、クロスリージョンリードレプリカになっています(バックアップはAuroraのSnapShotだけでなく、sqlのdumpデータも欲しいので…)。

実際はもう少し複雑な構成なのですが、シンプルにするとこのような流れです。
移行手順としてはオーソドックスだと思います。

失敗・想定外だったこと

では、上記手順でAurora移行をした際に発生した問題をご紹介します。大きくは3つでした。

Aurora→EC2上のMySQLへのレプリケーションはSSLにできない

上記図でいうバックアップサーバは、別リージョンのEC2インスタンス上にMySQLを立て、リアルタイムのバックアップを別リージョンに取るために使っていたのですが、料金を考えると、Aurora移行後も信頼性の低いEC2インスタンス上のMySQLでも十分でした。ですので、当初の予定では移行後も安いEC2インスタンスを使う予定でした。
しかし、調査しているとAurora→外側のMySQLサーバという向きではレプリケーションがSSL通信できないことがわかりました。 別リージョンとなるとさすがにSSL通信をしたかったため、高額ですがクロスリージョンリードレプリカを使うことになってしまいました。今はこのサーバが別リージョンのリアルタイムバックアップと、定期的なdumpデータバックアップを取得する役割を担っています。

これはそもそも別リージョンにリアルタイムバックアップを取る必要があるのか…という話なのですが…。

レプリケーション間のTimezoneがズレていたことによるデータズレ

移行前のMySQLサーバと、移行予定のAuroraサーバのTimezoneがズレていることにより、移行当日のデータ差分チェックで実際に差分が発生してしまい、移行作業をロールバックしています。
具体的には、移行前のMySQLはSYSTEM、というタイムゾーン設定で、これはOSのタイムゾーンを用います。移行前のOSのタイムゾーンはJSTでした。 一方、AuroraのOSのタイムゾーンはUTCですので、そのままSYSTEM設定でレプリケーションをすると、UTCとしてレプリケーション時のクエリが流れてしまいます。 この現象により、データのズレが発生しました。

移行前のMySQLサーバのタイムゾーンを変更することで問題は解決しました。

フェイルオーバー時に旧コネクションを維持してしまう問題

Auroraは障害時、迅速にフェイルオーバーしてくれるのですが、フェイルオーバーをテストしていると、コネクション周りで1つ問題が発生しました。 Auroraのフェイルオーバーは、Master側に障害が発生すると、SlaveのリードレプリカがMasterとして昇格するのですが、障害が発生したMaster側が、復旧後Slaveとして戻ってきます。 つまり、MasterとSlaveが逆転します。 この動きと、Webアプリケーションで行うコネクションプーリングとの相性があまり良くないのです。 具体的には、Webアプリケーションが持つMaster用のコネクションがフェイルオーバー時に破棄されずに保持されており、そのコネクションがSlaveとして復旧したAuroraインスタンスに接続しようとしてしまい、更新時にエラーが起きる、というものです。 毎回コネクションを作成すれば回避できる問題ですが、できればコネクションは使いまわしたいので、以下の解決を取りました。

Tomcatの場合

参考にした記事は以下の通り。

qiita.com

こちらは、context.xmlで設定するValidationクエリで、コネクションの状態がReadOnlyかどうかをチェックするストアドを作成し、それを使う、というものです。 Tomcatはこちらの方法で対応しました。

Railsの場合

Railsでは、基本的にconnectionは永続的に使うようになっているのですが、それを定期的に破棄する設定を加えました。 参考にした記事は以下の通り。

blog.livedoor.jp

github.com

activerecord-refresh_connectionを使うと、コネクションが一定数利用されると破棄されるようになります。 この対応はフェイルオーバーした直後にコネクションの向き先が変わることはないのですが、一定回数使われれば新しく接続されるため、一定時間が経過すれば正しいconnectionが得らるようになります。

クロスリージョンリードレプリカを作成するためのbinlogを有効にすると性能が落ちる

具体的には、binlog_formatMIXEDにしているのですが、これにより性能が劣化することがわかりました。ただし、弊社ではクロスリージョンリードレプリカの作成は必須だったので、受容しました。
詳細な指標は、以下のqiita記事に記載されていますが、かなり劣化しています。

qiita.com

その他、あまり語られることが少ないが、やらないといけなかったもの

  • エラーログ・スロークエリログ
     定期的にS3にログを吐き出す対応を、Lambdaで行っています。

まとめ

以上、弊社が遭遇したAurora移行時の失敗・想定外の出来事です。Aurora移行前は、簡単に移行できるでしょ…と思っていましたが、細かい問題が色々ありました。大抵のことはそうですよね…。
尚、移行して数ヶ月経過しましたが、特に問題は発生していません(気づいていないだけ?)。 これから移行を検討されている方、是非参考にしてみてください!

シンクロ・フードではエンジニアを募集しています。こんなインフラサイドの記事を書いていてアレなのですが、最近ではRailsをバリバリ書ける方を募集中です…。是非ご応募ください!

www.synchro-food.co.jp

AWS 認定ソリューションアーキテクト アソシエイト(新版)を取得しました

基盤チームの安藤です。
今回はAWS 認定ソリューションアーキテクト アソシエイトを取得した話を紹介しようと思います。
インフラ歴は3年ほどで最近までは開発と半々でしたが、積極的にインフラに関わっていくことになったためこの資格に挑戦しました。

AWS 認定ソリューションアーキテクト アソシエイト(新版)とは?

今回受験した新版は2018年2月から受験可能になった新しいバージョンで、内容が刷新されただけでなく問題数や試験時間なども大きく変更されています。
取得した当時は試験は英語でのみ受験可能でしたが、現在は日本語での受験も可能になっているようです。
詳しくは公式ページをご参照ください。
https://aws.amazon.com/jp/certification/certified-solutions-architect-associate/

勉強方法について

新版については情報が少なかったり、対応した教材がまだ少なかったため情報収集に苦労しました。
また、今回は英語での受験ということもあり主に英語の教材を選びました。
準備期間は2週間ほどでした。

Udemy - AWS Certified Solutions Architect Associate 2018 コース

https://www.udemy.com/aws-certified-solutions-architect-associate/
確認用のミニクイズや模擬試験が付いたUdemyのビデオ講座です。
内容はソリューションアーキテクト アソシエイトの試験に特化してかなりまとまった内容でした。
全てを通すとかなり長時間になるため、業務で利用したことがあるサービスは倍速で流し見して、触ったことがないサービスはなるべく手を動かすという形で取り組みました。
一部内容が古く現在では正しくない内容もありましたが、Q&Aで講師や他のユーザーからフォローを受けることができました。

Q&Aには実際に試験を受験した人たちのアドバイスやフィードバックも投稿されていて非常に役立ちました。
新版の情報が少ないなか、どういった範囲の問題がよく出題されるかなど傾向がつかめます。

A Cloud GuruのAWS Certified Solutions Architect - Associateというコースと内容は同じようですが、セール価格で購入できればかなりお得だと思います。
アカウントを連携することでA Cloud Guruのコースに付属している模擬試験も受験できました。

Wizlabs Practice Tests

https://www.whizlabs.com/aws-solutions-architect-associate/practice-tests/
UdemyのコースのQ&Aでよくおすすめされていた練習問題集です。
旧版、新版それぞれに対応した問題が用意されていて、問題数も豊富で解説が丁寧でした。

公式の模擬試験

有料ですが、本番の試験と同様の形式で受験できます。
形式に慣れる意味でもおすすめです。
試験結果は全体の正答率と5つのセクションごとの正答率しかわからず、問題ごとの正否まではわかりませんでした。
また、試験問題を振り返って確認することもできないため要注意です。

試験の流れ

申し込み

AWS training and certificationにログイン後、PSIから試験の予約を行います。
予約の際には受験料16,200円も支払います。今回は会社に全額負担してもらいました。
試験日については、都内であれば平日はほぼ毎日受験可能ですが、休日に受験できる会場はごく一部に限られているようです。

試験当日

会場で受付後、身分証明書(免許証とクレジットカードなど2枚以上の組み合わせ)と紙、ペンだけを持ってブースに入ります。
ブースには身分証明書のスキャナー、Webカメラが設置されたPCが用意されていました。
画面上のガイドと日本語のチャットの指示に従って身分証明書をスキャン後、試験が開始します。
試験中の質問などはチャットを通じて行えます。
試験完了後にはアンケートがあり、最後に試験の合否が画面に表示されて終了になります。

試験終了後

試験後数日以内にAWS training and certificationでスコア等が確認できるようになります。
試験のスコアは100から1000で評価され、合格ラインは720です。
また、セクションごとにMeets Competencies/Needs Improvementの判定があります。

試験の結果

899/1000というスコアで無事合格できました。
セクションごとで見ても全セクションでMeets Competenciesとなり偏りなくスコアを取れました。

感想

  • VPC,サブネット周りの基礎を改めて学べた
  • サービスの詳細な仕様を学べた
    • S3のデータ整合性モデルなど
  • 今まで見落としていた機能、設定を知ることができた
  • 様々なパターンのベストプラクティスを学べた
  • AWSの更新情報に敏感になった

など色々と得るものが多く、とても有用なものになりました。

その他にも

  • AWSサミットで認定者ラウンジに入場できる
  • AWS認定ストアで認定グッズが購入できる

といったおまけもあるようなので、機会があれば試してみようと思います。

最後に

以上、認定ソリューションアーキテクト アソシエイト取得までの紹介でした。
新版についてはまだまだ情報も少ないので、これから挑戦する方の参考になればと思います。


シンクロ・フードではエンジニアを募集しています。 資格の取得なども柔軟に補助がでますので、ご興味のある方は以下よりご連絡ください!

依存しあう複数の Git リポジトリを git subtree で monorepo 化する

基盤チームの川井 (@fohte) です。

今回は、monorepo と呼ばれる複数のリポジトリを単一のリポジトリで管理する運用方法の紹介と、実際にその運用に切り替えた話をします。

弊社は GitHub Enterprise を使っており、Git Flow を独自に拡張した master, develop, feature ブランチの 3 種類からなる運用フローを採用しています。 今回はその環境を前提としてお話します。

monorepo とは?

monorepo は、複数リポジトリを単一のリポジトリで管理するという Git リポジトリの運用方法の一つです。

monorepo のメリットとしては以下の点が挙げられます。

  • 密結合なリポジトリの運用が楽になる
  • 複数リポジトリに横断した修正が簡単にできるようになる

例えば、あるリポジトリ A の変更に従って別のリポジトリ B も変更するとき、リポジトリが複数分かれているとそれぞれのリポジトリに commit が分散することになります。 これは単純に面倒ですし、何より commit 可読性も悪くなります。

また、このようなときはリポジトリごとに Pull Request を立てる必要がありますが、monorepo では 1 つにまとまるため、Pull Request の可読性も向上します。

反対に monorepo のデメリットとしては以下の点が挙げられます。

  • リポジトリが巨大になる

複数に分散しているリポジトリを 1 つにまとめてしまうので、結果として Git リポジトリは統合したリポジトリの分だけ大きくなります。

あまり見慣れない運用方法ではありますが、RailsBabelReact といった巨大な OSS プロジェクトでも採用されています。

monorepo へシームレスに移行する

monorepo に移行するとなると、各リポジトリのブランチも monorepo 側に移さなければいけません。 そこで、開発者の手を止めることなく monorepo に移行する手段として、以下の方法を検討しました。

  • 各リポジトリの feature ブランチを含めたブランチ全てを monorepo に移し、移行直後から monorepo 側の feature ブランチで開発する f:id:synchro-food:20180329222655p:plain
  • master ブランチだけを monorepo に移し、各リポジトリで開発中の feature ブランチは merge 後に移行する f:id:synchro-food:20180329222652p:plain

前者の場合、各開発者の手元の変更は monorepo 側に取り込まれないという欠点があるため、今回は後者の手段を選択しました。 また、弊社では master ブランチとは別に develop ブランチもありますが、develop ブランチも後述の方法で移行すると master と develop が全行差分になってしまう問題があったため、master ブランチ移行後に新規に master から develop ブランチを切り直しました。

移行手順

今回は git subtree コマンドを用いて移行しています。

git subtree 自体は git に内蔵されているコマンドではないため、環境によってはコマンドが無く、その場合は手動でインストールする必要があります。

git clone https://github.com/git/git.git
cd git/contrib/subtree
make
cp git-subtree /usr/local/bin # パスが通っているディレクトリにコピーする

各リポジトリの master ブランチを monorepo に移行する

先に monorepo 化する各リポジトリの remote を登録し、fetch しておきます。

git remote add <name> <repo_url>
git fetch -a <name>
  • <name>: remote の名前です。今回は monorepo のサブディレクトリ名とします。
  • <repo_url>: 各リポジトリの URL です。 (例: git@github.com:rails/rails.git, https://github.com/rails/rails.git)

また、空 commit を作成しておきます。

git checkout --orphan master
git commit --allow-empty -m 'Root commit for master branch'

次に、git subtree add コマンドで各リポジトリを monorepo のサブディレクトリとして追加します。

git subtree add --prefix=apps/<name> <name>/master

--prefix に指定したディレクトリにリポジトリが取り込まれます。 今回は apps ディレクトリ下にフラットにリポジトリを置いています。

最後に、念のため git diff-tree で差分をチェックし、問題がなければ master ブランチの移行は完了です。

git diff-tree -r --stat <name>/master master:apps/<name>

feature ブランチの変更内容を monorepo に取り込む

今回は、各リポジトリで開発中の feature ブランチはそのリポジトリに merge してしまい、その後 monorepo 側に取り込みます。

その後、git subtree merge コマンドでサブディレクトリに merge し、monorepo 側に各リポジトリでの変更を取り込みます。

git checkout master
git subtree merge --prefix=apps/<name> <name>/<branch>

master ブランチ移行時同様に差分をチェックし、問題がなければ feature ブランチの変更の取り込みは完了です。

git diff-tree -r --stat <name>/master master:apps/<name>

所感

移行前に懸念していたこととして、上述のデメリットとしても挙げた Git リポジトリが重くなるかもしれないという点がありました。 今回はアクティブな Rails アプリケーションのみ (5, 6 リポジトリ) を monorepo 化し、commit の総数は 6,000 ほどとなりましたが、現時点では git コマンドに重さを感じることはありません。 ファイルが増えている分容量自体は大きくなっているため git clone 時などに重さを感じますが、これは複数リポジトリを git clone しているようなものであるため許容しています。

また、移行の難易度やコストが高いのではという懸念もありましたが、想像以上に容易に移行することができ、全く問題はありませんでした。

monorepo 化して 1 ヶ月ほど運用されていますが、特に大きな問題もなく、1 リポジトリで完結することの恩恵を大いに受けて生産性が向上していると感じます。

まとめ

今回は monorepo という Git リポジトリの運用方法について紹介しました。 git subtree コマンドを用いると手軽に monorepo 化できるので、密結合なリポジトリがあるときの解決策の 1 つとしてぜひ検討してみてください!


シンクロ・フードでは開発基盤設計に興味のあるエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!

Flow で静的型付けしながらフロントエンド開発した話

基盤チームの川井 (@fohte) です。

今回は飲食店リサーチというサービスのフロントエンドを Flow で型付けしながら React で開発して得た知見の話をします。

Flow とは?

Flow は Facebook 社が開発した、JavaScript の構文を拡張して静的型解析機能を提供するツールです。
例えば以下のような Flow を使ったコードは、Flow の型チェックによって事前にバグを検出することができます。

/* @flow */
function square(n: number): number {
  return n * n
}

square('2') // エラー

嬉しいですね。雑にコードを書いていても Flow が型の不一致を警告してくれるため、書いたコードに対して安心感が生まれます。さらに型解析により定義元ジャンプや補完も効くため、開発効率は大きく向上することでしょう。

Flow はあくまで静的型解析機能のみを提供するため、TypeScript などとは異なりそれ自身でトランスパイルは行なえません。実用するためには拡張された構文だけを取り除く以下のようなツールを用いて通常の JavaScript に変換する必要があります。

トランスパイルがほぼ必須となることから気軽さは少ないですが、既に JavaScript のビルド環境が整っている場合には導入しやすいです。

なぜ Flow を使うのか

今回開発した飲食店リサーチというサービスではフォームが動的に生成され、また入力内容によってフォームが動的に変化するという要件がありました。これを実現するためにはそれなりのコード量を必要とする複雑な処理を書く必要があり、そこで静的型解析ができればコード量が増えても安心できるプログラムが書きやすいのではと考え、Flow の導入に踏み切りました。

なぜ TypeScript ではないのか

同じく静的型付き言語の AltJS である TypeScript は、たびたび Flow と比較して語られます。静的型解析が欲しいという要求には TypeScript は十分に満たしているため TypeScript でも問題はありませんでしたが、既に webpack + Babel でのビルド環境が整っていたため、それに乗っかる形で今回は Flow を選択しました。

TypeScript と Flow で機能面での差はほとんどありませんが、TypeScript を選択するメリットとして、コミュニティが Flow よりも大きく (Flow も大きいですが) 歴史も長いためドキュメントが豊富です。 Flow は公式のドキュメントにない情報も少なくなく、また問題が発生したときもリポジトリの issues を見に行くしかないことが多い印象があります。

Flow は着実にバージョンアップを重ねており、このような問題も次第に解消されることを期待しています。

Flow を導入した所感

今回の開発は長期的にかつ断続的に続いたため過去のコードを思い出す手間がありましたが、型定義がドキュメント代わりになっていてその手間はほとんど省けました。静的に型チェックができることで、自分がコードを忘れていても型が間違っていれば即座にエラーになりますし、非常に体験が良かったです。

一方でデメリットとして、複雑な型を表現すると Flow 特有の知識が必要になり、ドキュメントが少ないことも相まって学習難度が上がってしまいます。難しい代表例としては、ある型から別の型を生成する Utility Types が挙げられます。例えばある object 型の key を全て read-only にしたいとき、$ReadOnly を用いればそれを表現できます。

type Props = {
  x: string,
  y: number,
  z: boolean,
}

type ReadOnlyProps = $ReadOnly<Props>

function render(props: ReadOnlyProps) {
  console.log(props.x) // OK
  props.x = 'foobar' // Error

  // ...
}

このような Utility Types は柔軟に型を表現できますが、Flow に慣れていないと難読な型表現になってしまいます。 Utility Types を駆使しないと表現できない型もあり、全てを厳密に型付けしようとすると消耗してしまいます。ときには型検査を捨てる any type を使い、型定義を妥協することが必要になることもあります。 *1

まとめ

今回は JavaScript に静的型解析機能を提供する Flow を紹介しました。型定義に苦戦することはあるものの、静的型解析で受けられる恩恵は非常に大きいです。

ぜひ快適な静的型解析生活を!


シンクロ・フードではフロントエンド開発に興味のあるエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!

*1:mixed type という逃げもあります。