Le 20 décembre 2016, les gars d'Uber Engineering ont publié
un article sur la nouvelle architecture (voici la
traduction de cet article sur le hub). Je vous présente la traduction de l'essentiel de la documentation.
À quoi sert l'architecture des semi-rigides?
RIBs est un cadre d'architecture multiplateforme d'Uber. Il a été conçu pour les grandes applications mobiles avec un grand nombre d'états intégrés.
En développant ce cadre, les ingénieurs Uber ont respecté les principes suivants:
- Prise en charge de la collaboration entre personnes développant sur différentes plateformes: la grande majorité des parties complexes des applications Uber sont similaires sur iOS et Android. Les RIB fournissent des modèles de développement communs pour Android et iOS. Lors de l'utilisation de RIB, les ingénieurs sur iOS et Android peuvent partager une architecture développée conjointement pour leurs fonctions.
- Minimisation des états et des décisions globaux : les changements d'état globaux peuvent conduire à des comportements imprévisibles et peuvent rendre impossible de savoir à quoi ces changements du code du programme entraîneront. L'architecture basée sur les RIB encourage les états encapsulés dans une hiérarchie profonde de RIB bien isolés pour éviter les problèmes avec les états globaux.
- Testabilité et isolation: les classes doivent être simples afin de pouvoir écrire des tests unitaires, et aussi avoir une raison d'être isolées (se référant à SRP ). Les classes RIB individuelles ont des responsabilités différentes (par exemple, routage, logique métier, logique de présentation, création d'autres classes RIB). De plus, la logique du RIB parent est fondamentalement distincte de la logique du RIB enfant. Cela facilite le test des classes RIB et réduit la dépendance entre les composants du système.
- Outils de développement productif: emprunter des modèles architecturaux non triviaux peut entraîner des problèmes de croissance de l'application s'il n'existe pas d'outils fiables pour prendre en charge l'architecture. L'architecture RIBs est livrée avec des outils IDE pour créer du code, une analyse statique et une intégration d'exécution, ce qui améliore la productivité des développeurs dans les grandes et petites équipes.
- Le principe d'ouverture-proximité: les développeurs, si possible, devraient ajouter de nouvelles fonctions sans changer le code existant. Lors de l'utilisation de RIB, la mise en œuvre de cette règle peut être observée à plusieurs endroits. Par exemple, vous pouvez attacher ou créer un RIB enfant complexe qui nécessite des dépendances sur son RIB parent, avec peu ou pas de changement dans le RIB parent.
- Structuration autour de la logique métier: la structure de la logique métier d'une application ne doit pas refléter strictement la structure de l'interface utilisateur. Par exemple, pour faciliter l'animation et les performances d'une vue, la hiérarchie des vues peut être plus petite que la hiérarchie RIB. Ou, une seule fonction RIB peut contrôler l'apparence de trois vues qui apparaissent à différents endroits de l'interface utilisateur.
- Contrats exacts: les exigences doivent être déclarées à l'aide de contrats vérifiés lors de la compilation. Une classe ne doit pas être compilée si ses propres dépendances, ainsi que les dépendances invitées, ne sont pas satisfaites. L'architecture RIBs utilise ReactiveX pour représenter les dépendances invitées, les systèmes d'injection de dépendance ( DI ) de type sécurisé pour représenter les dépendances de classe et de nombreuses autres fonctionnalités DI pour aider à créer des invariants de données.
Éléments composants RIB
Si vous avez déjà travaillé avec l'architecture
VIPER , les classes qui composent le RIB vous seront familières. Les RIB sont généralement constitués des éléments suivants, chacun étant implémenté dans sa propre classe:

