Propagando mensagens entre as camadas de sua aplicação

“Quem não se comunica, se trumbica!”, frase eternizada por Abelardo Barbosa (Chacrinha), que virou clichê para referências de problemas de comunicação entre “A” e “B”. No âmbito do desenvolvimento de software, a comunicação entre camadas, módulos, etc, é um dos pilares para que tudo funcione de forma correta e, portanto, pretendo abordar algumas técnicas para fazer a comunicação acontecer em suas aplicações!

Como já disse em artigos (ou em algum vídeo/palestra por aí, rs) anteriores, IMHO, desenvolvimento de software é uma “arte”, que tem como objetivo resolver um (ou um conjuto) problema(s) utilizando a tecnologia como ferramenta.

Sejam soluções simples ou complexas, é inerente à sistematização, estabelecer regras que devem ser respeitadas para que os problemas que o software se propõem a resolver seja feita de forma correta, ou ainda detectar falhas e interrupções inesperadas na execução dos processos, nos meios de comunicação e demais elementos que compõem uma aplicação.

A validação de “regras de negócio”, interrupções inesperadas e falhas diversas geram, em todos os níveis (ou camadas) de uma aplicação, mensagens que tendem a ser apresentadas ao usuário (que pode ou não ser capaz de resolver a questão antes de seguir o fluxo) e/ou registradas em mecanismos internos de log para que sejam analisadas pela equipe de desenvolvimento que irá, se necessário, interferir realizando as devidas correções e adaptações.

Como, então, podemos implementar um mecanismo para propagarmos essas mensagens de forma eficiente dentro de nossa aplicação? Vamos então observar algumas técnicas para tornar as coisas simples e eficazes!

Ambientes distintos, soluções distintas…

Uma importante etapa para implementar um mecanismo eficiente de propagação de mensagens é entender as diferenças entre os ambientes envolvidos no “Stack” da sua aplicação, entender o meio que as camadas do “Stack” se relacionam.

Por exemplo: A partir de um aplicativo móvel, um usuário solicita uma transferência de “X” reais para uma conta bancária “Y”, realizando uma requisição com os parâmetros dessa transação para uma API (REST), que irá validar o se o solicitante possui saldo disponível e, caso haja saldo, efetive a transferência para a conta de destino.

Considerando apenas as diferenças de AMBIENTE no fluxo acima, nós temos, NO MÍNIMO, 2 ambientes diferentes, são eles: Mobile (independente de plataforma) e a API (Servidor) onde, efetivamente, será processada a transação.

O ambiente Mobile se “relaciona” com a API através de um dos verbos (POST, por exemplo) do protocolo HTTP, por meio da internet.

Ou seja, caso ocorra alguma adversidade “do lado” da API, como o solicitante não haver saldo disponível para a transferência, uma mensagem de erro deverá ser enviada para o celular, para que o usuário seja notificado que a transferência não foi realizada e o motivo pelo qual o processo não pode ser efetivado.

Neste caso uma abordagem possível (e uma das mais utilizadas) é utilizar os códigos de status do próprio protocolo HTTP, por exemplo o 500 (Internal Server Error), que invalida a requisição e você também pode colocar uma mensagem indicando o motivo.

O código abaixo ilustra um Controller (ASP.NET Core) retornando um erro HTTP Status 500:

Neste link você poderá ver uma lista com todos os códigos de erros e também de sucesso na requisição realizada pelo cliente.

Sem “reinventarmos a roda”, utilizando uma implementação minimamente válida, resolvemos o problema de comunicação via HTTP entre o ambiente Mobile e o Servidor, o “problema” nesta abordagem é que há um “erro” semântico, “Como assim?”.

ANTES de efetivar a transferência, será verificado o saldo disponível da conta de origem e caso o valor da transferência ultrapasse o saldo disponível uma regra foi “quebrada” e o fluxo será interrompido.

Se observar a lista completa dos HTTP Status Codes acima, não há um código que represente uma interrupção no fluxo da aplicação por “quebra” de uma regra de negócio. “Bad Request” talvez, mas isso representa que a requisição foi realizada de forma errada, assim como “Internal Server Error” representa que um erro relacionado ao Servidor ocorreu e não que uma regra de negócio impediu a continuidade.

Vamos reservar essa reflexão um pouco e descer um nível do “lado do servidor”.

Ainda seguindo nosso fluxo de transferência de valores, do lado do servidor, é comum separarmos em camadas as regras de negócio de uma aplicação, acesso ao banco de dados, etc, sem novidades… Nesse caso, a relação entre as camadas é “transparente”, uma vez que, geralmente, uma camada faz uma referência direta a outra “consumindo” uma DLL (por exemplo) compilada e rodando no mesmo ambiente da aplicação de origem da chamada.

