Quais são as práticas recomendadas para lidar com erros em ações de várias etapas entre cliente e servidores?

Aug 18 2020

Eu tenho um site que carrega postagens de emprego para minha API, há várias etapas para fazer isso:

  1. Carregue uma imagem de logotipo para o armazenamento de arquivos.

  2. Insira dados sobre o anúncio de emprego em um banco de dados.

  3. Processe um pagamento com um provedor terceirizado.

  4. Envie um e-mail por meio de um provedor terceirizado.

Em geral, você pode imaginar outras etapas presentes aqui em diferentes aplicativos, por exemplo, obter algumas informações de uma API de terceiros, validar um ReCAPTCHA, atualizar a API de indexação do Google, enviar um SMS, etc., etc.

Uma vez que todos estes estão usando terceiros e são independentes do servidor que trata a chamada API, qualquer um deles pode deixar algumas das etapas concluídas com sucesso e outras não (por exemplo, logotipo atualizado, mas pagamento não cobrado).

Minha pergunta é como os erros nesses tipos de ações de várias etapas entre clientes e servidores normalmente são tratados em sistemas de produção? Existem padrões aceitos ou práticas recomendadas?


tenho considerado:

  1. Não lidar com erros e apenas esperar que tudo ocorra sem erros.

  2. Definir uma função 'desfazer' no back-end para cada uma das etapas e, se uma das etapas falhar, chamá-la nas etapas anteriores. Com ações que consistem em muitas etapas, isso pode se transformar em código espaguete rapidamente e algumas etapas não podem ser desfeitas tão facilmente - por exemplo, enviar um e-mail.

  3. Criar um endpoint separado no back-end para cada uma das etapas e permitir que o cliente chame cada uma delas. Isso também pode usar um endpoint de API 'desfazer', portanto, se o cliente receber um erro em uma das etapas, ele poderá desfazer todas as etapas anteriores. Isso tem a vantagem de permitir que o cliente estime o andamento da ação que está sendo concluída, ou seja, pode exibir '1 de 5 etapas concluídas' para o usuário.

  4. Criando uma linha em um banco de dados (ou banco de dados em memória?) para cada ação e quando cada etapa é concluída marcando a coluna correspondente como concluída. Quando todas as colunas da linha forem concluídas, envie uma resposta de volta ao usuário.

Respostas

1 RalfKleberhoff Aug 19 2020 at 13:30

Esse processo de várias etapas é uma transação, movendo-se de um estado inicial consistente ("nada aconteceu") para algum estado final ("anúncio de trabalho concluído"), com etapas intermediárias que deixam seu aplicativo em um estado (possivelmente) inconsistente.

Você não deve delegar o tratamento da transação ao seu cliente, especialmente não dividir o processo em chamadas individuais (ou ele talvez pule a etapa de pagamento - ruim para o negócio).

Se uma etapa falhar, certamente não faz sentido continuar, você deve fazer algum tipo de reversão para um estado aceitável, não necessariamente o estado inicial. Por exemplo, não vejo necessidade absoluta de remover a imagem carregada do armazenamento de arquivos.

Primeiro, tentaria organizar as etapas de forma que a maioria das etapas intermediárias fosse aceitável, de modo que não houvesse necessidade de reversão.

As etapas complicadas são certamente pagamento e e-mail (se entendi bem o seu negócio).

  • Cobrar o cliente sem enviar os e-mails é ruim (quase uma fraude).
  • Enviar os e-mails sem receber o pagamento significa perder dinheiro (se isso não acontecer com frequência, pode ser tolerável). Você definitivamente não pode reverter e-mails, uma vez que eles foram enviados, mas talvez você possa reverter pagamentos (a menos que você perca a conexão com o provedor naquele momento).

Como você depende de conexões externas, não vejo uma maneira de evitar absolutamente a conclusão parcial de sua transação, então projetaria o processo de forma que falhas intermediárias

  • ou altamente improvável
  • ou deixar um estado tolerável.

Então, eu

  1. Faça ping em todos os serviços externos para garantir que eles estejam ativos e acessíveis
  2. Carregue uma imagem de logotipo para o armazenamento de arquivos.
  3. Insira dados sobre o anúncio de emprego em um banco de dados.
  4. Envie um e-mail por meio de um provedor terceirizado.
  5. Processe um pagamento com um provedor terceirizado.

O procedimento de reversão seria

  • remover a imagem do armazenamento de arquivos,
  • remover o anúncio de emprego do banco de dados,
  • se o e-mail foi enviado, mas o pagamento falhou: agende a solicitação de pagamento para uma nova tentativa posterior.
JacquesB Aug 18 2020 at 21:56

"Melhor" obviamente depende dos requisitos. O número 1 é claramente o mais simples de implementar, mas as transações serão perdidas/incompletas em caso de erros. Talvez este seja um trade-off aceitável do ponto de vista comercial?

A solução mais robusta é dividir o processo em uma série de etapas em que cada etapa é uma transação. Uma transação é concluída ou falhou e, se falhar, pode ser tentada novamente com segurança. (Por exemplo, enviar um e-mail ou sms seria uma transação.) Uma linha do banco de dados controla quais etapas foram concluídas.

Não acho que você deva fazer o cliente chamar cada etapa. Isso criaria um acoplamento forte e apenas aumentaria a complexidade. Basta que o cliente chame uma única solicitação com todos os dados necessários, que inicia o fluxo de trabalho. O cliente pode então enviar uma solicitação periódica separada para consultar o status se você quiser mostrar o progresso.

O suporte para desfazer é mais complicado e (como você pode observar) nem sempre é possível. Se alguma etapa pode falhar de forma que todo o processo deva ser rejeitado (como se um cartão de crédito não pudesse ser válido), acho que deve ser executado em uma etapa de validação antes que o processo de várias etapas seja iniciado. Isso permitirá que você forneça feedback síncrono ao cliente e faça com que o cliente revise e insira novamente os dados.

AdrianL Aug 27 2020 at 01:00

Como outros mencionaram, idealmente, você deseja agrupar todas as peças necessárias para uma postagem de trabalho bem-sucedida e ter um cliente para enviá-lo ao seu back-end em uma solicitação. Como desenvolvedor móvel, sempre defendo que os clientes trabalhem o mínimo possível para preservar o uso de dados e a duração da bateria.

Quanto ao back-end, sugiro tentar inserir as informações mais importantes no banco de dados primeiro e, em seguida, tentar, por exemplo, inserir dados suplementares depois, como uma imagem de logotipo