Interactractor
Interactor contient la logique métier. Dans cette classe, les notifications Rx sont souscrites, les décisions sont prises pour changer l'état, stocker les données et attacher les RIB enfants.
Toutes les opérations effectuées dans Interactor doivent être limitées à son cycle de vie. Uber a créé une boîte à outils pour garantir que la logique métier n'est exécutée qu'avec une interaction active. Cela empêche les Interactors d'être désactivés, mais les abonnements Rx se déclenchent toujours et provoquent des mises à jour indésirables de la logique métier ou de l'état de l'interface utilisateur.
Routeur
Le routeur surveille les événements d'Interactor et convertit ces événements en attachant et détachant des RIB enfants. Le routeur existe pour trois raisons simples:
- Le routeur existe en tant qu'objet passif, ce qui simplifie le test de la logique complexe des Interacteurs sans avoir à créer de stubs pour les Interacteurs enfants ou d'une autre manière pour prendre soin de leur existence.
- Les routeurs créent une couche supplémentaire d'abstraction entre les parents et les enfants. Cela rend la communication synchrone entre les Interacteurs un peu plus complexe et encourage l'utilisation de communications Rx au lieu de communications directes entre RIB.
- Les routeurs contiennent une logique de routage simple et répétitive qui serait autrement implémentée dans les Interactors. Le portage de ce code passe-partout vers les routeurs aide les Interacteurs à être plus petits et plus concentrés sur la logique métier RIB principale.
Constructeur
Builder est nécessaire pour créer des instances pour toutes les classes incluses dans le RIB, ainsi que pour créer des instances de Builders pour les RIB enfants.
La mise en évidence de la logique de création de classe dans Builder ajoute la prise en charge de la possibilité de créer des stubs dans iOS et rend le reste du code RIB insensible aux détails de l'implémentation DI. Builder est la seule partie du RIB qui doit être au courant du système DI utilisé dans le projet. En implémentant un autre Builder, vous pouvez réutiliser le reste du code RIB dans le projet à l'aide d'un mécanisme DI différent.
Présentateur
Presenter est une classe sans état qui traduit un modèle commercial en modèle de présentation et vice versa. Il peut être utilisé pour faciliter le test des transformations de vue de modèle. Cependant, cette traduction est souvent si banale qu'elle ne justifie pas la création d'une classe Presenter distincte. Si le présentateur n'est pas fait, la traduction des modèles de vue devient la responsabilité de la vue (contrôleur) ou de l'interacteur.
Vue (contrôleur)
View crée et met à jour l'interface utilisateur. Cela inclut la création et l'organisation des composants d'interface, la gestion de l'interaction utilisateur, le remplissage des composants d'interface utilisateur avec des données et l'animation. View est conçu pour être aussi "stupide" (passif) que possible. Ils affichent simplement des informations. En général, ils ne contiennent aucun code pour lequel des tests unitaires doivent être écrits.
Composant
Le composant est utilisé pour gérer les dépendances RIB. Il aide le Builder à instancier les autres classes qui composent le RIB. Le composant permet d'accéder aux dépendances externes nécessaires à la création du RIB, ainsi qu'à ses propres dépendances créées par le RIB lui-même, et contrôle leur accès à partir d'autres RIB. Le composant du RIB parent est généralement intégré dans le RIB-Builder enfant pour fournir au RIB enfant un accès aux dépendances du RIB parent.
Gestion de l'État
L'état de l'application est principalement géré et représenté par des RIB actuellement connectés à l'arborescence RIB. Par exemple, lorsqu'un utilisateur passe par différents états dans une application de co-voyage simplifiée, l'application attache et détache les RIB suivants:

Les RIB ne prennent des décisions étatiques que dans le cadre de leurs compétences. Par exemple, le LoggedIn RIB prend uniquement la décision de faire la transition entre des états tels que Request et OnTrip. Il ne prend aucune décision sur le comportement du système lorsque nous sommes sur l'écran OnTrip.
Tous les états ne peuvent pas être enregistrés en ajoutant ou en supprimant des RIB. Par exemple, lorsque les paramètres du profil utilisateur changent, le RIB ne se lie ni ne se déconnecte. En règle générale, nous enregistrons cet état dans les flux de modèles immuables, qui renvoient des valeurs lors du changement de pièces. Par exemple, le nom d'utilisateur peut être stocké dans le fichier ProfileDataStream, qui relève de la compétence LoggedIn. Seules les réponses réseau ont un accès en écriture à ce flux. Nous passons une interface qui fournit un accès en lecture à ces threads dans le graphique DI.
Il n'y a rien dans les semi-rigides qui soit la vérité ultime pour l'état des semi-rigides. Cela contraste avec le fait que des cadres plus maîtrisés tels que React sont déjà fournis dès le départ. Dans le contexte de chaque RIB, vous pouvez choisir des modèles qui facilitent le flux de données unidirectionnel, ou vous pouvez laisser l'état de la logique métier et afficher l'état s'écarter temporairement de la norme afin de tirer parti des cadres d'animation efficaces pour la plate-forme.
Interaction entre les semi-rigides
Lorsque Interactor prend une décision de logique métier, il peut être nécessaire d'informer l'autre RIB des événements, tels que l'achèvement et l'envoi de données. Le cadre RIB n'inclut aucun moyen unique de transférer des données entre les RIB. Cependant, cette méthode est conçue pour faciliter certains modèles courants.
En règle générale, si la connexion descend vers le RIB enfant, nous transmettons ces informations en tant qu'événements dans le flux Rx. Ou les données peuvent être incluses en tant que paramètre dans la méthode
build () du RIB enfant, auquel cas ce paramètre devient un invariant pour la durée de vie de l'enfant.

