1. Introdução
Projetar um sistema bancário on-line moderno é uma tarefa bastante complicada. Ao mesmo tempo, várias tarefas para desenvolver a parte do cliente do aplicativo estão associadas ao processo de processamento de uma grande quantidade de dados provenientes quase simultaneamente de várias fontes de informação. Dados do sistema bancário remoto (RBS), serviços de mensagens instantâneas e vários serviços de informações devem ser recebidos e processados em tempo real, aqui e agora. Para resolver problemas desse tipo, os métodos de programação reativa são amplamente utilizados atualmente.
O termo "programação reativa", no sentido amplo, significa uma organização do aplicativo, na qual a propagação de alterações no sistema ocorre como resultado do processamento dos estados dos fluxos de dados. Questões importantes com esse método são a simplicidade de apresentar fluxos de informações e a possibilidade de responder a erros que ocorrem durante o processamento assíncrono dos resultados da apresentação.
Em um sentido restrito, a programação reativa da interface do usuário da Web pode significar o uso de ferramentas de desenvolvedor disponíveis no mercado, como a biblioteca RxJs. Esta biblioteca fornece uma representação discreta de seqüências de dados usando o objeto Observable, que serve como fonte de informações que entram no aplicativo em determinados intervalos.
Considere os recursos do uso da biblioteca no exemplo do design da interface da web de um banco online para pequenas empresas. Ao desenvolver a interface do usuário, usamos a plataforma Google Angular 6 com a versão 6 da biblioteca RxJs integrada.
Tarefas de design de interface do usuário reativas
Para o usuário, a maioria das operações no Internet Bank geralmente se resume a três estágios:
- selecionando a operação necessária na lista, por exemplo, reembolsando um empréstimo ou reabastecendo uma conta;
- preenchimento parcial do formulário correspondente (os detalhes do pagamento são preenchidos automaticamente pelo nome da organização ou pelo nome do beneficiário inserido pelo usuário);
- confirmação automática de operações usando mensagens SMS ou assinaturas eletrônicas.
Do ponto de vista do desenvolvedor, a implementação desses estágios inclui a solução das seguintes tarefas:
- verificar o status do sistema RBS, garantindo a relevância dos dados sobre as operações na lista;
- processamento assíncrono de fluxos de dados ao preencher um formulário, incluindo dados inseridos pelo usuário e recebidos de serviços de mensagens informativas (nome, NIF e BIC do banco, por exemplo);
- validação do formulário preenchido;
- salvamento automático de dados no formulário.
Verificando o status do sistema RBS
O processo de obtenção de dados relevantes do sistema RB, por exemplo, informações sobre uma linha de crédito ou o status de uma ordem de pagamento, inclui dois estágios:
- verificação do status de disponibilidade de dados;
- recebendo dados atualizados.
Para verificar o estado atual dos dados, solicitações são feitas à API do sistema com um certo período de tempo até que uma resposta seja recebida sobre a prontidão dos dados
Existem várias respostas possíveis para o sistema RB:
- {empty: true} - os dados ainda não estão prontos;
- dados atualizados podem ser recebidos pelo cliente;
{ empty: false
Como resultado, o recebimento de dados relevantes é realizado sob a forma de:
const MIN_TIME = 2000; const MAX_TIME = 60000; const EXP_BASE = 1.4; request()
Vamos analisar passo a passo:
- Enviamos uma solicitação. request ()
- A resposta vai se expandir. Expandir é uma instrução RxJS que repete recursivamente o código dentro de seu bloco para cada próxima notificação do Observable interno e externo, até que o fluxo relate sua conclusão bem-sucedida. Portanto, para concluir o fluxo, é necessário retornar um Observable para que não haja um próximo - VAZIO.
- Se a resposta veio {empty: true}, fazemos uma segunda solicitação após um certo atraso (delayTime). Para não sobrecarregar o servidor com solicitações, aumentamos o intervalo de tempo do ping a cada nova solicitação.
- Se, durante a próxima solicitação, surgir outra resposta, paramos de executar ping (return EMPTY) e retornamos o resultado da última solicitação ao assinante (last () operator).
- Depois de receber a resposta, pegamos o resultado e o processamos. Um objeto do formulário entra na inscrição:
{ empty: false
Formas reativas
Considere a tarefa de criar um formulário da Web reativo de um documento de pagamento usando a biblioteca ReactiveForms da estrutura Angular.
As três classes base da biblioteca FormControl, FormGroup e FormArray permitem usar uma descrição declarativa dos campos do formulário, definir os valores iniciais dos campos e também definir regras de validação para cada campo:
this.myForm = new FormGroup({ name: new FormControl('', Validators.required),
Para formulários com um grande número de campos, é habitual usar o serviço FormBuilder, que permite criá-los usando código mais compacto
this.myForm = this.fb.group({ name: ['', Validators.required], surname: '' });
Depois de criar o formulário no modelo da página da ordem de pagamento, basta especificar um link para o formulário myForm, assim como os nomes dos campos, nome e sobrenome
<form [formGroup]="myForm"> <label>Name: <input formControlName="name"> </label> <label>Surname: <input formControlName="surname"> </label> </form>
O design resultante permite gerar e rastrear qualquer fluxo de informações que passa pelos campos do formulário como resultado da entrada do usuário e com base na lógica de negócios do aplicativo. Para fazer isso, basta se inscrever nos eventos gerados pelo observador assíncrono do formulário ValueChanges
this.myForm.valueChanges .subscribe(value => { …
Suponha que a lógica comercial defina os requisitos para preencher automaticamente os detalhes do destino de pagamento quando o usuário digitar o NIF do destinatário ou o nome da organização. O código para processar os dados inseridos pelo usuário no NIF / nome da organização será semelhante a:
this.payForm.valueChanges .pipe( mergeMap(value => this.getRequisites(value))
Validação
Os validadores vêm em duas formas:
Encontramos validadores síncronos regularmente - são funções que verificam os dados inseridos ao trabalhar com o campo. Em termos de formas reativas:
"Um validador síncrono é uma função que assume formas de controle e retorna um valor verdadeiro, se houver um erro e falsidade."
function customValidator(control) { return isInvalid(control.value) ? { code: "mistake", message: "smth wents wrong" } : null; }
Implementaremos um validador que verificará se o usuário indicou no formulário uma série de documentos, se o passaporte foi indicado anteriormente como o tipo de documento de identificação:
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; }
Aqui também nos referimos ao formulário pai e, usando-o, obtemos o valor de outro campo. Foi possível retornar apenas verdadeiro como um erro, mas, neste caso, foi decidido fazer o contrário. Você pode capturar essas mensagens de erro no campo de erros de um controle ou formulário. Se o campo tiver vários validadores, você poderá especificar exatamente quais validadores falharam ao exibir a mensagem de erro desejada ou ajustar a validação de outros campos.
O validador será adicionado ao formulário da seguinte maneira:
this.documentForm = this.fb.group({ docType: ['', Validators.required], seria: ['', requredSeria], number: '' });
Fora da caixa, vários validadores comumente encontrados também estão disponíveis. Todos eles são representados por métodos estáticos da classe Validators. Existem também métodos para compor validadores.
A incorreta de um campo leva imediatamente à invalidez de todo o formulário. Isso pode ser usado quando você precisar desativar um certo botão OK, se o formulário tiver pelo menos um campo inválido. Tudo se resume a verificar uma condição "myform.invalid", que retornará true se o formulário for inválido.
O validador assíncrono tem uma diferença - o tipo do valor de retorno. O valor verdadeiro ou falso deve ser passado em uma promessa ou em um Observável.
Cada controle ou cada formulário possui um status (mySuperForm.status), que pode ser "VÁLIDO", "INVÁLIDO", "DESATIVADO". Como ao usar validadores assíncronos, pode não estar claro em que estado o formulário está no momento, há um status especial de "PENDENTE". Graças a essa condição (mySuperForm.status === “PENDING”), você pode exibir o pré-carregador ou criar qualquer outro estilo de formulário.
Salvamento automático
O desenvolvimento de software bancário (software) envolve trabalhar com vários documentos padrão. Por exemplo, esses são formulários ou questionários de inscrição, que podem consistir em dezenas de campos obrigatórios. Ao trabalhar com esses documentos volumosos, o suporte para salvamento automático é necessário para maior comodidade do usuário, para que, se você perder sua conexão com a Internet ou outros problemas técnicos, os dados que o usuário inseriu anteriormente permanecerão na versão de rascunho no servidor.
Aqui estão os principais aspectos do procedimento de salvamento automático da arquitetura cliente-servidor:
- As solicitações de salvamento devem ser processadas pelo servidor na ordem em que as alterações foram feitas. Se você enviar imediatamente uma solicitação para cada alteração, não poderá garantir que uma solicitação anterior não venha a seguir e não substituirá novas alterações.
- Não é necessário enviar um grande número de solicitações ao servidor até que o usuário termine de entrar, basta fazer isso por tempo.
- Se várias alterações foram feitas com um atraso relativamente grande e a solicitação para as primeiras alterações ainda não foi retornada, não será necessário enviar solicitações para cada alteração subsequente imediatamente após o retorno da primeira solicitação. Você pode usar apenas o último, para não enviar dados irrelevantes.
O primeiro caso pode ser tratado facilmente usando o operador
concatMap . O segundo caso será resolvido sem problemas usando o
debounceTime . A lógica do terceiro pode ser descrita como:
const lastRequest$ = new BehaviorSubject(null);
Permanece em saveQueue $ para enviar uma solicitação. Observe a presença do operador
exaustMap em vez de concatMap. Este operador é necessário ignorar todas as notificações do Observable externo até que o interno conclua sua observação ("compilado"). Porém, no nosso caso, se durante a solicitação houver uma fila de novas notificações, devemos pegar a última e descartar o restante. O exaustMap soltará tudo, incluindo o último. Portanto, salvamos a última notificação no BehaviorSubject e, na assinatura, se a solicitação concluída atual for diferente da última, lançaremos a última solicitação na fila novamente.
Também vale a pena notar os erros de
ignição durante as consultas, implementadas usando a
instrução catchError . Você pode escrever um tratamento de erro mais complexo com uma notificação para o usuário de que ocorreu um erro ao salvar. Mas sua essência é que, quando ocorre um erro no fluxo, o fluxo não deve ser fechado, como é o caso das notificações completas e de erro.
Conclusão
O nível atual de desenvolvimento de tecnologias de programação reativa usando a biblioteca RxJS permite criar aplicativos de cliente completos para sistemas bancários on-line, sem custos adicionais de mão-de-obra para organizar a interação com interfaces altamente carregadas de sistemas bancários remotos.
O primeiro conhecimento do RxJS pode assustar até mesmo um desenvolvedor experiente que se depara com os “meandros” da biblioteca que implementam o padrão de design “Observer”. Mas, talvez superando essas dificuldades, no futuro, o RxJS se tornará uma ferramenta indispensável para resolver os problemas do processamento assíncrono de fluxos de dados heterogêneos em tempo real.