RESS - Neue Architektur für mobile Anwendungen



Im Gegensatz zum provokanten Titel ist dies keine neue Architektur, sondern ein Versuch, einfache und bewährte Praktiken in Newspeak zu übersetzen, das von der modernen Android-Community gesprochen wird

Einführung


In letzter Zeit ist es schmerzhaft geworden zu sehen, was in der Welt der Entwicklung für mobile Plattformen passiert. Die architektonische Astronautik boomt, jeder Hipster sieht es als seine Pflicht an, eine neue Architektur zu entwickeln und anstelle von zwei Zeilen eine einfache Aufgabe zu lösen und mehrere modische Frameworks einzufügen.

Diese Websites enthalten Tutorials zu trendigen Frameworks und anspruchsvollen Architekturen, aber es gibt nicht einmal eine bewährte Methode für Android REST-Clients. Dies ist zwar einer der häufigsten Anwendungsfälle. Ich möchte, dass die normale Herangehensweise an die Entwicklung auch an die Massen geht. Deshalb schreibe ich diesen Artikel

Warum sind bestehende Lösungen schlecht?


Im Großen und Ganzen ist das Problem von neuem MVP, VIPER und dergleichen genau dasselbe, ihre Autoren wissen nicht, wie sie entwerfen sollen. Und ihre Anhänger - umso mehr. Und deshalb verstehen sie wichtige und offensichtliche Dinge nicht. Und sie beschäftigen sich mit konventioneller Überentwicklung .

1. Die Architektur sollte einfach sein


Je einfacher desto besser. Dies macht es verständlicher, zuverlässiger und flexibler. Jeder Dummkopf kann sich neu komplizieren und eine Reihe von Abstraktionen machen, aber um es einfach zu machen, müssen Sie sorgfältig überlegen.

2. Überentwicklung ist schlecht


Sie müssen nur dann eine neue Abstraktionsebene hinzufügen, wenn die alte keine Probleme löst. Nach dem Hinzufügen einer neuen Ebene sollte das System verständlicher und weniger Code sein. Wenn Sie danach beispielsweise drei Dateien anstelle einer Datei hatten und das System komplizierter wurde, haben Sie einen Fehler gemacht und auf einfache Weise Müll geschrieben .

Fans von MVP schreiben beispielsweise in ihren Artikeln selbst im Klartext, dass MVP dumm zu einer erheblichen Komplikation des Systems führt. Und sie rechtfertigen es damit, dass es so flexibel und leichter zu warten ist . Wie wir jedoch aus Absatz 1 wissen, schließen sich diese Dinge gegenseitig aus.

Schauen Sie sich zum Beispiel VIPER zum Beispiel das Diagramm in diesem Artikel an.

Schema
Bild

Und das ist für jeden Bildschirm! Es tut meinen Augen weh. Ich sympathisiere besonders mit denen, die bei der Arbeit gegen ihren Willen damit umgehen müssen. Denjenigen, die es selbst vorgestellt haben, sympathisiere ich mit etwas anderen Gründen.

Neuer Ansatz


Hey, ich möchte auch einen modischen Namen . Daher wird die vorgeschlagene Architektur als RESS - R equest, E vent, S creen, S torage bezeichnet. Die Buchstaben und Namen sind so dumm detailliert, um ein lesbares Wort zu erhalten. Nun, um keine Verwechslung mit den bereits verwendeten Namen zu schaffen. Nun, mit REST im Einklang.

Machen Sie sofort eine Reservierung, diese Architektur ist für REST-Clients. Für andere Arten von Anwendungen wird es wahrscheinlich nicht funktionieren.



1. Lagerung


Data Warehouse (mit anderen Worten Modell, Repository). Diese Klasse speichert Daten und verarbeitet sie (speichert, lädt, fügt sie der Datenbank hinzu usw.), sowie alle Daten vom REST-Service werden zuerst hier abgerufen, analysiert und hier gespeichert.

2. Bildschirm


Der Anwendungsbildschirm, im Fall von Android, ist dies Ihre Aktivität. Mit anderen Worten, es ist ein normaler ViewController wie Apples MVC.

3. Anfrage


Die Klasse, die für das Senden von Anforderungen an den REST-Service sowie für das Empfangen von Antworten und das Benachrichtigen über die Antwort anderer Systemkomponenten verantwortlich ist.

4. Ereignis


Die Verbindung zwischen den anderen Komponenten. Beispielsweise sendet Request ein Ereignis über die Antwort des Servers an diejenigen, die ihn abonnieren. Und Storage sendet ein Ereignis über Datenänderungen.

