A otimização da tela de pagamento dos sites das marcas B2W

Igor Felipe
b2w engineering
Published in
7 min readJun 2, 2021

Como foi possível melhorar a manutenção, performance e UX da etapa final de compra.

Desde que entrei na B2W(2018), fazer alguma alteração na tela de pagamento era complicado, arriscado e demorado. Isso se dava pela forma que foi construído o projeto.

Por ser uma tela de pagamento, o código contava com muitas regras de negócios que geravam inúmeras possibilidades de exibições e ações, dificultando a nossa vida na hora de realizar alguma manutenção.

Como dito acima, a nossa tela de pagamento era responsável por realizar todas as chamadas HTTP para as API’s, tratá-las, aplicar as regras de negócio e exibir os dados na tela.

Arquitetura da tela de pagamento antigo onde o front-end fazia todas as requisições para as apis e tratava as respostas

Por termos inúmeros fluxos e várias possibilidades de exibição de elementos na tela, o nosso código acabou contando com muitas variáveis booleanas, representando contextos do usuário e compondo esses fluxos. A conclusão: criamos um boolean hell, pois sempre havia muitas condições e combinações de variáveis em nosso código

Como evoluímos

Com todos esses desafios citados acima, era a hora de refatorar a nossa aplicação por inteiro.

Antes de tomar qualquer tipo de decisão, listamos primeiro quais eram os problemas que precisávamos solucionar:

  1. reduzir o número de requests do lado do front-end;
  2. tornar a manutenção do código mais fácil;
  3. melhorar a experiência do usuário.

Tendo em mãos os problemas, era a hora de discutir e desenhar uma arquitetura que solucionasse tudo citado acima. Foi então que, depois de muitos desenhos no famoso quadro branco, decidimos e realizamos as seguintes mudanças.

Reduzindo o número de requests feitos no front

Vamos voltar ao exemplo da nossa arquitetura antiga.

No exemplo acima, temos uma interface de usuário (representada na imagem como Front) que faz todas as requisições para os microsserviços necessários. Como dito acima, isso acarretou inúmeros problemas para o nosso contexto.

Por isso, implantamos mais um serviço, responsável por tornar toda essa cadeia de requisições mais simples para a interface.

Arquitetura da tela nova onde o BFF é responsável por realizar paralelamente todas as chamadas para as APIs

No diagrama ilustrado acima, todas as requisições são feitas para o BFF(Backend for frontend) que executa as chamadas para as outras APIs em paralelo, reduzindo então a quantidade de requisições feitas do lado do cliente.

Mesmo que fosse possível paralelizar essas chamadas no modelo antigo, a quantidade de requests do lado do cliente reduzia consideravelmente a performance da aplicação (principalmente em máquinas menos potentes e com baixa conexão).

Uma coisa legal que o BFF também nos trouxe de bônus foi a possibilidade de realizar todos esses requests das APIs via rede interna. Com isso, melhoramos a performance em 67% e aumentamos a taxa de conversão em 1.6%.

Tornando a manutenção do código mais fácil

O projeto do pagamento legado foi construído com AngularJS utilizando o paradigma funcional. O grande problema é que os padrões definidos no início acabaram se perdendo no dia a dia. Além disso, como dito na introdução, todo código de regras de negócio e fluxos eram feitos nesse mesmo projeto utilizando variáveis boolean.

Como construímos o BFF

Antes de entrarmos na solução de como melhoramos a manutenção, precisamos entender como foi arquitetado o nosso BFF.

Nossa arquitetura é constituída de 5 elementos:

Session Meta - responsável por todas as variáveis de contexto do usuário;

Screens - telas da interface;

Actions - ações na interface que modificam as variáveis de contexto do Session Meta;

Standalone Actions - Ações na interface que NÃO modificam o Session Meta;

Components - Representação dos componentes da interface.

Para ilustrar melhor, vamos a um exemplo.

Suponhamos que um usuário que tenha um id 1234 entre na nossa tela de pagamento e compre os produtos contidos num carrinho de id 4567, com a opção de frete A_JATO.

Ao entrar na tela de pagamento, é disparada uma chamada para o BFF, executando uma ação de criação de sessão do usuário com as variáveis de idUsuario, idCarrinho e opcaoDeFrete no corpo.

Quando a ação é um sucesso e a sessão é criada, todas as variáveis de contexto contidas na ação vão para o Session Meta e o BFF indica qual a Screen que o front-end deve renderizar e quais Components.

Dessa forma, a resposta para essa ação fica como o exemplo abaixo.

Isso nos leva a conclusão de que, além de controle de estado e variáveis de contexto, o nosso BFF também controla qual Screen o usuário deve estar e com quais Components renderizados.