Si la connexion remonte l'arborescence RIB vers l'interacteur RIB parent, cette connexion est établie via l'interface d'écoute, car le RIB parent peut avoir un cycle de vie plus long que le RIB enfant. Un RIB parent, ou un objet sur son graphe DI, implémente une interface d'écoute et le place sur son graphe DI afin que ses RIB enfants puissent l'appeler. L'utilisation de ce modèle pour transférer des données en amont plutôt que d'avoir des RIB parents directement abonnés aux flux Rx de leurs RIB enfants présente plusieurs avantages. Il empêche les fuites de mémoire, vous permet d'écrire, de tester et de maintenir les RIB parents sans savoir quels RIB enfants leur sont attachés, et réduit également la quantité d'agitation requise pour attacher / détacher un RIB enfant. Les flux Rx ou les écouteurs n'ont pas besoin de se désinscrire ou de se réinscrire avec cette méthode de connexion d'un RIB enfant.

Boîte à outils RIB
Pour garantir une mise en œuvre fluide de l'architecture RIB dans les applications, les ingénieurs Uber ont créé des outils pour simplifier l'utilisation de RIB et utiliser des invariants créés par la mise en œuvre de l'architecture RIB. Le code source de cette boîte à outils était partiellement ouvert et est mentionné dans les
exemples (voir la partie droite - environ Per.).
La boîte à outils, qui est actuellement open source, comprend:
- Générateur de code: plugins IDE pour créer de nouveaux RIB et tests associés.
- NPE Static Analyzer (Android): NullAway est un outil d'analyse statique qui vous permet d'oublier NullPointerExceptions.
- Analyseur automatique de placement statique (Android): empêche les fuites de mémoire les plus courantes dans RIB.
Boîte à outils pour laquelle Uber prévoit d'ouvrir l'open source à l'avenir:
- Analyseur statique pour éviter diverses fuites de mémoire dans RIB
- Intégration RIB avec un détecteur de fuite de mémoire pendant l'exécution du programme
- (Android) Processeurs d'annotation pour des tests plus faciles
- (Android) Analyseur statique RxJava qui fournit aux RIB des vues inchangées par rapport au thread principal
PS
Chez sports.ru, nous
avons vraiment aimé l'approche des ingénieurs Uber, car plusieurs fois, nous avons rencontré tous les problèmes architecturaux décrits dans l'article. Malgré le caractère raisonnable, RIB présente un certain nombre d'inconvénients, par exemple, un seuil assez élevé pour entrer dans l'architecture. Nous analyserons plus en détail les avantages et les inconvénients de l'architecture dans les articles suivants, ils sont prévus au moins deux - pour iOS et pour Android. Pour ceux qui veulent plonger dans RIB en ce moment, il y a une colonne sur la page wiki à droite qui a des leçons d'anglais. À moi seul, je note que l'architecture est clairement née de longues discussions techniques et a rassemblé les meilleures pratiques pour la construction d'architectures pour les applications mobiles qui sont actuellement disponibles. Et enfin, un peu de relations publiques - nous à
sports.ru aimons aussi les discussions techniques,
organisons souvent des ateliers techniques pour les collègues, étudions régulièrement les nouvelles technologies et, en général, nous avons une bonne ambiance. Donc, si vous souhaitez faire partie de notre équipe -
bienvenue !