Nesse caso, uma forma eficiente de propagarmos mensagens de interrupção de fluxo, são as chamadas “Excptions” em C# (assim como muitas outras linguagens de programação) há um vasto conjunto de “Exceptions” que podem ser utilizadas para representar diferentes tipos de quebras de fluxo, exemplos:

  • InvalidOperationException
  • ArgumentException
  • ArgumentNullException
  • e muitos outros tipos existentes de Exception…

As “Exceptions” já dizem algo mais sobre os motivos pelos quais houve um quebra no fluxo da aplicação, por exemplo:

Há portanto maior clareza que houve uma interrupção (“Exception”) em virtude da falta de saldo, a camada superior irá receber essa “Exception”, tratá-la e retornar para o usuário, “embarcando” a mensagem junto do HTTP Status Code, por exemplo, de acordo com nosso fluxo definido acima.

“Exceptions” ajudam muito na propagação de mensagens de interrupção de fluxo quando as camadas da aplicação compartilham o mesmo ambiente. As coisas complicam um pouco quando as exceções precisam ser propagadas para uma aplicação Mobile, por exemplo, sendo necessário algum desenvolvimento customizado para que isso seja eficiente e faça sentido no escopo de nossa aplicação.

“Exceptions” representam um bloqueio no fluxo da aplicação, eu costumo chamar os bloqueios de fluxo de IMPEDIMENTOS, algo do tipo…

You shall not pass
You shall not pass

Mas, como toda regra há uma exceção (rs, muito oportuno nessa altura do campeonato), nem sempre “quebrar” uma regra representa um bloqueio de fluxo.

Imagine um cenário em que o solicitante da transação possui um saldo de R$ 300,00 em sua conta e um limite de R$ 500,00 de cheque especial. A transação que ele está tentando realizar é de R$ 400,00, somando o saldo + o limite do cheque especial, há o montante “disponível” para efetivar a transação, porém irá consumir R$ 100,00 do limite, a transação será efetivada mas é legal por parte da aplicação avisar o usuário que foi feita a transferência mas ele está utilizando seu limite e poderão correr juros sobre o valor utilizado.

Entenda, a “quebra” da regra não gerou um impedimento, mas gerou um alerta que deve ser enviado ao usuário. Bom “né”?

Mas onde há trevas também pode haver luz!

Tudo o que apresentei até o momento pode estar parecendo um cenário um tanto quanto nebuloso, mas vale a reflexão. Veja como algo que teoricamente simples, muitas vezes considerado “etapa vencida”, pode se tornar um problemão dentro da nossa aplicação.

Lembre-se que, a tudo isso, ainda podemos acrescentar um pouco mais de complexidade se considerarmos o fator “Cross-Platform”, conforme apresentei em meu artigo anterior.

Retomando nossa reflexão “reservada” sobre os HTTP Status Codes não “cobrirem” todas as situações possíveis na propagação de mensagens entre Cliente x Servidor (principalmente no contexto semântico), aliados a necessidade de propagarmos “Impedimentos” e “Alertas” resultantes da aplicação das regras de negócio de nossa aplicação (do lado do cliente e/ou do lado do servidor), é intrínseco à arquitetura do software fornecer recursos para que as mensagens possam ser propagadas de forma transparente, de ponta-a-ponta em todas as camadas, ambientes, protocolos, etc em nossa aplicação.

Uma maneira para resolver essa questão, é criar um “Envelope” que recebe o resultado das chamadas de nossa aplicação. Este “Envelope”, além do resultado do processamento (no caso do nosso exemplo aqui, os dados da transação realizada, em caso de sucesso), as mensagens de erros, impedimentos e alertas ocorridos durante o processamento.

Relembrando meu artigo anterior, apresentei uma proposta de “Design Pattern” para desenvolvimento de aplicações cujo princípio base é promover o máximo compartilhamento de código entre camadas e plataformas.

Como comentado neste artigo, é função da arquitetura prover os recursos necessários para que essa comunicação ocorra de forma eficiente, deixo como sugestão a você, caro leitor, que observe a classe MessagesContainer, nela implemento todas as estruturas necessárias para propagação de mensagens entre camadas de uma aplicação, Cross-Platform inclusive e distribuída em um NuGet Package.

Observe também a classe ServiceResult, que representa o retorno do processamento de uma chamada a um serviço e retorna o resultado herdando as características de MessagesContainer para retornar também as mensagens ocorridas durante esse processamento.

Espero ter contribuído com o entendimento da função da comunicação entre camadas e plataformas de uma aplicação e apresentado uma proposta de implementação desse recurso para seus projetos!

Antes de me despedir (rs), siga-me no GitHub, dê “Watch” no repositório do “Design Pattern” CrossPlatform que em breve colocarei no ar a Wiki do projeto e alguns exemplos de uso!

Um grande abraço e até a próxima conexão!