Caractéristiques de l'utilisation de la bibliothèque RxJs dans un système bancaire en ligne

Présentation


La conception d'un système bancaire en ligne moderne est une tâche assez compliquée. Dans le même temps, un certain nombre de tâches de développement de la partie cliente de l'application sont associées au processus de traitement d'une grande quantité de données provenant presque simultanément de plusieurs sources d'information. Les données du système bancaire à distance (RBS), les services de messagerie instantanée, divers services d'information doivent être reçus et traités en temps réel ici et maintenant. Pour résoudre des problèmes de ce type, les méthodes de programmation réactive sont largement utilisées aujourd'hui.

Le terme "programmation réactive" au sens large désigne une telle organisation de l'application, dans laquelle la propagation des changements dans le système se produit à la suite du traitement des états des flux de données. Les problèmes importants de cette méthode sont la simplicité de présentation des flux d'informations et la possibilité de répondre aux erreurs qui se produisent lors du traitement asynchrone des résultats de présentation.

Au sens strict, la programmation réactive de l'interface utilisateur Web peut signifier l'utilisation d'outils de développement standard, tels que la bibliothèque RxJs. Cette bibliothèque fournit une représentation discrète des séquences de données à l'aide de l'objet observable, qui sert de source d'informations entrant dans l'application à certains intervalles.

Considérez les caractéristiques de l'utilisation de la bibliothèque sur l'exemple de la conception de l'interface Web d'une banque en ligne pour les petites entreprises. Lors du développement de l'interface utilisateur, nous avons utilisé la plateforme Google Angular 6 avec la bibliothèque RxJs intégrée version 6.

Tâches de conception d'interface utilisateur réactives


Pour l'utilisateur, la plupart des opérations de la banque Internet se résument souvent à trois étapes:

  • sélectionner l'opération nécessaire dans la liste, par exemple, rembourser un prêt ou réapprovisionner un compte;
  • remplir partiellement le formulaire correspondant (les détails du paiement sont remplis automatiquement par le nom de l'organisation ou le nom du bénéficiaire entré par l'utilisateur);
  • confirmation automatisée des opérations par SMS ou signatures électroniques.

Du point de vue du développeur, la mise en œuvre de ces étapes comprend la solution des tâches suivantes:

  • vérifier l'état du système RBS, assurer la pertinence des données sur les opérations de la liste;
  • traitement asynchrone des flux de données lors du remplissage d'un formulaire, y compris les données saisies par l'utilisateur et reçues des services de messagerie d'informations (nom, TIN et BIC de la banque par exemple);
  • validation du formulaire rempli;
  • sauvegarde automatique des données dans le formulaire.

Vérification de l'état du système RBS


Le processus d'obtention de données pertinentes à partir du système RB, par exemple des informations sur une ligne de crédit ou le statut d'un ordre de paiement, comprend deux étapes:

  • vérifier l'état de disponibilité des données;
  • recevoir des données mises à jour.

Pour vérifier l'état actuel des données, des demandes sont faites à l'API du système avec un certain laps de temps et jusqu'à ce qu'une réponse soit reçue sur l'état de préparation des données

Il existe plusieurs réponses possibles pour le système RB:

  • {vide: vrai} - les données ne sont pas encore prêtes;
  • les données mises à jour peuvent être reçues par le client;