Das Folgende ist ein Beispiel für eine vereinfachte Implementierung. Der Code wurde mit Annahmen geschrieben und nicht überprüft, sodass möglicherweise Syntaxfehler und Tippfehler auftreten

Anfrage
public class Request { public interface RequestListener { default void onApiMethod1(Json answer) {} default void onApiMethod2(Json answer) {} } private static class RequestTask extends AsyncTask<Void, Void, String> { public RequestTask(String methodName) { this.methodName = methodName; } private String methodName; @Override protected String doInBackground(Void ... params) { URL url = new URL(Request.serverUrl + "/" + methodName); HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection(); // ... //      // ... return result; } @Override protected void onPostExecute(String result) { // ... //  JSON  result // ... Requestr.onHandleAnswer(methodName, json); } } private static String serverUrl = "myserver.com"; private static List<OnCompleteListener> listeners = new ArrayList<>(); private static void onHandleAnswer(String methodName, Json json) { for(RequestListener listener : listeners) { if(methodName.equals("api/method1")) listener.onApiMethod1(json); else if(methodName.equals("api/method2")) listener.onApiMethod2(json); } } private static void makeRequest(String methodName) { new RequestTask(methodName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } public static void registerListener(RequestListener listener) { listeners.add(listener); } public static void unregisterListener(RequestListener listener) { listeners.remove(listener); } public static void apiMethod1() { makeRequest("api/method1"); } public static void onApiMethod2() { makeRequest("api/method2"); } } 


Lagerung
 public class DataStorage { public interface DataListener { default void onData1Changed() {} default void onData2Changed() {} } private static MyObject1 myObject1 = null; private static List<MyObject2> myObjects2 = new ArrayList<>(); public static void registerListener(DataListener listener) { listeners.add(listener); } public static void unregisterListener(DataListener listener) { listeners.remove(listener); } public static User getMyObject1() { return myObject1; } public static List<MyObject2> getMyObjects2() { return myObjects2; } public static Request.RequestListener listener = new Request.RequestListener() { private T fromJson<T>(Json answer) { // ... //    JSON // ... return objectT; } @Override public void onApiMethod1(Json answer) { myObject1 = fromJson(answer); for(RequestListener listener : listeners) listener.data1Changed(); } @Override public void onApiMethod2(Json answer) { myObject2 = fromJson(myObjects2); for(RequestListener listener : listeners) listener.data2Changed(); } }; } 


Bildschirm
 public class MyActivity extends Activity implements DataStorage.DataListener { private Button button1; private Button button2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); button1.setOnClickListener((View) -> { Request.apiMethod1(); }); button2.setOnClickListener((View) -> { Request.apiMethod2(); }); updateViews(); } @Override protected void onPause() { super.onPause(); DataStorage.unregisterListener(this); } @Override protected void onResume() { super.onResume(); DataStorage.registerListener(this); updateViews(); } private void updateViews() { updateView1(); updateView2(); } private void updateView1() { Object1 data = DataStorage.getObject1(); // ... //     // ... } private void updateView2() { List<Object2> data = DataStorage.getObjects2(); // ... //     // ... } @Override public void onData1Changed() { updateView1(); } @Override public void onData2Changed() { updateView2(); } } 


Die App
 public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); Request.registerListener(DataStorage.listener); } } 


Dieselbe Shemka, aber in Bezug auf RESS, zum Verständnis


Das funktioniert folgendermaßen: Wenn Sie auf die Schaltfläche klicken, zuckt die gewünschte Methode für die Anforderung. Die Anforderung sendet eine Anforderung an den Server, verarbeitet die Antwort und benachrichtigt zuerst DataStorage. DataStorage analysiert die Antwort und speichert die Daten zu Hause zwischen. Die Anforderung benachrichtigt dann den aktuell aktiven Bildschirm. Der Bildschirm nimmt Daten aus dem DataStorage und aktualisiert die Benutzeroberfläche.

Bildschirmzeichen und Abmeldungen von Mittelmäßigkeit in onResume bzw. onPause. Zusätzlich zu onResume wird auch die Benutzeroberfläche aktualisiert. Was gibt es? Benachrichtigungen kommen nur in der aktuell aktiven Aktivität, keine Probleme bei der Verarbeitung der Anforderung im Hintergrund oder beim Drehen der Aktivität. Die Aktivitäten werden immer auf dem neuesten Stand sein. Die Benachrichtigung erreicht die Hintergrundaktivität nicht und wenn Sie in den aktiven Zustand zurückkehren, werden die Daten aus dem DataStorage übernommen. Daher gibt es keine Probleme, wenn Sie den Bildschirm drehen und die Aktivität neu erstellen.

Und dafür reicht die Standard-API aus dem Android SDK.

Fragen und Antworten für zukünftige Kritik


1. Was ist der Gewinn?


Echte Einfachheit, Flexibilität, Wartbarkeit, Skalierbarkeit und ein Minimum an Abhängigkeiten. Sie können jederzeit einen bestimmten Teil des Systems komplizieren, wenn Sie dies benötigen. Zu viele Daten? Teilen Sie den DataStorage vorsichtig in mehrere Teile auf. Riesenservice-REST-API? Machen Sie ein paar Anfragen. Ist Listering zu einfach, umständlich und unmodern? Nimm den EventBus. Siehst du einen Friseursalon auf der HttpConnection schief an? Nehmen Sie Retrofit. Mutige Aktivität mit ein paar Fragmenten? Stellen Sie sich einfach vor, dass jedes Fragment Screen ist, oder teilen Sie es in Unterklassen auf.

2. AsyncTask ist ein schlechter Mann, nimm mindestens Retrofit!


Huh? Und welche Probleme verursacht es in diesem Code? Speicherlecks? Nein, hier speichert AsyncTask keine Links zu Aktivierungen, sondern nur einen Link zu einer statischen Methode. Die Antwort ist verloren? Nein, die Antwort kommt immer zum statischen DataStorage, bis die Anwendung beendet wird. Versuchen Sie, die Aktivität in der Pause zu aktualisieren? Nein, Benachrichtigungen werden nur in der aktiven Aktivität angezeigt.

Und wie kann Retrofit hier helfen? Schau einfach hier . Der Autor nahm RxJava, Retrofit und formt immer noch Krücken, um ein Problem zu lösen, das RESS einfach nicht hat.

3. Bildschirm ist der gleiche ViewController! Logik und Präsentation müssen getrennt werden, arrr!


Lass dieses Mantra schon fallen. Ein typischer Client für einen REST-Service ist eine große Ansicht für die Serverseite. Ihre gesamte Geschäftslogik besteht darin, den richtigen Status für eine Schaltfläche oder ein Textfeld festzulegen. Was wirst du dort teilen? Sagen wir, es wird einfacher zu warten sein? 3 Dateien mit 3 Tonnen Code pflegen, statt 1 Datei mit 1 Tonne einfacher? Ok Und wenn wir Aktivität mit 5 Fragmenten haben? Dies sind bereits 3 x (5 + 1) = 18 Dateien.

Die Trennung in Controller und View erzeugt in solchen Fällen einfach eine Menge bedeutungslosen Codes. Es ist Zeit, dies zuzugeben. Das Hinzufügen von Funktionen zu einem Projekt mit MVP macht besonders viel Spaß: Möchten Sie einen Button-Handler hinzufügen? Ok, korrigieren Sie Presenter, Activity und View-Interface. In RESS werde ich dazu ein paar Codezeilen in eine einzige Datei schreiben.

Aber in großen Projekten wächst ViewController furchtbar? Sie haben also keine großen Projekte gesehen. Ihr REST-Client für die nächste Site mit 5.000 Zeilen ist ein kleines Projekt, und 5.000 Zeilen dort nur, weil auf jedem Bildschirm 5 Klassen vorhanden sind. Wirklich große Projekte auf RESS mit über 100 Bildschirmen und mehreren Teams von 10 Personen fühlen sich großartig an. Machen Sie einfach ein paar Anfragen und Speicher. Und Bildschirm für fett gedruckte Bildschirme enthalten zusätzlichen Bildschirm für große Benutzeroberflächenelemente, z. B. dieselben Fragmente. Ein Projekt auf einem MVP der gleichen Größenordnung wird einfach in einer Reihe von Präsentatoren, Schnittstellen, Aktivierungen, Fragmenten und nicht offensichtlichen Verbindungen ertrinken. Und der Übergang zu VIPER wird in der Regel dazu führen, dass das gesamte Team eines Tages ausscheidet.

Fazit


Ich hoffe, dieser Artikel wird viele Entwickler ermutigen, ihre Ansichten zur Architektur zu überdenken, keine Abstraktionen zu erstellen und einfachere und bewährte Lösungen zu betrachten.

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


All Articles