新しいUber Passengerアプリケーションアーキテクチャの設計

-こんにちは。 Uberのようなアプリケーションを作成するにはどれくらいの費用がかかりますか?



当社の着信アプリケーションの管理者は、週に一度、そのようなコンテンツの電話を安定して受け取ります。 原則として、それを理解する価値があります。クライアントは、乗客と運転手間の通信のためのアプリケーションの同様に成功したアナログ、または______のUber(希望する業界に入る)のいずれかを望んでいます。



そのような瞬間に、Uberは技術的に非常に複雑なプロジェクトであり、数百万の投資と数十万の開発時間があるため、そのクローンを作成することはあまりお勧めできません。



今、私たちの立場を擁護する議論があります。 Uber開発者は、あるアーキテクチャから新しい独自のアーキテクチャにアプリケーションを移植した経験について、同社のブログにメモを公開しました。 この非常に大規模なイベントは、Uberが基本的なアプリケーションからほど遠いことを確認します。 私たちはこの資料を過ぎて翻訳することはできませんでした。



この記事は、モバイル開発者だけでなく、説明された状況に直面しているマネージャーにとっても有用です。







Uberを再編集した理由



Uberのアイデアはシンプルです。ボタンを押すと、必要な場所に移動できます。 プレミアムブラックカーを注文するサービスから始まり、今日Uberは膨大なサービスを提供し、毎日何百もの都市で何百万もの旅行を調整しています。 2017年の現実を満たすために、アプリケーションアーキテクチャ全体を再開発する必要がありました。



しかし、どこから始めますか? 2009年に開始したことから:最初から。 アプリケーションを完全に書き直し、そのデザインをやり直すことにしました。 開発されたソフトウェアベースと設計ソリューションの拒否は、そうでなければ妥協点を探さなければならないような行動の自由を与えてくれました。 出力で、私たちは完全に新しいアプリケーションを受け取って、なめらかに輝き、その新しいアーキテクチャのすべての利点をiOSユーザーとAndroidユーザーの両方に与えました。 この記事では、Ribletsと呼ばれる新しいアーキテクチャパターンを作成した方法と、それを使用して目標を達成した方法について説明します。







動機と目標



ドライバーと乗客間のコミュニケーションを提供するというUberの主なアイデアはまだ残っていますが、当社の製品はより大きなものに成長し、以前のアプリケーションのアーキテクチャは要件の増加に対応できなくなりました。 毎年アプリケーションに新しい機能を追加することはますます難しくなりました。 UberPOOL、計画された旅行、車の写真などのエクストラは、物事を難しくしました。 次の変更を行った後、アプリケーションの一部が動作を停止するリスクは、そのサイズとともに日ごとに増加しました。これが、あらゆるデバッグが長いデバッグの理由になる可能性があるためです。



ある時点で、それ以上の成長はまったく不可能でした。 高品質のユーザーサービスを維持するためには、現在のニーズと将来の開発能力を考慮して、かつてのシンプルさを再検討する必要がありました。



新しいアプリケーションは、乗客とUber開発者の両方にとってシンプルであり、毎日改善して新しい機能を追加するために日々取り組んでいます。 最初と2番目の両方の利益を考慮に入れてアプリケーションを作り直すために、2つのタスクを設定します。結果は乗客にとってできるだけアクセスしやすく、製品内で徹底的な実験を行えるようにする必要があります。



信頼性が最重要



開発者のタスクは、信頼性が99.99%のアプリケーションを作成することです。 これは、アプリケーションが1年に1時間または1週間に1回しか動作しないことを意味し、10,000回の起動では1回の失敗しか与えられません。



これを実現するために、新しいアプリケーションの構造がメインコードとオプションコードに分割されました。 基本的なコード-アプリケーションの入力、確認、完了、および旅行のキャンセルに関連するすべてのものは、時計のように機能するはずです。 メインコードに加えられた変更はすべて厳密にテストされています。 オプションのコードに加えられた変更は、それほど厳密ではないチェックに合格し、アプリケーション全体を停止することなく無効にできます。 このようなコードの分離のおかげで、新しい機能を追加する機会があり、誤った操作が発生した場合、ユーザーに不便を与えることなく自動的にそれらをオフにします。



今後の計画



