Plattformübergreifende RIBs für die mobile Architektur von Uber

Am 20. Dezember 2016 veröffentlichten die Jungs von Uber Engineering einen Artikel über die neue Architektur (hier ist die Übersetzung dieses Artikels auf dem Hub). Ich präsentiere Ihnen die Übersetzung des Hauptteils der Dokumentation.

Wofür ist die RIB-Architektur?


RIBs ist ein plattformübergreifendes Architektur-Framework von Uber. Es wurde für große mobile Anwendungen mit einer großen Anzahl eingebetteter Zustände entwickelt.

Bei der Entwicklung dieses Frameworks haben die Uber-Ingenieure die folgenden Grundsätze eingehalten:

  • Unterstützung für die Zusammenarbeit zwischen Personen, die sich auf verschiedenen Plattformen entwickeln: Die überwiegende Mehrheit der komplexen Teile von Uber-Anwendungen ist unter iOS und Android ähnlich. RIBs bieten allgemeine Entwicklungsmuster für Android und iOS. Bei der Verwendung von RIBs können Ingenieure unter iOS und Android eine gemeinsam entwickelte Architektur für ihre Funktionen gemeinsam nutzen.
  • Minimierung globaler Zustände und Entscheidungen: Globale Zustandsänderungen können zu unvorhersehbarem Verhalten führen und es unmöglich machen zu wissen, wozu diese oder jene Änderungen im Programmcode führen werden. Die auf RIBs basierende Architektur fördert gekapselte Zustände in einer tiefen Hierarchie gut isolierter RIBs, um Probleme mit globalen Zuständen zu vermeiden.
  • Testbarkeit und Isolation: Klassen sollten einfach sein, um Unit-Tests schreiben zu können, und auch einen Grund für die Isolation haben (siehe SRP ). Einzelne RIB-Klassen haben unterschiedliche Verantwortlichkeiten (z. B. Routing, Geschäftslogik, Präsentationslogik, Erstellen anderer RIB-Klassen). Darüber hinaus ist die Logik der übergeordneten RIB grundsätzlich von der Logik der untergeordneten RIB getrennt. Dies erleichtert das Testen von RIB-Klassen und verringert die Abhängigkeit zwischen Systemkomponenten.
  • Tools für die produktive Entwicklung: Das Ausleihen nicht trivialer Architekturmuster kann zu Problemen beim Wachstum der Anwendung führen, wenn keine zuverlässigen Tools zur Unterstützung der Architektur vorhanden sind. Die RIBs-Architektur enthält IDE-Tools zum Erstellen von Code, zur statischen Analyse und zur Laufzeitintegration, die die Entwicklerproduktivität in großen und kleinen Teams verbessern.
  • Das Prinzip der Offenheit-Nähe: Entwickler sollten nach Möglichkeit neue Funktionen hinzufügen, ohne den vorhandenen Code zu ändern. Bei Verwendung von RIBs kann die Implementierung dieser Regel an mehreren Stellen gesehen werden. Sie können beispielsweise eine komplexe untergeordnete RIB anhängen oder erstellen, für die Abhängigkeiten von der übergeordneten RIB erforderlich sind, wobei die übergeordnete RIB nur geringfügig oder gar nicht geändert wird.
  • Strukturierung nach Geschäftslogik: Die Struktur der Geschäftslogik einer Anwendung sollte nicht unbedingt die Struktur der Benutzeroberfläche widerspiegeln. Um beispielsweise die Animation und Leistung einer Ansicht zu erleichtern, kann die Ansichtshierarchie kleiner als die RIB-Hierarchie sein. Oder eine einzelne RIB-Funktion kann das Erscheinungsbild von drei Ansichten steuern, die an verschiedenen Stellen auf der Benutzeroberfläche angezeigt werden.
  • Genaue Verträge: Anforderungen müssen mithilfe von Verträgen deklariert werden, die zur Kompilierungszeit überprüft werden. Eine Klasse sollte nicht kompiliert werden, wenn ihre eigenen Abhängigkeiten sowie Gastabhängigkeiten nicht erfüllt sind. Die RIBs-Architektur verwendet ReactiveX zur Darstellung von Gastabhängigkeiten, typsichere DI- Systeme (Dependency Injection) zur Darstellung von Klassenabhängigkeiten und viele andere DI-Funktionen zur Erstellung von Dateninvarianten.

Komponentenelemente RIBs


Wenn Sie zuvor mit VIPER- Architektur gearbeitet haben, werden Ihnen die Klassen, aus denen sich die RIB zusammensetzt, bekannt vorkommen. RIBs bestehen normalerweise aus den folgenden Elementen, von denen jedes in einer eigenen Klasse implementiert ist:



Interactractor


Interactor enthält Geschäftslogik. In dieser Klasse werden Empfangsbenachrichtigungen abonniert, Entscheidungen über das Ändern des Status, das Speichern von Daten und das Anhängen von untergeordneten RIBs getroffen.