{ empty: false // -   } 

  • une erreur.

En conséquence, la réception des données pertinentes s'effectue sous la forme de:

 const MIN_TIME = 2000; const MAX_TIME = 60000; const EXP_BASE = 1.4; request() //     .pipe( expand((response, index) => { const delayTime = Math.min(MIN_TIME * Math.pow(EXP_BASE, index), MAX_TIME); return response.empty ? request().pipe(delay(delayTime)) : EMPTY; }), last() ) .subscribe((response) => { /** -  */ }); 


Analysons étape par étape:

  1. Nous envoyons une demande. demande ()
  2. La réponse va s'élargir. Expand est une instruction RxJS qui répète récursivement le code dans son bloc pour chaque prochaine alerte pour l'Observable interne et externe, jusqu'à ce que le thread annonce sa réussite. Par conséquent, afin de terminer le flux, il est nécessaire de renvoyer un tel observable afin qu'il n'y en ait pas un seul suivant - VIDE.
  3. Si la réponse est venue {empty: true}, alors nous faisons une deuxième demande après un certain délai (delayTime). Afin de ne pas surcharger le serveur de requêtes, nous augmentons l'intervalle de temps pour le ping à chaque nouvelle requête.
  4. Si lors de la prochaine requête, quelque chose d'autre est venu en réponse, arrêtez le ping (retournez VIDE) et renvoyez le résultat de la dernière requête à l'abonné (opérateur last ()).
  5. Après avoir reçu la réponse, nous prenons le résultat et le traitons. Un objet du formulaire entrera en souscription:

 { empty: false // -   } 


Formes réactives


Envisagez la tâche de concevoir un formulaire Web réactif d'un document de paiement à l'aide de la bibliothèque ReactiveForms du cadre angulaire.

Les trois classes de base de la bibliothèque FormControl, FormGroup et FormArray vous permettent d'utiliser une description déclarative des champs de formulaire, de définir les valeurs initiales des champs et également de définir des règles de validation pour chaque champ:

 this.myForm = new FormGroup({ name: new FormControl('', Validators.required), //          surname: new FormControl('') }); 


Pour les formulaires avec un grand nombre de champs, il est d'usage d'utiliser le service FormBuilder, qui vous permet de les créer en utilisant du code plus compact

 this.myForm = this.fb.group({ name: ['', Validators.required], surname: '' }); 


Après avoir créé le formulaire dans le modèle de la page d'ordre de paiement, il suffit de spécifier un lien vers le formulaire myForm, ainsi que les noms de ses champs nom et prénom

 <form [formGroup]="myForm"> <label>Name: <input formControlName="name"> </label> <label>Surname: <input formControlName="surname"> </label> </form> 


La conception qui en résulte vous permet de générer et de suivre tout flux d'informations passant par les champs du formulaire à la suite de la saisie de l'utilisateur et basé sur la logique métier de l'application. Pour ce faire, il vous suffit de vous abonner aux événements générés par l'observateur asynchrone du formulaire ValueChanges

 this.myForm.valueChanges .subscribe(value => { … //     }) 


Supposons que la logique métier définit les exigences pour remplir automatiquement les détails de la destination de paiement lorsque l'utilisateur entre le NIF du destinataire ou le nom de l'organisation. Le code de traitement des données saisies par l'utilisateur dans le NIF / nom de l'organisation ressemblera à:

 this.payForm.valueChanges .pipe( mergeMap(value => this.getRequisites(value)) //       ) .subscribe(requisites => { this.patchFormByRequisites(requisites) //        }) 


Validation


Les validateurs se présentent sous deux formes:

  • synchrone;
  • asynchrone.

Nous rencontrons régulièrement des valideurs synchrones - ce sont des fonctions qui vérifient les données saisies lorsque vous travaillez avec le champ. En termes de formes réactives:
"Un valideur synchrone est une fonction qui prend des formes de contrôle et renvoie une valeur vraie s'il y a une erreur et une fausse sinon."

 function customValidator(control) { return isInvalid(control.value) ? { code: "mistake", message: "smth wents wrong" } : null; } 


Nous mettrons en place un validateur qui vérifiera si l'utilisateur a indiqué dans le formulaire une série de documents, si le passeport était précédemment indiqué comme type de document d'identification:

 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; } 


Ici, nous nous référons également au formulaire parent et en l'utilisant, nous obtenons la valeur d'un autre champ. Il était possible de retourner juste vrai comme une erreur, mais dans ce cas, il a été décidé de faire autrement. Vous pouvez intercepter ces messages d'erreur dans le champ d'erreurs d'un contrôle ou d'un formulaire. Si le champ a plusieurs validateurs, vous pouvez spécifier exactement lequel des validateurs n'a pas pu afficher le message d'erreur souhaité ou ajuster la validation des autres champs.

Le validateur sera ajouté au formulaire comme suit:

  this.documentForm = this.fb.group({ docType: ['', Validators.required], seria: ['', requredSeria], number: '' }); 


Prêt à l'emploi, plusieurs validateurs couramment rencontrés sont également disponibles. Tous sont représentés par des méthodes statiques de la classe Validators. Il existe également des méthodes de composition des validateurs.
L'inexactitude d'un champ entraîne immédiatement la nullité de l'ensemble du formulaire. Cela peut être utilisé lorsque vous devez désactiver un certain bouton OK, si le formulaire contient au moins un champ non valide. Ensuite, tout se résume à vérifier une condition «myform.invalid», qui retournera vrai si le formulaire n'est pas valide.

Le validateur asynchrone a une différence - le type de la valeur de retour. La valeur véridique ou fausse doit être transmise dans une promesse ou dans un observable.

Chaque contrôle ou chaque formulaire a un statut (mySuperForm.status), qui peut être "VALIDE", "INVALIDE", "DÉSACTIVÉ". Étant donné que lors de l'utilisation de validateurs asynchrones, il peut ne pas être clair dans quel état le formulaire est actuellement, il y a un statut spécial de "EN ATTENTE". Grâce à cette condition (mySuperForm.status === "EN ATTENTE"), vous pouvez afficher le préchargeur ou effectuer tout autre style de formulaire.

Sauvegarde automatique


Le développement de logiciels bancaires (logiciels) implique de travailler avec différents documents standards. Par exemple, il s'agit de formulaires de demande ou de questionnaires, qui peuvent comprendre des dizaines de champs obligatoires. Lorsque vous travaillez avec de tels documents volumineux, la prise en charge de la sauvegarde automatique est requise pour plus de commodité pour l'utilisateur, de sorte que si vous perdez votre connexion Internet ou d'autres problèmes techniques, les données que l'utilisateur a saisies précédemment restent dans la version provisoire sur le serveur.

Voici les principaux aspects de la procédure d'enregistrement automatique pour l'architecture client-serveur:

  1. Les demandes de sauvegarde doivent être traitées par le serveur dans l'ordre dans lequel les modifications ont été apportées. Si vous envoyez immédiatement une demande à chaque modification, vous ne pouvez pas garantir qu'une demande antérieure ne viendra pas ensuite et n'écrasera pas les nouvelles modifications.
  2. Il n'est pas nécessaire d'envoyer un grand nombre de requêtes au serveur jusqu'à ce que l'utilisateur ait fini d'entrer, il suffit de le faire par chronométrage.
  3. Si plusieurs modifications ont été apportées avec un délai relativement important et que la demande des premières modifications n'est pas encore retournée, il n'est pas nécessaire d'envoyer des demandes pour chaque modification suivante immédiatement après le retour de la première demande. Vous ne pouvez prendre que ces derniers, afin de ne pas envoyer de données non pertinentes.

Le premier cas peut être facilement traité à l'aide de l'opérateur concatMap . Le deuxième cas sera résolu sans aucun problème en utilisant debounceTime . La logique du troisième peut être décrite comme:

 const lastRequest$ = new BehaviorSubject(null); //   queue$.subscribe(lastRequest$); queue$ .pipe( debounceTime(1000), exaustMap(request$ => request$.pipe( //  ,     map(response => ({request$, response})), //       catchError(() => of(null) //   ) ) .subscribe(({request$, response}) => { if (lastRequest$.value !== request$) { queue$.next(lastRequest$.value); //     } }); 


Il reste dans saveQueue $ pour envoyer une demande. Notez la présence de l'opérateur exaustMap au lieu de concatMap. Cet opérateur est nécessaire pour ignorer toutes les notifications de l'Observable externe jusqu'à ce que celle de l'Interne ait terminé son observation ("compilée"). Mais dans notre cas, si lors de la demande il y aura une file d'attente de nouvelles notifications, nous devons prendre la dernière et jeter le reste. exaustMap supprimera tout, y compris le dernier. Par conséquent, nous enregistrons la dernière notification dans BehaviorSubject et dans l'abonnement, si la demande terminée en cours est différente de la dernière, nous jetterons à nouveau la dernière demande dans la file d'attente.

Il convient également de noter l'ignorance des erreurs lors des requêtes, implémentées à l'aide de l' instruction catchError . Vous pouvez écrire une gestion des erreurs plus complexe avec une notification pour l'utilisateur qu'une erreur s'est produite lors de l'enregistrement. Mais son essence est que lorsqu'une erreur se produit dans le flux, le flux ne doit pas être fermé, comme c'est le cas pour les notifications d'erreur et complètes.

Conclusion


Le niveau de développement actuel des technologies de programmation réactive utilisant la bibliothèque RxJS vous permet de créer des applications client à part entière pour les systèmes bancaires en ligne sans frais de main-d'œuvre supplémentaires pour organiser l'interaction avec les interfaces très chargées des systèmes bancaires à distance.

La première connaissance de RxJS peut effrayer même un développeur expérimenté qui est confronté aux «subtilités» de la bibliothèque qui implémentent le modèle de conception «Observer». Mais, surmontant peut-être ces difficultés, à l'avenir, RxJS deviendra un outil indispensable pour résoudre les problèmes de traitement asynchrone de flux de données hétérogènes en temps réel.

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


All Articles