数百のチームと数千のエンジニアが乗客に不便を与えることなく高品質のアドオンを開発し、アプリケーションに組み込むことができるプラットフォームが必要です。 そのため、AndroidとiOSの開発者が同等の条件で作業できるように、新しいクロスプラットフォームアーキテクチャを作成しました。



通常、AndroidとiOSに最適なアプリケーションを作成するには、そのアーキテクチャ、ライブラリ、および分析ツールへの個別のアプローチが必要です。 新しいアーキテクチャでは、両方のプラットフォームで同じパターンとプラクティスを使用する必要があります。 これにより、あるプラットフォームで見つかったソリューションを別のプラットフォームで作業しながら適用することで、最大限の利益を得ることができ、私たち自身の間違いから学ぶことができます。 その結果、AndroidとiOSの開発者は密接に協力して、新しいオプションと追加機能を作成できます。



場合によっては、開発は各プラットフォーム(ユーザーインターフェイスの実装など)ごとに個別に行われますが、両方のプラットフォームには多くの共通点があります。





これらの共通機能を最大限に活用するために、新しいアーキテクチャでは、ビジネスロジック、プレゼンテーションロジック、データフロー、ルーティングを明確に編成し、分離する必要があります。 このようなアーキテクチャは、混乱を避け、テストを簡素化し、したがって、開発の生産性と最終製品の信頼性を向上させるのに役立ちます。



MVCからRibletsへ



タスクを決定した後、古いアーキテクチャを改善する方法を見つけることを決定し、可能性を探り始めました。 古いバージョンのUberから継承したコードベースは、MVCアーキテクチャパターンに基づいていました。 その他のパタ​​ーン、たとえばVIPERなど、Ribletsの作成に部分的に使用したものを検討しました。 Ribletsのコアイノベーションは、プレゼンテーションロジックではなくビジネスロジックを介したルーティングです。 MVCとRibletsに慣れていない場合は、iOSの最新のアーキテクチャパターンに関する記事をいくつか読んでください(たとえば、 これ )。これらのパターンをUberに適合させることの利点と欠点を理解しやすくなります。



開始場所:MVC(Model-View-Controller)



最初の乗客用アプリは、ほぼ4年前に小規模な開発チームによって作成されました。 その瞬間、MVCの使用は正当化されたように見えました。 開発チームが数百人に成長したとき、MVCは私たちと共に成長できないという事実に直面しました。 主な理由は2つあります。



  1. 成長するMVCアーキテクチャでは、大規模なビューコントローラーと呼ばれる問題が発生することがよくあります。 たとえば、現在の状態の300行のコードで始まるRequestViewControllerには、ビジネスロジック、データ操作、データ検証、ネットワークロジック、ルーティングロジックなど、多数の義務が割り当てられているため、3,000行以上が含まれています。 読み取りと変更は非常に困難になりました。
  2. MVCアーキテクチャは、コードを更新するための非常に脆弱なプロセスを提供し、テストを複雑にします。 新しいアドオンの開発プロセスでは、多くの実験を行います。 すべての実験は、if-elseステートメントを使用することになります。 多数の機能を持つクラスが遭遇するたびに、if-elseステートメントが互いに衝突し、テストの可能性をゼロに減らします。 これに加えて、RequestViewControllerやTripViewControllerなどの内部コードの成長に伴い、アプリケーションの更新プログラムの作成は非常に脆弱で機密性の高いプロセスになりました。 それがどのようなものか想像してみてください-考えられるすべての変更について、相互にネストされたif-elseステートメントの考えられるすべての組み合わせをテストしてください。


アプリケーションをさらに開発し、Uberビジネスを成長させるという目標を実験し続けたいと思ったため、このアーキテクチャが使い果たされたことを認めなければなりませんでした。



途中:VIPER



MVCの代替案を検索する過程で、iOSアプリケーションの開発に純粋なアーキテクチャを適用する例であるVIPERを見つけました。 VIPERには、MVCよりもいくつかの重要な利点があります。



  1. 彼はより多くの抽象化を提供します。 Presenterには、ビジネスロジックをプレゼンテーションロジックに接続するロジックが含まれています。 インタラクターは、データの操作と検証に関与しています。 これには、旅行を入力または予約するなど、状態を操作するバックエンド要求が含まれます。 最後に、ルーターは遷移を初期化します(そのうちの1つは、ホームページから注文確認ページへの遷移のようなものです)。
  2. VIPERの場合、プレゼンターとインタラクターはプレーンオールドオブジェクトなので、通常の単体テストを使用できます。