Alle in Interactor ausgeführten Vorgänge sollten auf den Lebenszyklus beschränkt sein. Uber hat ein Toolkit erstellt, um sicherzustellen, dass Geschäftslogik nur mit aktiver Interaktion ausgeführt wird. Dadurch wird verhindert, dass Interactors deaktiviert werden. Rx-Abonnements werden jedoch weiterhin ausgelöst und verursachen unerwünschte Aktualisierungen der Geschäftslogik oder des Benutzeroberflächenstatus.

Router


Der Router überwacht Ereignisse von Interactor und konvertiert diese Ereignisse in das Anhängen und Trennen von untergeordneten RIBs. Der Router existiert aus drei einfachen Gründen:

  • Der Router ist ein passives Objekt, das das Testen komplexer Interactor-Logik vereinfacht, ohne dass Stubs für untergeordnete Interactors erstellt oder auf andere Weise für deren Existenz gesorgt werden müssen.
  • Router erstellen eine zusätzliche Abstraktionsebene zwischen übergeordneten und untergeordneten Interaktoren. Dies macht die synchrone Kommunikation zwischen Interactors etwas komplexer und fördert die Verwendung von Rx-Kommunikation anstelle der direkten Kommunikation zwischen RIBs.
  • Router enthalten eine einfache und sich wiederholende Routing-Logik, die andernfalls in Interactors implementiert würde. Durch die Portierung dieses Boilerplate-Codes auf Router können Interactors kleiner und stärker auf die Kernlogik des RIB-Geschäfts ausgerichtet werden.

Baumeister


Builder wird benötigt, um Instanzen für alle in der RIB enthaltenen Klassen sowie Instanzen von Buildern für untergeordnete RIBs zu erstellen.

Durch Hervorheben der Klassenerstellungslogik in Builder wird die Möglichkeit zum Erstellen von Stubs in iOS unterstützt und der Rest des RIB-Codes wird unempfindlich gegenüber den Details der DI-Implementierung. Builder ist der einzige Teil der RIB, der das im Projekt verwendete DI-System kennen muss. Durch die Implementierung eines anderen Builders können Sie den Rest des RIB-Codes im Projekt mithilfe eines anderen DI-Mechanismus wiederverwenden.

Moderator


Presenter ist eine zustandslose Klasse, die ein Geschäftsmodell in ein Präsentationsmodell übersetzt und umgekehrt. Es kann verwendet werden, um das Testen von Modellansichtstransformationen zu erleichtern. Oft ist diese Übersetzung jedoch so trivial, dass sie die Erstellung einer separaten Presenter-Klasse nicht rechtfertigt. Wenn Presenter nicht fertig ist, liegt die Übersetzung von Ansichtsmodellen in der Verantwortung von View (Controller) oder Interactor.

Ansicht (Controller)


View erstellt und aktualisiert die Benutzeroberfläche. Es umfasst das Erstellen und Anordnen von Schnittstellenkomponenten, das Behandeln der Benutzerinteraktion, das Auffüllen von Benutzeroberflächenkomponenten mit Daten und Animationen. Die Ansicht ist so "dumm" (passiv) wie möglich gestaltet. Sie zeigen einfach Informationen an. Im Allgemeinen enthalten sie keinen Code, für den Unit-Tests geschrieben werden sollen.

Komponente


Die Komponente wird zum Verwalten von RIB-Abhängigkeiten verwendet. Es hilft dem Builder, die anderen Klassen, aus denen die RIB besteht, zu instanziieren. Die Komponente bietet Zugriff auf externe Abhängigkeiten, die zum Erstellen der RIB erforderlich sind, sowie auf ihre eigenen Abhängigkeiten, die von der RIB selbst erstellt wurden, und steuert den Zugriff von anderen RIBs auf diese. Die Komponente der übergeordneten RIB ist normalerweise in den untergeordneten RIB-Builder eingebettet, um der untergeordneten RIB Zugriff auf die Abhängigkeiten der übergeordneten RIB zu gewähren.

Staatsverwaltung


Der Status der Anwendung wird hauptsächlich von RIBs verwaltet und dargestellt, die derzeit mit dem RIB-Baum verbunden sind. Wenn ein Benutzer beispielsweise in einer vereinfachten Co-Travel-Anwendung verschiedene Zustände durchläuft, fügt die Anwendung die folgenden RIBs hinzu und entfernt sie:



RIBs treffen staatliche Entscheidungen nur im Rahmen ihrer Zuständigkeit. Beispielsweise trifft die angemeldete RIB nur die Entscheidung, zwischen Zuständen wie Request und OnTrip zu wechseln. Er trifft keine Entscheidungen darüber, wie sich das System verhalten soll, wenn wir uns auf dem OnTrip-Bildschirm befinden.

Nicht alle Zustände können durch Hinzufügen oder Entfernen von RIBs gespeichert werden. Wenn sich beispielsweise die Benutzerprofileinstellungen ändern, wird die RIB nicht gebunden oder getrennt. In der Regel speichern wir diesen Status in den Flows unveränderlicher Modelle, die beim Teilewechsel erneut Werte senden. Beispielsweise kann der Benutzername in der ProfileDataStream-Datei gespeichert werden, die unter die Zuständigkeit von LoggedIn fällt. Nur Netzwerkantworten haben Schreibzugriff auf diesen Stream. Wir übergeben eine Schnittstelle, die Lesezugriff auf diese Threads im DI-Diagramm bietet.

