Einführung
Das Entwerfen eines modernen Online-Banking-Systems ist eine ziemlich komplizierte Aufgabe. Gleichzeitig sind eine Reihe von Aufgaben zur Entwicklung des Client-Teils der Anwendung mit dem Prozess der Verarbeitung einer großen Datenmenge verbunden, die fast gleichzeitig aus mehreren Informationsquellen stammt. Daten aus dem Remote-Banking-System (RBS), Instant Messaging-Diensten und verschiedenen Informationsdiensten sollten hier und jetzt in Echtzeit empfangen und verarbeitet werden. Um Probleme dieser Art zu lösen, werden heute häufig reaktive Programmiermethoden eingesetzt.
Der Begriff "reaktive Programmierung" im weiteren Sinne bedeutet eine solche Organisation der Anwendung, bei der die Ausbreitung von Änderungen im System infolge der Verarbeitung der Zustände von Datenströmen erfolgt. Wichtige Probleme bei dieser Methode sind die einfache Darstellung von Informationsflüssen und die Möglichkeit, auf Fehler zu reagieren, die während der asynchronen Verarbeitung von Darstellungsergebnissen auftreten.
Im engeren Sinne kann reaktive Web-UI-Programmierung die Verwendung von Standard-Entwicklertools wie der RxJs-Bibliothek bedeuten. Diese Bibliothek bietet eine diskrete Darstellung von Datensequenzen unter Verwendung des Observable-Objekts, das in bestimmten Intervallen als Informationsquelle für die Anwendung dient.
Betrachten Sie die Funktionen der Verwendung der Bibliothek am Beispiel des Entwurfs der Weboberfläche einer Online-Bank für kleine Unternehmen. Bei der Entwicklung der Benutzeroberfläche haben wir die Google Angular 6-Plattform mit der integrierten RxJs-Bibliothek Version 6 verwendet.
Reaktive UI-Entwurfsaufgaben
Für den Benutzer sind die meisten Vorgänge in der Internetbank häufig dreistufig:
- Auswählen der erforderlichen Operation aus der Liste, z. B. Rückzahlung eines Kredits oder Auffüllen eines Kontos;
- teilweises Ausfüllen des entsprechenden Formulars (Zahlungsdetails werden automatisch mit dem Namen der Organisation oder dem vom Benutzer eingegebenen Namen des Zahlungsempfängers ausgefüllt);
- Automatische Bestätigung von Vorgängen mithilfe von SMS-Nachrichten oder elektronischen Signaturen.
Aus Sicht des Entwicklers umfasst die Implementierung dieser Phasen die Lösung der folgenden Aufgaben:
- Überprüfung des Status des RBS-Systems, Sicherstellung der Relevanz von Daten zu Vorgängen in der Liste;
- asynchrone Verarbeitung von Datenflüssen beim Ausfüllen eines Formulars, einschließlich Daten, die vom Benutzer eingegeben und von Informationsnachrichtendiensten empfangen wurden (z. B. Name, TIN und BIC der Bank);
- Validierung des ausgefüllten Formulars;
- Automatisches Speichern von Daten im Formular.
Überprüfen des Status des RBS-Systems
Der Prozess zum Abrufen relevanter Daten aus dem RB-System, z. B. Informationen über eine Kreditlinie oder den Status eines Zahlungsauftrags, umfasst zwei Phasen:
- Überprüfen des Datenverfügbarkeitsstatus;
- aktualisierte Daten empfangen.
Um den aktuellen Status der Daten zu überprüfen, werden innerhalb eines bestimmten Zeitraums und bis eine Antwort über die Bereitschaft der Daten eingeht, Anforderungen an die API des Systems gestellt
Für das RB-System gibt es mehrere mögliche Antworten:
- {leer: wahr} - die Daten sind noch nicht fertig;
- aktualisierte Daten können vom Kunden empfangen werden;
{ empty: false
Infolgedessen erfolgt der Empfang der relevanten Daten in Form von:
const MIN_TIME = 2000; const MAX_TIME = 60000; const EXP_BASE = 1.4; request()
Lassen Sie uns Schritt für Schritt analysieren:
- Wir senden eine Anfrage. Anfrage ()
- Die Antwort wird erweitert. Expand ist eine RxJS-Anweisung, die den Code innerhalb ihres Blocks für jede nächste Benachrichtigung für das interne und externe Observable rekursiv wiederholt, bis der Stream seinen erfolgreichen Abschluss meldet. Um den Stream zu vervollständigen, ist es daher erforderlich, ein solches Observable zurückzugeben, damit es kein einziges nächstes gibt - LEER.
- Wenn die Antwort {leer: wahr} kam, stellen wir nach einer bestimmten Zeitverzögerung (delayTime) eine zweite Anfrage. Um den Server nicht mit Anforderungen zu überlasten, erhöhen wir das Zeitintervall für den Ping mit jeder neuen Anforderung.
- Wenn während der nächsten Anfrage etwas anderes als Antwort kam, hören wir auf zu pingen (LEER zurückgeben) und geben das Ergebnis der letzten Anfrage an den Abonnenten zurück (last () -Operator).
- Nach Erhalt der Antwort nehmen wir das Ergebnis und verarbeiten es. Ein Objekt des Formulars wird abonniert:
{ empty: false
Reaktive Formen
Betrachten Sie die Aufgabe, ein reaktives Webformular eines Zahlungsdokuments mithilfe der ReactiveForms-Bibliothek aus dem Angular-Framework zu entwerfen.
Mit den drei Basisklassen der Bibliothek FormControl, FormGroup und FormArray können Sie eine deklarative Beschreibung der Formularfelder verwenden, die Anfangswerte der Felder festlegen und außerdem Validierungsregeln für jedes Feld festlegen:
this.myForm = new FormGroup({ name: new FormControl('', Validators.required),
Für Formulare mit einer großen Anzahl von Feldern ist es üblich, den FormBuilder-Dienst zu verwenden, mit dem Sie sie mit kompakterem Code erstellen können
this.myForm = this.fb.group({ name: ['', Validators.required], surname: '' });
Nach dem Erstellen des Formulars in der Vorlage auf der Zahlungsauftragsseite reicht es aus, einen Link zum myForm-Formular sowie die Namen der Felder Vor- und Nachname anzugeben
<form [formGroup]="myForm"> <label>Name: <input formControlName="name"> </label> <label>Surname: <input formControlName="surname"> </label> </form>
Das resultierende Design ermöglicht es Ihnen, jeden Informationsfluss zu generieren und zu verfolgen, der als Ergebnis von Benutzereingaben und basierend auf der Geschäftslogik der Anwendung durch die Formularfelder fließt. Abonnieren Sie dazu einfach die Ereignisse, die vom asynchronen Beobachter des ValueChanges-Formulars generiert wurden
this.myForm.valueChanges .subscribe(value => { …
Angenommen, die Geschäftslogik definiert die Anforderungen für das automatische Ausfüllen der Details des Zahlungsziels, wenn der Benutzer die TIN des Empfängers oder den Namen der Organisation eingibt. Der Code für die Verarbeitung der vom Benutzer in die TIN / den Namen der Organisation eingegebenen Daten sieht folgendermaßen aus:
this.payForm.valueChanges .pipe( mergeMap(value => this.getRequisites(value))
Validierung
Validatoren gibt es in zwei Formen:
Wir begegnen regelmäßig synchronen Validatoren - dies sind Funktionen, die die eingegebenen Daten bei der Arbeit mit dem Feld überprüfen. In Bezug auf reaktive Formen:
"Ein synchroner Validator ist eine Funktion, die Kontrollformen annimmt und einen wahrheitsgemäßen Wert zurückgibt, wenn ein Fehler vorliegt und andernfalls ein Fehler vorliegt."
function customValidator(control) { return isInvalid(control.value) ? { code: "mistake", message: "smth wents wrong" } : null; }
Wir werden einen Validator implementieren, der prüft, ob der Benutzer in dem Formular eine Reihe von Dokumenten angegeben hat, wenn der Pass zuvor als Typ des Ausweisdokuments angegeben wurde:
function requredSeria(control) { const docType = control.parent.get("docType"); let error = null; if (docType && docType.value === "passport" && !control.value) { error = { code: "wrongSeria", message: " " } } return error; }
Hier verweisen wir auch auf das übergeordnete Formular und erhalten damit den Wert eines anderen Feldes. Es war möglich, nur true als Fehler zurückzugeben, aber in diesem Fall wurde beschlossen, etwas anderes zu tun. Sie können diese Fehlermeldungen im Fehlerfeld eines Steuerelements oder Formulars abfangen. Wenn das Feld mehrere Validatoren enthält, können Sie genau angeben, welcher der Validatoren die gewünschte Fehlermeldung nicht anzeigen oder die Validierung anderer Felder anpassen konnte.
Der Validator wird dem Formular wie folgt hinzugefügt:
this.documentForm = this.fb.group({ docType: ['', Validators.required], seria: ['', requredSeria], number: '' });
Standardmäßig sind auch mehrere häufig vorkommende Validatoren verfügbar. Alle von ihnen werden durch statische Methoden der Validators-Klasse dargestellt. Es gibt auch Methoden zum Erstellen von Validatoren.
Die Unrichtigkeit eines Feldes führt sofort zur Ungültigkeit des gesamten Formulars. Dies kann verwendet werden, wenn Sie eine bestimmte OK-Schaltfläche deaktivieren müssen, wenn das Formular mindestens ein ungültiges Feld enthält. Dann kommt es darauf an, eine Bedingung „myform.invalid“ zu überprüfen, die true zurückgibt, wenn das Formular ungültig ist.
Der asynchrone Validator hat einen Unterschied - den Typ des Rückgabewerts. Der Wahrheits- oder Falschwert muss in einem Versprechen oder in einem Observable übergeben werden.
Jedes Steuerelement oder jedes Formular hat einen Status (mySuperForm.status), der "GÜLTIG", "UNGÜLTIG", "DEAKTIVIERT" sein kann. Da bei Verwendung von asynchronen Validatoren möglicherweise nicht klar ist, in welchem Status sich das Formular gerade befindet, gibt es den Sonderstatus "PENDING". Dank dieser Bedingung (mySuperForm.status === "PENDING") können Sie den Preloader anzeigen oder ein anderes Formular-Styling vornehmen.
Automatisch speichern
Die Entwicklung von Bankensoftware (Software) umfasst die Arbeit mit verschiedenen Standarddokumenten. Dies sind beispielsweise Antragsformulare oder Fragebögen, die aus Dutzenden von erforderlichen Feldern bestehen können. Wenn Sie mit solch umfangreichen Dokumenten arbeiten, ist für zusätzlichen Benutzerkomfort eine automatische Speicherunterstützung erforderlich. Wenn Sie Ihre Internetverbindung oder andere technische Probleme verlieren, bleiben die zuvor eingegebenen Daten in der Entwurfsversion auf dem Server.
Hier sind die Hauptaspekte der automatischen Speicherprozedur für die Client-Server-Architektur:
- Speicheranforderungen müssen vom Server in der Reihenfolge verarbeitet werden, in der die Änderungen vorgenommen wurden. Wenn Sie sofort eine Anfrage an jede Änderung senden, können Sie nicht garantieren, dass eine frühere Anfrage nicht als nächstes kommt und neue Änderungen nicht überschreibt.
- Es ist nicht erforderlich, eine große Anzahl von Anforderungen an den Server zu senden, bis der Benutzer die Eingabe abgeschlossen hat. Es reicht aus, dies durch Timing zu tun.
- Wenn mehrere Änderungen mit einer relativ großen Verzögerung vorgenommen wurden und die Anforderung für die ersten Änderungen noch nicht zurückgegeben wurde, müssen keine Anforderungen für jede nachfolgende Änderung sofort nach der Rückgabe der ersten Anforderung gesendet werden. Sie können nur letzteres nehmen, um keine irrelevanten Daten zu senden.
Der erste Fall kann einfach mit dem Operator
concatMap behandelt werden. Der zweite Fall wird ohne Probleme mit
debounceTime gelöst. Die Logik des dritten kann beschrieben werden als:
const lastRequest$ = new BehaviorSubject(null);
Es bleibt in saveQueue $, um eine Anfrage zu senden. Beachten Sie das Vorhandensein des Operators
exaustMap anstelle von concatMap. Dieser Operator muss alle Benachrichtigungen des externen Observable ignorieren, bis der interne seine Beobachtung abgeschlossen hat ("kompiliert"). In unserem Fall müssen wir jedoch die letzte nehmen und den Rest verwerfen, wenn während der Anforderung eine Warteschlange mit neuen Benachrichtigungen angezeigt wird. exaustMap lässt alles fallen, auch das letzte. Daher speichern wir die letzte Benachrichtigung in BehaviorSubject. Wenn sich die aktuell abgeschlossene Anforderung von der letzten unterscheidet, wird die letzte Anforderung im Abonnement erneut in die Warteschlange gestellt.
Es ist auch erwähnenswert, Fehler bei Abfragen zu ignorieren, die mit der
catchError-Anweisung implementiert wurden. Sie können eine komplexere Fehlerbehandlung mit einer Benachrichtigung für den Benutzer schreiben, dass beim Speichern ein Fehler aufgetreten ist. Im Wesentlichen sollte der Stream jedoch nicht geschlossen werden, wenn ein Fehler im Stream auftritt, wie dies bei Fehlern und vollständigen Benachrichtigungen der Fall ist.
Fazit
Der heutige Entwicklungsstand reaktiver Programmiertechnologien unter Verwendung der RxJS-Bibliothek ermöglicht es Ihnen, vollwertige Clientanwendungen für Online-Banking-Systeme ohne zusätzliche Arbeitskosten für die Organisation der Interaktion mit hoch belasteten Schnittstellen von Remote-Banking-Systemen zu erstellen.
Die erste Bekanntschaft mit RxJS kann sogar einen erfahrenen Entwickler abschrecken, der mit den „Feinheiten“ der Bibliothek konfrontiert ist, die das Entwurfsmuster „Observer“ implementieren. Um diese Schwierigkeiten möglicherweise zu überwinden, wird RxJS in Zukunft ein unverzichtbares Werkzeug sein, um die Probleme der asynchronen Verarbeitung heterogener Datenströme in Echtzeit zu lösen.