Um guarda nunca foi o trabalho inteiro
Dois cursos já apontaram para cá pelo nome. O curso de nível Pleno "Padrão: chaves de idempotência" construiu uma guarda de reservar, trabalhar, concluir em volta de uma operação inteira, e fechou descrevendo este: "Aquele faz uma pergunta mais difícil: como projetar um job para ser seguro de tentar de novo quantas vezes for, incluindo cada ponto dentro do perform em que ele pode falhar no meio." O curso de nível Pleno "Solid Queue: arquitetura de jobs" seguiu o que acontece quando um worker morre no meio de um job. O claim vira uma linha falha, e nada roda de novo sozinho. A nova tentativa, ele disse, é decisão do retry_on, de uma nova tentativa manual, "ou de um job escrito para que rodar duas vezes seja inofensivo".
Os dois partem do princípio de que você sabe o que é idempotência e por que a entrega at-least-once a torna necessária. O curso de nível Pleno "Service objects: idempotência", antes nesta série, e os cursos de nível Estagiário sobre verbos HTTP construíram esse vocabulário. Este curso começa depois disso.
O job mais simples desta aplicação, e a guarda que ele já tem
O job mais simples em app/jobs/ é o Partners::FetchSiteIconJob:
module Partners
class FetchSiteIconJob < ApplicationJob
queue_as :default
def perform(partner_id)
partner = ::Partners::Partner.find_by(id: partner_id)
::Partners::FetchSiteIconService.call(partner: partner) if partner && !partner.logo.attached?
end
end
end
Uma linha de trabalho, protegida por !partner.logo.attached?. Rode esse job uma vez, ou cinco vezes porque uma nova tentativa ou uma reexecução manual o entregou de novo. Depois da primeira execução bem-sucedida, toda execução seguinte encontra partner.logo.attached? verdadeiro e não faz nada. Essa única condição é todo o mecanismo de idempotência deste job, e custa uma chamada a ActiveStorage::Attached::One#attached?.
Não é a única checagem. O ponto de enfileiramento, Admin::Partners::SavePartnerService, testa a mesma coisa antes de chamar perform_later:
::Partners::FetchSiteIconJob.perform_later(partner.id) unless partner.logo.attached?
Duas checagens da mesma condição, em dois momentos diferentes. Isso não é redundância; elas fecham duas corridas diferentes. A checagem no enfileiramento impede que um job seja enfileirado quando obviamente não há nada a fazer. A checagem dentro do perform é a que importa para idempotência, porque roda de novo a cada reexecução. Isso pode acontecer minutos ou horas depois de a checagem do enfileiramento passar, e o mundo pode ter mudado: um admin pode ter enviado um logo à mão nesse meio-tempo. Uma guarda checada uma vez, no enfileiramento, e confiada pelo resto da vida do job não protege contra novas tentativas. Só a que fica dentro do perform protege.
O que este job não precisa resolver
O FetchSiteIconJob se vira com um if. Não é sorte. O job faz uma coisa, e essa coisa já tem um estado natural de sim ou não, attached?, no registro que ele vai mudar.
A maior parte do que faz a idempotência parecer difícil aparece quando o trabalho de um job deixa de ser uma coisa só. Se o perform fizesse duas coisas, digamos anexar um logo e depois avisar alguém que aconteceu, uma queda entre elas deixaria a primeira feita e a segunda não. Uma nova tentativa ingênua teria então de decidir corretamente o que refazer e o que pular.
Esta aplicação tem um job assim. O Partners::DeliverWebhookJob faz um POST no endpoint de um parceiro e depois registra o sucesso, e o próprio comentário dele explica por que uma repetição é tolerável: o parceiro deduplica pelo código do certificado. A aula 6 volta a esse tipo de segurança. O resto deste curso constrói o caso de vários passos em cima do FetchSiteIconJob, com um passo extra claramente hipotético.
Pontos-chave
- Este curso começa onde "Padrão: chaves de idempotência" e "Solid Queue: arquitetura de jobs" disseram que começaria: não o que idempotência significa, mas como construí-la dentro do próprio
performde um job, passo a passo. - O
Partners::FetchSiteIconJobé idempotente por causa de uma condição,!partner.logo.attached?. Ela é checada uma vez no enfileiramento, noSavePartnerService, e, mais importante, toda vez que operformroda. - A checagem no enfileiramento e a checagem dentro do
performfecham duas corridas diferentes. Só a de dentro doperformprotege contra uma nova tentativa que acontece depois que o mundo mudou. - Um job que faz uma coisa pode muitas vezes se apoiar numa checagem natural de estado. Um job que faz mais de uma coisa precisa de uma resposta separada para cada uma, e esse é o assunto do resto deste curso.
Continue com a aula 2
Três formas de tornar um passo seguro para refazer
Apresenta conferir o estado antes de agir, deixar um índice único recusar duplicatas e gravar estados finais absolutos.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.