📜 ⬆️ ⬇️

Features of using the RxJs library in the online banking system

Introduction


Designing a modern online banking system is quite a challenge. At the same time, a number of client application development tasks are associated with the process of processing a large amount of data coming almost simultaneously from several information sources. Data from the system of remote banking services (RBS), instant messaging services, various information services should be received and processed in real time here and now. To solve problems of this kind today are widely used methods of reactive programming.

The term “Reactive Programming” in a broad sense means such an organization of the operation of an application in which the propagation of changes in the system occurs as a result of processing the states of data streams. Important issues with this method are the simplicity of the presentation of information flows and the ability to respond to errors that arise in the process of asynchronous processing of the presentation results.

In the narrow sense, reactive web UI programming can mean using off-the-shelf developer tools, for example, the RxJs library. This library provides a discrete representation of data sequences using the Observable object, which serves as a source of information that enters an application at regular intervals.

Consider the features of using the library on the example of designing a web interface of an online bank for a small business. When developing the UI, we used Google’s Angular 6 platform with a built-in RxJs version 6 library.
')

Reactive UI Design Tasks


For a user, performing most of the operations in the Internet bank is often reduced to three stages:


From the standpoint of the developer, the implementation of these stages includes the following tasks:


Checking the status of the RB System


The process of obtaining relevant data from the RBS system, for example, information about the credit line or the status of the payment order, includes two stages:


To check the current state of the data, requests are made to the API of the system with a certain period of time and until the answer is received on the availability of data

Perhaps several answers to the RB System:


{ empty: false // -   } 


As a result, the actual data is obtained in the form of:

 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) => { /** -  */ }); 


Let us analyze in steps:

  1. We send the request. request ()
  2. The answer goes to expand. Expand is an RxJS statement that recursively repeats code within its block for each alert next for internal and external Observable, until the thread reports its successful completion. Therefore, in order to complete the stream, you need to return such an Observable so that there is not one next - EMPTY.
  3. If the answer is {empty: true}, then we make a second request after a certain time delay (delayTime). In order not to overload the server with requests, we increase the time interval for the ping with each new request.
  4. If during the next request, something else came in response, then we stop pinging (return EMPTY) and return the result of the last request to the subscriber (last () operator).
  5. After receiving the answer, we take the result and process it. An object of the following type will fall into subspace:

 { empty: false // -   } 


Reactive forms


Consider the task of designing a reactive web form of a payment document using the ReactiveForms library from the Angular framework.

The three base classes of the FormControl, FormGroup and FormArray libraries allow you to use a declarative description of form fields, set initial field values, and also set validation rules for each field:

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


For forms with a large number of fields, it is common to use the FormBuilder service, which allows you to create them using a more compact code.

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


After creating the form in the template of the payment order page, it is enough to indicate a link to the form myForm, as well as the names of its fields name and surname

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


The resulting design allows you to generate and track any flow of information passing through the form fields as a result of user input, and based on the business logic of the application. To do this, just subscribe to the events generated by the asynchronous observer of the form ValueChanges.

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


Suppose business logic defines the requirements for automatically filling in the details of the addressee of a payment when the user enters the TIN of the recipient or the name of the organization. The code for processing data entered by the user in the INN / organization name fields will be:

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


Validation


Validators are of two types:


We encounter synchronous validators on a regular basis - these are functions that check the entered data when working with a field. In terms of reactive forms:
"The synchronous validator is a function that takes control forms and returns a truthy value if there is an error and falsy otherwise."

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


We will implement a validator that will check whether the user indicated a series of documents in the form, if a passport was previously specified as the type of identity document:

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


Here we also turn to the parent form and use it to get the value of another field. As an error, it was possible to return just true, but in this case it was decided to do otherwise. These error messages can be intercepted in the errors field of a control or form. If the field has several validators, you can specify which of the validators failed to display the desired error message or correct the validation of other fields.

The validator will be added to the form as follows:

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


Out of the box, there are also several commonly used validators. All of them are represented by static methods of the class Validators. There are also methods for composition validators.
The incorrectness of one field leads immediately to the invalidity of the whole form. This can be used in case you need to deactivate a certain OK button if there is at least one invalid field in the form. Then it all comes down to checking one condition “myform.invalid”, which returns true if the form is invalid.

The asynchronous validator has one difference - the type of the return value. The value truthy or falsy must be passed in promise or in Observable.

Each control or each form has a status (mySuperForm.status), which can be “VALID”, “INVALID”, “DISABLED”. Since, when using asynchronous validators, it may not be clear what condition the form is currently in, there is a special “PENDING” status. Thanks to this condition (mySuperForm.status === “PENDING”), you can display a preloader or make any other stylization of the form.

Auto save


Development of banking software (software) involves working with various sample documents. For example, these are application forms or questionnaires, which may consist of dozens of required fields. When working with such large documents, for additional convenience of the user, support for autosave is required so that when the connection to the Internet is lost or other technical problems, the data that the user previously entered remains in the draft on the server.

Here are the main aspects of the autosave procedure for the client-server architecture:

  1. Save requests must be processed by the server in the order in which the changes were made. If you send a request to every change right away, then you cannot guarantee that an earlier request will not come next and will not overwrite the new changes.
  2. You do not need to send a large number of requests to the server until the user has finished entering; it is enough to do this by timing.
  3. If several changes were made with a relatively large delay, and the request for the first changes has not yet returned, there is no need to send requests for each subsequent change immediately after the return of the first request. You can only take the latter in order not to send out irrelevant data.

With the first case, you can easily cope with the operator concatMap . The second case will be solved without any problems using debounceTime . The logic of the third can be described as:

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


It remains to saveQueue $ send request. Note the presence of the exaustMap operator instead of concatMap. This operator is necessary to ignore all notifications of an external Observable, while the internal one has not completed its observation (“zakkomplitsya”). But in our case, if during the request there will be a queue of new notifications, we must take the last one and discard the rest. exaustMap will drop everything, including the last one. Therefore, we save the last notification in the BehaviorSubject, and in the subscription, if the currently processed request is different from the last - we throw the last request into the queue again.

Also worth noting is the ignoring of errors in the course of queries, implemented using the catchError operator. You can write more complex error handling with notifications for the user that an error occurred while saving. But its essence is that when an error occurs in the stream, the stream should not close, as it does with the error and complete alerts.

Conclusion


The current level of development of reactive programming technologies using the RxJS library allows you to create full-fledged client applications for online banking systems without additional labor costs for organizing interaction with high-loaded interfaces of RBS systems.

The first acquaintance with RxJS can scare away even an experienced developer who is faced with the library's “intricacies” implementing the “Observer” design pattern. But, perhaps, having overcome these difficulties, RxJS will later become an indispensable tool for solving problems of asynchronous processing of heterogeneous data streams in real time.

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


All Articles