Es gibt nichts in RIBs, was die ultimative Wahrheit für den Zustand von RIBs ist. Dies steht im Gegensatz zu der Tatsache, dass meisterhaftere Frameworks wie React bereits sofort verfügbar sind. Im Kontext jeder RIB können Sie Muster auswählen, die einen unidirektionalen Datenfluss ermöglichen, oder Sie können den Status der Geschäftslogik und des Ansichtsstatus vorübergehend von der Norm abweichen lassen, um effiziente Animationsframeworks für die Plattform zu nutzen.

Interaktion zwischen RIBs


Wenn Interactor eine Geschäftslogikentscheidung trifft, muss es möglicherweise die andere RIB über Ereignisse wie das Abschließen und Senden von Daten informieren. Das RIB-Framework enthält keine einzige Möglichkeit zum Übertragen von Daten zwischen RIBs. Diese Methode soll jedoch einige gängige Muster erleichtern.

Wenn die Verbindung zum untergeordneten RIB unterbrochen wird, übertragen wir diese Informationen in der Regel als Ereignisse im Rx-Stream. Oder die Daten können als Parameter in die build () -Methode der untergeordneten RIB aufgenommen werden. In diesem Fall wird dieser Parameter für die Lebensdauer des untergeordneten Elements invariant.



Wenn die Verbindung über den RIB-Baum zum übergeordneten RIB-Interaktor erfolgt, wird diese Verbindung über die Listener-Schnittstelle hergestellt, da die übergeordnete RIB einen längeren Lebenszyklus als die untergeordnete RIB haben kann. Ein übergeordnetes RIB oder ein Objekt in seinem DI-Diagramm implementiert eine Listener-Schnittstelle und platziert es in seinem DI-Diagramm, damit seine untergeordneten RIBs es aufrufen können. Die Verwendung dieser Vorlage zum Übertragen von Daten im Upstream-Modus, anstatt dass übergeordnete RIBs die Rx-Streams ihrer untergeordneten RIBs direkt abonnieren, hat mehrere Vorteile. Es verhindert Speicherlecks, ermöglicht das Schreiben, Testen und Verwalten von übergeordneten RIBs, ohne zu wissen, welche untergeordneten RIBs mit ihnen verbunden sind, und reduziert den Aufwand für das Anhängen / Trennen eines untergeordneten RIB. Rx-Streams oder Listener müssen sich bei dieser Methode zum Anhängen einer untergeordneten RIB nicht abmelden oder neu registrieren.



RIB Toolkit


Um eine reibungslose Implementierung der RIB-Architektur in Anwendungen sicherzustellen, haben die Uber-Ingenieure Tools erstellt, um die Verwendung von RIB zu vereinfachen und Invarianten zu verwenden, die durch die Implementierung der RIB-Architektur erstellt wurden. Der Quellcode dieses Toolkits war teilweise offen und wird in den Beispielen erwähnt (siehe rechter Teil - ca. Per.).

Das Toolkit, das derzeit Open Source ist, enthält:


Toolkit, für das Uber in Zukunft Open Source plant:

  • Statischer Analysator zur Vermeidung verschiedener Speicherlecks in RIB
  • RIB-Integration mit einem Speicherleckdetektor während der Programmausführung
  • (Android) Anmerkungsprozessoren zum einfacheren Testen
  • (Android) Statischer RxJava-Analysator, der RIBs Ansichten bietet, die gegenüber dem Hauptthread unverändert sind

PS


Wir bei sports.ru mochten den Ansatz der Uber-Ingenieure sehr, weil oft stießen wir auf alle architektonischen Probleme, die der Artikel beschrieb. Trotz der Angemessenheit weist RIB eine Reihe von Nachteilen auf, beispielsweise eine ziemlich hohe Schwelle für den Eintritt in die Architektur. Wir werden die Vor- und Nachteile der Architektur in den folgenden Artikeln genauer analysieren. Sie sind mindestens zwei Mal geplant - für iOS und Android. Für diejenigen, die jetzt in RIB eintauchen möchten, gibt es auf der Wiki-Seite rechts eine Spalte mit Englischunterricht. Ich selbst stelle fest, dass Architektur eindeutig in langen technischen Diskussionen entstanden ist und die Best Practices für die Erstellung von Architekturen für mobile Anwendungen gesammelt hat, die derzeit verfügbar sind. Und schließlich ein bisschen PR - wir bei sports.ru lieben auch technische Diskussionen, veranstalten oft technische Workshops für Kollegen, lernen regelmäßig neue Technologien und haben im Allgemeinen eine großartige Atmosphäre. Wenn Sie also Teil unseres Teams werden möchten, sind Sie herzlich willkommen !

Source: https://habr.com/ru/post/de424305/


All Articles