Clean Architecture: comment refléter les changements de la couche de données dans L'interface utilisateur
j'essaie de faire un design basé sur le Oncle Bob est Propre Architecture dans Android.
Le problème:
je voudrais résoudre est de savoir comment faire les changements générés dans un dépôt pour être reflétés dans d'autres parties de l'application, comme d'autres dépôts ou vues.
exemple
j'ai conçu un exemple TRÈS simplifié pour cet exemple. Veuillez noter que les interfaces limites ont été enlevé pour garder les diagrammes petits.
Imaginez une application qui affiche une liste de vidéos (avec titre, thumnail et like count), en cliquant sur une vidéo vous pouvez voir le détail (là vous pouvez aimer/n'aimer pas la vidéo).
en Outre, l'application dispose d'un système de statistiques qui compte le nombre de vidéos que l'utilisateur aimé ou détesté.
Les principales classes pour cette application pourrait être:
Pour les Vidéos partie/module:
Pour les Stats de la partie/module:
cible
maintenant, imaginez que vous vérifiez vos statistiques, puis naviguez dans la liste des vidéos, ouvrez le détail d'une, et cliquez sur le bouton comme.
après, est envoyé au serveur, il y a plusieurs éléments des applications qui doivent être au courant des changements:
- De bien sûr la vue de détail, devraient être mis à jour avec les modifications (ce qui peut être fait si les rappels donc pas de problème)
- la liste des vidéos doit mettre à jour le nombre de "j'aime" pour la vidéo donnée
- Le " StatsRepository souhaitez mettre à jour/invalider les caches, après le vote d'une nouvelle vidéo
- si la liste des statistiques est visible (imaginez un écran divisé) , elle devrait également afficher les statistiques mises à jour (ou au moins recevoir l'événement pour une nouvelle requête). de données)
La Question
Quels sont les modèles communs pour résoudre ce type de communication? Merci de formuler votre réponse aussi complète que vous pouvez, en précisant d'où les événements sont générés, comment obtiennent-ils propagés par l'app, etc.
Remarque: des primes seront accordées pour compléter les réponses
2 réponses
Publier / S'Abonner
typiquement, pour la communication n:m (les expéditeurs n peuvent envoyer un message aux récepteurs m, alors que tous les expéditeurs et récepteurs ne se connaissent pas) vous utiliserez un publier / s'abonner pattern. Il y a beaucoup de bibliothèques qui implémentent un tel style de communication, pour Java il y a par exemple un EventBus mise en œuvre dans la Goyave bibliothèque. Pour la communication in-app ces bibliothèques sont généralement appelés EventBus ou EventManager et envoyer/recevoir événements.
Evénements Du Domaine
Supposons que vous avez maintenant créé un événement VideoRatedEvent
, qui indique qu'un utilisateur a aimé ou détesté une vidéo.
Ces types d'événements sont appelés Evénements Du Domaine. La classe event est un simple POJO et pourrait ressembler à ceci:
class VideoRatedEvent {
/** The video that was rated */
public Video video;
/** The user that triggered this event */
public User user;
/** True if the user liked the video, false if the user disliked the video */
public boolean liked;
}
événements de répartition
maintenant chaque fois que vos utilisateurs aiment ou n'aiment pas une vidéo, vous devez envoyer un VideoRatedEvent
.
Avec de la goyave, vous il suffit de passer un objet événement instancié à object à EventBus.post(myVideoRatedEvent)
.
Idéalement, les événements sont générés dans vos objets de domaine et sont expédiés dans le cadre de la transaction persistante (voir ce post pour plus de détails).
Cela signifie que lorsque votre état de modèle de domaine persiste, les événements sont envoyés.
Écouteurs D'Événements
dans votre application, tous les composants affectés par un événement peuvent maintenant écouter les événements du domaine.
Dans votre exemple, le VideoDetailView
ou StatsRepository
peut-être des écouteurs d'événement pour l' VideoRatedEvent
.
Bien sûr, vous aurez besoin de les enregistrer à L'EventBus De Goyave avec EventBus.register(Object)
.
ce sont mes 5cents personnels et peut-être pas assez étroitement liés à votre exemple de "L'architecture propre".
J'essaie habituellement de forcer une sorte de MVC sur les activités et les fragments d'androïdes et d'utiliser publier/souscrire pour la communication. En tant que composants, j'ai des classes de modèles qui gèrent la logique commerciale et l'état des données. Ces méthodes de changement de données ne doivent être appelées que par les classes de controller qui sont généralement la classe activity et aussi handles session state. Je utilisez des fragments pour gérer différentes parties de l'application et des vues sous ces fragments (évidemment). Tous les fragments s'inscrivent à un ou plusieurs sujets. J'utilise mon propre service de diffusion de données simple qui traite différents sujets, prend des messages des éditeurs enregistrés et les relaie à tous les abonnés. (influencé en partie par les OMG DDS mais beaucoup plus primitif) une application simple n'aurait qu'un seul sujet par exemple "principal".
Chaque partie de la vue l'interaction (touche etc) est traitée par son fragment d'abord. Le fragment peut potentiellement changer quelques choses sans envoyer de notifications. Par exemple: changer le sous-rang des éléments de données rendus si le reste de l'application n'a pas besoin de savoir/réagir. Sinon, le fragment publie une ViewRequest (...) contenant les paramètres nécessaires au DDS.
Le DDS diffuse ce message et atteint un contrôleur. Cela peut tout simplement être l'activité principale ou d'une instance spécifique du contrôleur. Il ne doit y avoir qu'un seul contrôleur pour que la requête ne soit traitée qu'une seule fois. Le controller a essentiellement une longue liste de codes de traitement des requêtes. Lorsqu'une demande arrive le contrôleur appelle à la logique métier dans le modèle. Le contrôleur gère également d'autres choses liées à la vue comme l'organisation de la vue (onglets) ou le démarrage des dialogues pour l'entrée de l'utilisateur (dossier de réécriture?) et d'autres choses que le modèle n'est pas censé connaître mais des influences (jeter nouveau Nooverritepermissionexception ())
Une fois les modifications de modèle effectuées, le contrôleur décide si une notification de mise à jour doit être envoyée. (en général, il n'). De cette façon, les classes modèles n'ont pas besoin d'écouter ou d'envoyer des messages et ne s'occupent que de la logique et de l'état cohérent de l'entreprise. La notification de mise à jour est diffusée et reçue par les fragments qui exécutent alors "updateFromModel()".
Effets:
Les commandes sont globales. Tout ViewRequest ou tout autre type de requête peut être envoyé de n'importe où le DDS peut être accédé. Les Fragments n'ont pas à fournir une classe d'écouteur et aucune instance supérieure n'a à implémenter des écouteurs pour leurs fragments instanciés. Si un nouveau fragment ne nécessite pas de nouvelles requêtes, il peut être ajouté sans aucune modification aux classes de controller.
Les classes modèles n'ont pas besoin d'être au courant de la communication. Il peut être assez difficile de maintenir un état cohérent et de traiter toutes les données gestion. Aucune manipulation de message ou de l'état de session n'est nécessaire. Toutefois, le modèle pourrait ne pas être protégé contre les appels maliques de la vue. Mais il s'agit d'un problème général qui ne peut pas être évité si le modèle doit donner des références à un moment donné. Si votre application est très bien avec un modèle qui ne passe que des copies/données plates est possible. Mais à un moment donné, L'Adaptateur de tableau a simplement besoin d'accéder aux bitmaps qu'il est censé dessiner dans le gridview. Si vous n'avez pas les moyens de faire des copies, vous ont le risque de "voir fait un appel changeant au modèle". Les différents champs de bataille...
Les appels de mise à jour peuvent être trop simples. Si la mise à jour d'un fragment est coûteuse (OpenGL fragment recharger les textures...) vous souhaitez avoir des informations plus détaillées de mise à jour. Le contrôleur pourrait envoyer une notification plus détaillée mais il ne devrait pas être en mesure de savoir quelles parties du modèle ont exactement changé. Envoyer des notes de mise à jour à partir du modèle est moche. Non seulement l' modèle doit mettre en œuvre la messagerie, mais il obtient également très chaotique avec des notifications mixtes. Le contrôleur peut diviser un peu les notifications de mise à jour et d'autres en utilisant des sujets. Par exemple: un sujet spécifique pour modifier vos ressources vidéo. De cette façon, les fragments peuvent décider des sujets auxquels ils souscrivent. En dehors de cela, vous voulez avoir un modèle qui peut être interrogé pour des valeurs changées. Timestamp etc. J'ai une application où l'utilisateur dessine des formes sur la toile. Ils sont rendus à bitmaps et sont utilisés comme textures dans une vue OpenGL. Je ne veux certainement pas recharger des textures à chaque fois que "updateFromModel()" est appelé dans le fragment GLview.
La Dépendance À La Règle:
Probablement pas respecté tout le temps. Si le controller gère un commutateur tab, il peut simplement appeler "seletTab()" sur un TabHost et donc avoir une dépendance aux cercles extérieurs. Vous pouvez en faire un message, mais c'est toujours une dépendance logique. Si la partie controller doit organiser certains éléments de la vue (afficher l'onglet Image-Editeur-fragment-automatiquement après avoir chargé une image via l'onglet image-gallery-fragmen) vous ne pouvez pas éviter complètement les dépendances. Peut-être que vous pouvez le faire en modelant viewstate et que vos parties de vue s'organisent à partir de viewstate.currentUseCase ou smth comme ça. Mais si vous avez besoin d'un contrôle global sur la vue de votre application, vous aurez des problèmes avec cette règle de dépendance. Que faire si vous essayez d'enregistrer certaines données et de votre modèle de demande pour remplacer l'autorisation? Vous avez besoin de créer une sorte d'INTERFACE utilisateur. Encore une dépendance. Vous pouvez envoyer un message à la vue et espérer qu'un dialogue la reprenne. S'il existe dans le monde extrêmement modulaire décrit à votre lien.
Entités:
sont les classes modèles dans mon approche. C'est assez proche du lien que vous avez fourni.
Cas D'Utilisation:
Je n'ai pas ceux explicitement modélisés pour le moment. Atm je travaille sur les éditeurs de jeu vidéo actif. Dessiner des formes dans un fragment, appliquer des valeurs d'ombrage dans un autre fragment, sauvegarder/charger dans un fragment Galler, exporter vers un atlas de texture dans un autre ... des trucs comme ça. J'ajouterais les cas D'utilisation comme une sorte de sous-ensemble de requêtes. Fondamentalement un cas D'utilisation comme un ensemble de règles qui demandent dans quel ordre sont autorisés/requis/attendus/interdits, etc. Je voudrais les construire comme des transactions de sorte qu'un cas D'utilisation peut continuer à progresser, peut être terminé, peut être annulé et peut-être même retranché. E. g. un cas d'utilisation définirait l'ordre de sauvegarde d'une nouvelle image dessinée. Y compris l'affichage d'une boîte de dialogue pour demander la permission de réécrire et de revenir en arrière si la permission n'est pas donnée ou si le délai est atteint. Mais les cas D'utilisation sont définis de différentes façons. Certaines applications ont un seul Cas d'Utilisation pour une heure de active interaction de l'utilisateur, certaines applications ont 50 Cas d'Utilisation de l'argent d'un guichet automatique. ;)
Adaptateurs D'Interface:
Ici, cela devient un peu compliqué. Pour moi, cela semble être niveau extrêmement élevé pour les applications android. "L'anneau des adaptateurs D'Interface contient toute l'architecture MVC d'une interface graphique". Je ne peux pas vraiment envelopper ma tête autour de cela. Peut-être que vous créez des applications beaucoup plus compliquées que moi.
Les cadres et les Pilotes:
Pas sûr que penser de celui-ci. "Le web est un détail, la base de données est un détail..."et le graphique contient" UI " dans cet anneau aussi. Trop pour mon petit tête
Permet de vérifier les autres ", affirme"
indépendant des cadres. L'architecture ne dépend pas de l'existence d'une bibliothèque de fonction laden logiciel. Cela vous permet d'utiliser de tels cadres comme outils, plutôt que d'avoir à ancrer votre système dans leurs contraintes limitées.
HM ouais bien, si vous dirigez votre propre architecture c'est ce que vous obtenez.
Testable. Les règles commerciales peuvent être testé sans L'interface utilisateur, la base de données, le serveur Web, ou tout autre élément externe.
Comme dans my approach model, les classes ne connaissent ni les contrôleurs, ni les vues, ni le message qui passe. On peut tester la cohérence d'état avec seulement ces classes seules.
indépendante de L'IU. L'INTERFACE utilisateur peut changer facilement, sans changer le reste du système. Une interface utilisateur Web pourrait être remplacée par une interface utilisateur console, par exemple, sans changer l'entreprise. règle.
Encore un peu exagéré pour android n'est-ce pas? L'indépendance oui. Dans ma démarche, vous pouvez ajouter ou supprimer des fragments tant qu'ils ne nécessitent pas explicite manipulation quelque part plus haut. Mais remplacer une interface utilisateur Web par une interface utilisateur console et faire tourner le système comme avant est un rêve humide de détraqués de l'architecture. Certains éléments de l'INTERFACE sont partie intégrante du service fourni. Bien sûr, je peux facilement échanger le fragment de dessin sur toile pour un fragment de dessin sur console, ou la photo classique fragment pour "prendre une photo avec la console' fragment, mais cela ne signifie pas que l'application fonctionne toujours. Techniquement ses beaux dans ma démarche. Si vous implémentez un lecteur vidéo de console ascii, vous pouvez y afficher les vidéos et aucune autre partie de l'application ne s'en souciera nécessairement. Cependant, il se peut que l'ensemble des requêtes que le contrôleur supporte ne s'aligne pas bien avec la nouvelle interface utilisateur de la console ou qu'un cas D'utilisation ne soit pas conçu pour l'ordre dans lequel une vidéo doit être accédée via une console. interface. La vue n'est pas toujours l'esclave de présentation sans importance que beaucoup de gourous de l'architecture aiment la voir comme.
indépendante de la base de données. Vous pouvez changer Oracle ou SQL Server, pour Mongo, BigTable, CouchDB, ou autre chose. Vos règles d'affaires ne sont pas liées à la base de données.
Ouais, et alors? En quoi est-ce directement lié à votre architecture? Utilisez les bons adaptateurs et l'abstraction et vous pouvez avoir que dans un monde bonjour App.
Indépendant de tout organisme externe. En fait, vos règles d'affaires ne savent tout simplement rien du tout sur le monde extérieur.
Même ici. Si vous voulez un code indépendant modularisé, alors écrivez-le. Difficile de dire quelque chose de précis à ce sujet.