ただし、VIPERにはいくつかの欠点もあります。

  1. iOSに特化したその設計は、Androidの妥協点を見つける必要があることを意味しました。
  2. ビュー駆動型ロジックは、アプリケーションがビューのコンポーネントによって制御され、アプリケーション全体がビューツリーにバインドされることを意味します。
  3. Interactorによって実行されるビジネスロジックは、アプリケーションの状態を管理する必要があり、常にプレゼンターを通過する必要があります。
  4. 表現とロジックツリーが互いに密接に関連している場合、1種類のロジックのみを含む要素の実装は非常に複雑になります。


MVCより明らかに優れているため、VIPERは明確なモジュール性を備えた拡張可能なプラットフォームに対するUberのニーズを完全に満たすことはできません。 したがって、私たちは製図板に戻り、VIPERの利点と欠点のないアーキテクチャパターンの開発を試みました。 結果はRibletsでした。



リブレット:Uber Passenger Application Architecture



新しいアーキテクチャパターンでは、ロジックは独立してテスト可能な小さな部分に分割されます。 各部分は、単独責任の原則に従って単一の目的を持っています。 これらのモジュラーピースの形でRibletsを使用し、アプリケーション全体の構造はRibletsツリーです。



リブレットとそのコンポーネント



Ribletsを使用して、6つの異なるコンポーネントに責任を分散し、プレゼンテーションロジックからビジネスロジックをさらに抽象化しました。







RibletsとVIPERおよびMVCの違いは何ですか? ルーティングは、プレゼンテーションロジックではなくビジネスロジックによって行われます。 つまり、アプリケーションは外観ではなく、情報と意思決定の流れによって制御されます。 Uberのビジネスロジックのすべてが、ユーザーが見るものに関連しているわけではありません。 MVCのViewControllerでビジネスロジックを使用したり、VIPERでPresenterを使用してアプリケーションの状態を操作したりする代わりに、ビジネスロジックごとに個別のRibletを作成し、より簡単に操作できるローカルグループを作成できます。 さらに、プラットフォームに依存しないようにRibletパターンを設計しました。 後者では、iOSとAndroidの開発を組み合わせることができます。



各Ribletには、 Rアウター、 I nteractor、 B uilderが含まれ、独自のコンポーネント(名前)があり、必要に応じて、プレゼンターとビューがあります。 ルーターとインターアクターはビジネスロジックを行い、プレゼンターとビューはプレゼンテーションロジックを行います。



Production Select Ribleを例として使用して、各Riblet要素の機能を見てみましょう。





新しいUberアプリでの関税選択



ビルダー

Builderは、すべてのプライマリRiblet要素とそれらの間の依存関係をインストールします。 製品選択リブレットでは、この要素は必要な都市のデータフロー依存関係を設定します。



成分

コンポーネントは、Riblet依存関係を受け取り、インストールします。 これには、サービス、データストリーム、およびRibletの主要な要素以外のすべてが含まれます。 製品選択コンポーネントは、都市交通の依存関係を受信して​​確立し、対応するネットワークイベントに関連付けて、Interactorに実装します。



ルーター

ルーターは、子Ribletsをアタッチおよびデタッチすることにより、アプリケーションツリーを形成します。 これらの決定は、Interactorによって渡されます。 ルーターは、特定のアプリケーションの状態でインターアクターのライフサイクルをオンまたはオフにすることによっても管理します。



ルーターには、2つのビジネスロジックが含まれています。



  1. ヘルパーメソッド-ルーターをアタッチおよびデタッチします。
  2. 状態切り替え-子モジュールの状態を決定するためのロジック。


Product Selection Ribletには関連するRibletsはありません。 親RibletのルーターであるConfirmation Ribletは、製品選択ルーターを接続し、そのビューをビュー階層に追加する役割を果たします。 次に、製品が選択されると、製品選択ルーターはそのインターアクターを非アクティブにします。



インタラクター