Component

Um Component é composto de:

Rel - nome do componente;

Payload - dados que vão ser utilizados pelo front para renderização. Ex: Um componente de endereço retorna no payload as informações de rua, bairro, número, etc;

Behaviour - qual comportamento o componente deve ter na tela;

Messages - possíveis mensagens de alerta, erro, etc.

Para entendermos melhor o que cada elemento é responsável, vamos supor que, quando o usuário cria a sessão no BFF, precisamos verificar se os produtos no carrinho indicado tem algum tipo de desconto. Se, por acaso, existir algum tipo de desconto, deveremos retornar na resposta um component com o behaviour COM_DESCONTO e, caso não exista, precisamos retornar com o behaviour SEM_DESCONTO.

Exemplo carrinho com desconto:

Exemplo carrinho sem desconto:

Observe que o payload também muda de acordo com o behaviour retornado.

Ex: caso tenha desconto, o payload conta com um atributo desconto.

Actions e Standalone Actions

Presumimos então que o nosso usuário selecione uma opção de frete diferente. Dessa vez, ele decidiu comprar com a opção de frete mais barata diminuindo o total da sua compra.

Para realizar essa transação, nós contamos com as nossas actions. Essa camada é responsável por realizar ações feitas pelo usuário na nossa tela e modificar os componentes e variáveis de contexto. Isso significa que toda a troca de estado por transações é feita por chamadas para o BFF, que nos entrega um novo estado da nossa aplicação.

Continuando com o nosso exemplo, o usuário clica na opção de frete mais barata e a action de MUDAR_FRETE é disparada.

Perceba que enviamos todo o nosso Session Meta com as variáveis de contexto, incluindo qual era o frete selecionado. Também no corpo dessa chamada, passamos no payload para qual opção de frete queremos mudar e no type passamos qual é a action que estamos executando.

Dessa forma, a resposta do BFF é:

A única diferença entre uma action e uma standalone action é que as standalones não modificam as variáveis de contexto contidas no session meta.

Um exemplo da aplicação de uma standalone action no nosso pagamento é o fluxo de seleção para o PIX. Quando disparada, a ação apenas retorna o componente com a url do QRCode, mantendo todo o session meta e screen.

Diminuindo o número de booleans com máquina de estado finita

Suponhamos um código em AngularJS:

https://gist.github.com/igorfelipee/fa45e5f4701868f88df70dc370756adc

Observando esse código, podemos perceber que além de ser muito complicado dar manutenção, qualquer erro mínimo pode causar um bug enorme na nossa página. Isso se dá pelo fato de utilizarmos muitas flags boolean para tomar, ou não, um fluxo específico.

Decidimos então implementar uma FSM e acabar de vez com esse boolean hell. Nesse caso, essa arquitetura nos ajudou pois, diferente dessa quantidade de comparações, a FSM juntamente da arquitetura que construímos o BFF, faz com que todo esse contexto de elementos da tela esteja acoplada num estado dessa máquina. Assim, o nosso front-end só precisa se preocupar em renderizar o que veio na resposta das ações.

O código acima, dada a aplicação dessa arquitetura, fica assim:

Como toda a regra de negócio agora é aplicada no nosso back-end, recebemos apenas 'o que' e 'como' um componente deve ser renderizado no front-end. Dessa forma, a manutenção nesse componente fica muito mais simples e menos suscetível a erros de desenvolvimento.

Melhorando a UX

Na tela antiga, era muito complicado realizar qualquer mudança visual sem que algum fluxo específico viesse a quebrar.

Dessa forma, acabávamos deixando um pouco de lado melhorar a experiência do usuário. Com todo esse ganho de manutenibilidade e performance, estamos conseguindo otimizar essa experiência cada vez mais.

Considerações finais

Após inúmeros testes de carga e o lançamento dessa nova versão, tivemos o primeiro frio na barriga que foi a Black Friday. Felizmente, nosso projeto teve excelência em performance e disponibilidade. Com isso, a reescrita foi considerada um sucesso.

Toda essa experiência de desenvolvimento do projeto, nos leva a conclusão de que uma simples mudança de paradigma (quando bem planejada) consegue resolver e melhorar inúmeros pontos.

Por isso, é muito importante discutir bastante sobre a arquitetura dos seus projetos.

Gostaria de agradecer ao meu time pela oportunidade de participar dessa reescrita do projeto e de me ajudar MUITO na escrita desse texto.

Sem vocês nada disso seria possível.

Se você busca uma oportunidade de desenvolvimento, trabalhando com inovação em um negócio de alto impacto, acesse o portal B2W Carreiras! Nele, você consegue acessar todas as vagas disponíveis. Venha fazer parte do nosso time!

--

--