インタラクターはビジネスロジックを実行します。 これには以下が含まれます:







Product Selection Interactorは、この都市のサービスからのオファー、価格情報、推定所要時間、車の写真などのデータを含む都市ストリームを取得し、この情報をPresenterに転送します。 ユーザーがuberPOOLからuberXに切り替えると、Interactorはプレゼンターからこの情報を受け取り、その後、すべての関連データを収集してViewに返し、uberX車と到着予定時刻を表示します。 つまり、Interactorは、Viewが表示するすべてのビジネスロジックを実行します。



ビュー(コントローラー)

ビューは、個々の要素の作成と配置、ユーザーとの対話、インターフェイス要素へのデータとアニメーションの入力など、ユーザーインターフェイスを構成および更新します。 製品選択の表示Ribletは、プレゼンターから渡されたオブジェクト(旅行構成、価格、到着予定時刻、地図上の車の画像)を表示し、ユーザーのアクション(つまり製品選択)を返します。



発表者

Presenterは、インタラクターとビュー間の通信を管理します。 インタラクターからビューまで、Presenterは、ビューに表示されるオブジェクトのビジネスモデルを伝えます。 Product Selection Ribletの場合、これらは価格データと車の画像です。 Presenterのもう1つの目標は、ユーザーアクション(製品の選択ボタンをクリックするなど)をコマンドに変換し、Interactorに渡すことです。



すべてをまとめる



各RibletにはRouterとInteractorのペアが1つだけ含まれていますが、ビューのいくつかの部分を持つことができます。 ビジネスロジックのみを担当し、ユーザーインターフェイス要素を持たないRibletには、ビューパーツが含まれていません。



Ribletは次のとおりです。





これにより、ビジネスロジックツリーをより深く、フラットなプレゼンテーションツリーとは異なるものにすることができ、画面間の切り替えが容易になります。



たとえば、Ride Ribletはビューレスです。 そのタスクは、ユーザーがアクティブな旅行をしているかどうかを確認することです。 その場合、彼は旅行のルートを地図上に表示するTrip Ribletを接続します。 そうでない場合、Request Ribletが接続され、ユーザーが旅行を注文できる画面が表示されます。 プレゼンテーションロジック(Ride Ribletなど)を含まないRibletsの主な目的は、アプリケーションを制御するビジネスロジックを分離し、新しいアーキテクチャのモジュール構造を維持することです。



Ribletsからアプリケーションを作成する方法



リプレットはアプリケーションツリーで結合され、情報を更新したり、旅行を注文する次のステップにユーザーを誘導したりするために、互いに連絡を取り合う必要があります。 互いに通信する方法について説明する前に、単一のRiblet内でデータがどのように送信されるかを見てみましょう。



Riblet内のデータフロー



Interactorは、アプリケーションを制御し、このInteractorの範囲内にあるビジネスロジックを格納します。 この要素は、必要なデータを取得するためにサービスへのリクエスト(サービス呼び出し)を行います。



新しいアーキテクチャのデータは常に一方向にのみ送信されます。 サービスからモデルストリームに移動してから、Interactorに移動します。 インタラクター、イベントプランナー、およびインターネットからのプッシュ通知は、モデルストリームを変更するためにサービスに要求を行うことができます。



モデルストリームは不変モデルを作成します。 これにより、アプリケーションの状態を変更するために、Interactorクラスがサービスレイヤーを使用する必要があるという要件が作成されます。







スレッドの例:







リブレット間の相互作用









Interactorが決定を下す場合、別のRibletに通知してデータを転送する必要があります。 これを行うために、Interactorは別のRibletのInteractorと交渉するインターフェースを呼び出します。



リンクがツリーを上って親Ribletのインターアクターに移動する場合、インターフェイスはリスナーとして定義されます。 リスナーはほとんどの場合、親Ribletのインターアクターによって設定されます。 接続が子Ribletにダウンした場合、インターフェイスはデリゲートとして定義され、子Ribletのインターアクターによって設定される必要があります。 デリゲートは、親のインタラクターと子などの要素間の直接同期通信にのみ使用されます。



ダウンリンクの場合、親Ribletは子Ribletのインターアクターに監視可能なデータストリームを送信でき、親インターアクターはデリゲートインターフェイスの代わりにこのストリームを介してデータを送信できます。 データ転送のほとんどのダウンリンクの場合、同様の方法が推奨されます。



たとえば、仮想のProductSelectionInteractorが製品が選択されていることを理解すると、ConfirmationInteractorによって設定されたリスナーを呼び出し、選択された車両のビュー識別子を渡します。 ConfirmationInteractorはこのIDを保存して、サービスリクエストとして送信できるようにし、「Disable」ProductSelection Ribletをルーターに送信します。



このようにRiblets内およびRiblets間のデータフローを構造化することにより、適切なデータが適切なタイミングで適切な画面に表示されるようになります。 Ribletsツリーはビジネスロジックに基づいているため、プレゼンテーションロジックではなくビジネスロジックのレベルで通信を確立できます。 これは非常に理にかなっており、コードの分離を維持し、アプリケーション開発を不必要な複雑さから保護します。



基本に戻る



アプリケーションを完全にやり直すことにしたとき、信頼性が向上したことでユーザーに近づき、将来の開発に適した条件を作成したかったのです。 これら2つの目標を達成するには、新しいアーキテクチャを作成することが重要なステップでした。



信頼性とエンドユーザーアクセシビリティの向上を達成した方法



リブレットは明らかに責任を明確にしているため、テストはずっと簡単になりました。 各Ribletは、他のRibletとは独立してテストできます。 これらの機能により、次回の更新後にアプリケーションにバグが発生しないことを確認できます。 各Ribletには1つの責任があるため、Ribletsを主要なもの(アプリケーションの入力とuberPOOLとuberXへの旅行の注文に必要)とオプションのコードに簡単に分けることができました。 メインコードのチェックに対してより高い要求を行うことにより、プログラムのメイン機能が最大限の信頼性で動作することを確認できます。



また、プログラムの主な機能を保証された動作状態にグローバルにロールバックできるようにしました。 すべてのオプションコードには、マスター機能フラグが付いています。コードの一部にエラーがあるか、不安定な場合は、オフにすることができます。 最悪のシナリオでは、基盤のみを残して、オプションのコードをすべて完全に無効にすることができます。 メインコードの最も厳しい要件を考慮すると、常に機能することを確認できます。



将来の開発のために適切な条件を作成した方法



各Ribletの専門性が狭いため、ビジネスロジックとプレゼンテーションロジックの間に明確な境界線を引くことができました。 これにより、コードベースが圧倒されるのを防ぎ、シンプルさを保つことができます。 新しいアーキテクチャはクロスプラットフォームであるため、iOSおよびAndroid開発者は互いの作業を簡単に理解し、友人の間違いから学び、協力してUberをさらに開発できます。 Ribletsはオプションのコードをメインのコードから分離するのに役立つため、実験はアプリケーションコアのパフォーマンスにあまり影響を与えません。 プラグインの形で作成された新しいアドオンが、アプリケーション全体を誤って無効にすることを恐れることなく体験できます。



Ribletsの主な機能は、責任の最大限の抽象化と分離、および明確に定義されたデータフローと通信方法であるため、さらなる開発が非常に簡単になり、このアーキテクチャは長年にわたって忠実に機能します。



前進する意欲



新しいアーキテクチャには大きな期待が寄せられています。 乗客用のアプリケーションコードを完全に書き直し、以前から存在していたものをすべて追加し直し、ユーザー調査、ケーススタディ、さまざまなテスト、更新オプションを実施しました。その中にはニュースフィードがあります。 ユーザーができるだけ早く更新されたアプリケーションを受信できるように最善を尽くしました。そのため、設計、オプション、ローカリゼーション、さまざまなデバイスでの作業、テスト機能に関する世界中のレビューに耳を傾けます。 アプリケーションの起動がすでに遅れているという事実にもかかわらず、まだ多くの作業があります。



新しいアーキテクチャのおかげで、さらなる開発の機会が膨大にあります。 正直に言うと、プロトタイプを組み立てるために数か月を費やし、すべてが正しく行われたことを確認しました。 これで、多くのことを達成できるアーキテクチャがあると完全に確信しています。 この種の作業が気に入ったら、iOSまたはAndroid開発者として開発に参加することで、私たちのストーリーの一部になり、Uberの意見を改善できます。



All Articles