{"id":30778,"date":"2019-10-31T21:37:23","date_gmt":"2019-10-31T18:37:23","guid":{"rendered":"https:\/\/prohoster.info\/blog\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\/"},"modified":"2019-10-31T21:37:23","modified_gmt":"2019-10-31T18:37:23","slug":"opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","title":{"rendered":"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>O que pode levar uma grande empresa como a Lamoda, com um processo bem estruturado e dezenas de servi\u00e7os interconectados, a mudar substancialmente sua abordagem? A motiva\u00e7\u00e3o pode variar bastante: desde quest\u00f5es legais at\u00e9 o desejo inerente dos programadores de experimentar.<\/p>\n<p>Mas isso n\u00e3o significa que n\u00e3o se pode esperar um ganho adicional. O que pode ser ganho ao implementar uma API orientada a eventos no Kafka ser\u00e1 explicado por Sergey Zaika (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/fewald\/\" class=\"user_link\">fewald<\/a><\/noindex>). Tamb\u00e9m haver\u00e1 experi\u00eancias e descobertas interessantes, pois n\u00e3o pode haver experimenta\u00e7\u00e3o sem isso.<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/7ab959ab45ec5c6565b35b18b361c0ea.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<em>Disclaimer: Este artigo \u00e9 baseado em materiais de um meetup que Sergey conduziu em novembro de 2018 no HighLoad++. A experi\u00eancia ao vivo da Lamoda com o Kafka atraiu os ouvintes tanto quanto outras apresenta\u00e7\u00f5es do cronograma. Achamos que este \u00e9 um excelente exemplo de que sempre \u00e9 poss\u00edvel e necess\u00e1rio encontrar pessoas com interesses semelhantes, e os organizadores do HighLoad++ continuar\u00e3o a criar uma atmosfera prop\u00edcia para isso.<\/em><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Sobre o processo<\/h2>\n<p>\nA Lamoda \u00e9 uma grande plataforma de e-commerce, que possui seu pr\u00f3prio call center, servi\u00e7o de entrega (e muitos parceiros), est\u00fadio fotogr\u00e1fico, um enorme armaz\u00e9m e tudo isso funciona com seu pr\u00f3prio software. Existem dezenas de m\u00e9todos de pagamento, parceiros B2B que podem usar parte ou todos esses servi\u00e7os e que querem saber informa\u00e7\u00f5es atualizadas sobre seus produtos. Al\u00e9m disso, a Lamoda opera em tr\u00eas pa\u00edses al\u00e9m da R\u00fassia e cada um deles tem suas peculiaridades. No total, deve haver mais de uma centena de maneiras de configurar um novo pedido, que deve ser processado de uma forma espec\u00edfica. Tudo isso funciona por meio de dezenas de servi\u00e7os que se comunicam de maneiras por vezes n\u00e3o \u00f3bvias. H\u00e1 tamb\u00e9m um sistema central cuja principal responsabilidade s\u00e3o os status dos pedidos. N\u00f3s a chamamos de BOB, e eu trabalho com ela.<\/p>\n<h2>Ferramenta de Reembolso com API orientada a eventos <\/h2>\n<p>\nA express\u00e3o API orientada a eventos j\u00e1 est\u00e1 bastante batida; mais adiante vamos definir com mais detalhes o que isso significa. Come\u00e7arei pelo contexto em que decidimos testar a abordagem de API orientada a eventos no Kafka. <\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/3bfdce47dd8420fc63d84645e76de647.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEm qualquer loja, al\u00e9m dos pedidos pelos quais os clientes pagam, h\u00e1 momentos em que a loja precisa devolver o dinheiro, porque o produto n\u00e3o atendeu \u00e0s expectativas do cliente. Este \u00e9 um processo relativamente curto: confirmamos a informa\u00e7\u00e3o, se necess\u00e1rio, e realizamos a devolu\u00e7\u00e3o do dinheiro. <\/p>\n<p>No entanto, o retorno se complicou devido \u00e0 mudan\u00e7a na legisla\u00e7\u00e3o, e tivemos que implementar um microservi\u00e7o separado para isso.<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/0e358df5476e7448976e0f1147a103fb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNossa motiva\u00e7\u00e3o:<\/p>\n<ol>\n<li><strong>Lei Federal 54<\/strong>\u00a0\u2014 em resumo, a lei exige que se informe \u00e0 receita sobre cada opera\u00e7\u00e3o financeira, seja um retorno ou um recebimento, em um SLA bastante curto de alguns minutos. N\u00f3s, como e-commerce, realizamos muitas opera\u00e7\u00f5es. Tecnologicamente, isso significa uma nova responsabilidade (e, portanto, um novo servi\u00e7o) e ajustes em todos os sistemas envolvidos.<\/li>\n<li><strong>BOB split<\/strong>\u00a0\u2014 um projeto interno da empresa para livrar o BOB de um grande n\u00famero de responsabilidades n\u00e3o relacionadas e diminuir sua complexidade geral.<\/li>\n<\/ol>\n<p>\n<img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/d3ecf9961bdb372fc5f84ee9389f73ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNeste diagrama est\u00e3o desenhados os principais sistemas da Lamoda. Atualmente, a maioria deles se assemelha mais a <strong>um conglomerado de 5-10 microservi\u00e7os em torno de um mon\u00f3lito em encolhimento.<\/strong>Eles est\u00e3o crescendo aos poucos, mas tentamos mant\u00ea-los menores, porque implementar um fragmento isolado \u00e9 assustador \u2014 n\u00e3o podemos permitir que ele caia. Todas as trocas (setas) precisam ser redundantes, e temos que contar que qualquer uma delas pode se tornar indispon\u00edvel.<\/p>\n<p>No BOB tamb\u00e9m h\u00e1 muitas trocas: sistemas de pagamento, entrega, notifica\u00e7\u00f5es etc. <\/p>\n<p>Tecnicamente, o BOB \u00e9:<\/p>\n<ul>\n<li>~150k linhas de c\u00f3digo + ~100k linhas de testes;<\/li>\n<li>php7.2 + Zend 1 &amp; Symfony Components 3;<\/li>\n<li>&gt;100 API &amp; ~50 integra\u00e7\u00f5es externas;<\/li>\n<li>4 pa\u00edses com suas l\u00f3gicas de neg\u00f3cios. <\/li>\n<\/ul>\n<p>\nImplementar o BOB \u00e9 caro e dif\u00edcil, a quantidade de c\u00f3digo e as tarefas que ele resolve s\u00e3o tais que ningu\u00e9m consegue ter tudo isso na cabe\u00e7a de uma vez. Em resumo, h\u00e1 muitos motivos para simplific\u00e1-lo.<\/p>\n<h2>Processo de retorno<\/h2>\n<p>\nOriginalmente, o processo envolve dois sistemas: BOB e Payment. Agora aparecem mais dois:<\/p>\n<ul>\n<li>Servi\u00e7o de Fiscaliza\u00e7\u00e3o, que lidar\u00e1 com problemas de fiscaliza\u00e7\u00e3o e comunica\u00e7\u00e3o com servi\u00e7os externos.<\/li>\n<li>Ferramenta de Reembolso, que simplesmente gerencia novas trocas para n\u00e3o sobrecarregar o BOB.<\/li>\n<\/ul>\n<p>\nAgora o processo se parece com isto:<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/13c02975881ad35c61304053c604cda3.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ol>\n<li>O BOB recebe um pedido de devolu\u00e7\u00e3o de dinheiro.<\/li>\n<li>O BOB informa isso \u00e0 Ferramenta de Reembolso.<\/li>\n<li>A Ferramenta de Reembolso diz ao Payment: 'Devolva o dinheiro'.<\/li>\n<li>O Payment devolve o dinheiro.<\/li>\n<li>A Ferramenta de Reembolso e o BOB sincronizam os status entre si, porque por enquanto ambos precisam disso. Estamos ainda n\u00e3o prontos para mudar completamente para a Ferramenta de Reembolso, uma vez que o BOB possui uma interface, relat\u00f3rios para contabilidade e, na verdade, muitos dados que n\u00e3o podem ser transferidos t\u00e3o facilmente. Precisamos nos equilibrar entre os dois.<\/li>\n<li>Um pedido de fiscaliza\u00e7\u00e3o \u00e9 enviado.<\/li>\n<\/ol>\n<p>\nNo final, n\u00f3s criamos um barramento de eventos no Kafka, onde tudo se conectou. Uhur, agora temos um \u00fanico ponto de falha (sarcasmo).<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/674edd7972998b4985071f5250612c7e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOs pr\u00f3s e contras s\u00e3o bastante \u00f3bvios. Criamos um barramento, ent\u00e3o agora todos os servi\u00e7os dependem dele. Isso simplifica o design, mas traz para o sistema um \u00fanico ponto de falha. Se o Kafka cair, o processo para.<\/p>\n<h2>O que \u00e9 uma API orientada a eventos <\/h2>\n<p>\nUma boa resposta para essa pergunta est\u00e1 na apresenta\u00e7\u00e3o de Martin Fowler (GOTO 2017) <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/STKCRSUsyPO\">\u00abThe Many Meanings of Event-Driven Architecture\u00bb<\/a><\/noindex>. <\/p>\n<p>Resumindo, o que fizemos:<\/p>\n<ol>\n<li>Encapsulamos todas as trocas ass\u00edncronas atrav\u00e9s de <strong>events storage<\/strong>. Em vez de notificar cada consumidor interessado sobre a mudan\u00e7a de status pela rede, escrevemos em um armazenamento centralizado um evento sobre a mudan\u00e7a de estado, e os consumidores interessados no t\u00f3pico leem tudo o que aparece de l\u00e1.<\/li>\n<li>Um evento, neste caso, \u00e9 uma notifica\u00e7\u00e3o (<strong>notifications<\/strong>) de que algo mudou em algum lugar. Por exemplo, o status de um pedido mudou. O consumidor que precisa de alguns dados referentes \u00e0 mudan\u00e7a de status, que n\u00e3o est\u00e3o na notifica\u00e7\u00e3o, pode verificar seu estado por conta pr\u00f3pria.<\/li>\n<li>A variante m\u00e1xima \u00e9 o event sourcing completo, <strong>state transfer<\/strong>, onde o evento cont\u00e9m todas as informa\u00e7\u00f5es necess\u00e1rias para o processamento: de onde e para qual status foram transferidos, como os dados mudaram, etc. A quest\u00e3o \u00e9 apenas a viabilidade e o volume de informa\u00e7\u00f5es que voc\u00ea pode se permitir armazenar.<\/li>\n<\/ol>\n<p>\nNo lan\u00e7amento da Refund Tool, utilizamos a terceira op\u00e7\u00e3o. Isso simplificou o processamento de eventos, pois n\u00e3o foi necess\u00e1rio buscar informa\u00e7\u00f5es detalhadas, al\u00e9m de excluir o cen\u00e1rio em que cada novo evento gera um estouro de requisi\u00e7\u00f5es GET de esclarecimento dos consumidores.<\/p>\n<p>O servi\u00e7o Refund Tool <strong>n\u00e3o est\u00e1 sobrecarregado<\/strong>, ent\u00e3o o Kafka l\u00e1 \u00e9 mais um teste do que uma necessidade. N\u00e3o acho que, se o servi\u00e7o de reembolso se tornasse um projeto de alta carga, o neg\u00f3cio ficaria satisfeito.<\/p>\n<h4>Troca ass\u00edncrona AS IS<\/h4>\n<p>\nPara trocas ass\u00edncronas, o departamento de PHP geralmente utiliza RabbitMQ. Coletamos os dados para a solicita\u00e7\u00e3o, colocamos na fila, e o consumidor do mesmo servi\u00e7o lida com isso e envia (ou n\u00e3o envia). Para a pr\u00f3pria API, a Lamoda utiliza ativamente o Swagger. Projetamos a API, descrevemos-a no Swagger, geramos o c\u00f3digo do cliente e do servidor. Tamb\u00e9m utilizamos um JSON RPC 2.0 um pouco estendido. <\/p>\n<p>Em alguns lugares, utilizam barramentos ESB, outros dependem do ActiveMQ, mas, no geral, <strong>RabbitMQ \u2014 padr\u00e3o<\/strong>.<\/p>\n<h4>Troca ass\u00edncrona A SER<\/h4>\n<p>\nAo projetar a troca atrav\u00e9s do events-bus, \u00e9 poss\u00edvel perceber uma analogia. Descrevemos de maneira similar a futura troca de dados atrav\u00e9s das descri\u00e7\u00f5es da estrutura do evento. O formato YAML, e a gera\u00e7\u00e3o de c\u00f3digo tiveram que ser feitas manualmente, o gerador de acordo com a especifica\u00e7\u00e3o cria DTO e ensina clientes e servidores a trabalharem com eles. A gera\u00e7\u00e3o acontece em duas linguagens \u2014 <strong>golang e php<\/strong>. Isso permite manter as bibliotecas consistentes. O gerador foi escrito em golang, por isso recebeu o nome gogi.<\/p>\n<p>Event-sourcing no Kafka \u00e9 algo t\u00edpico. Existe uma solu\u00e7\u00e3o da vers\u00e3o enterprise principal do Kafka Confluent, al\u00e9m de <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/zalando\/nakadi\">nakadi<\/a><\/noindex>, uma solu\u00e7\u00e3o de nossos \"irm\u00e3os\" no mesmo dom\u00ednio, a Zalando. Nossa <strong>motiva\u00e7\u00e3o para come\u00e7ar com vanilla Kafka<\/strong>\u00a0\u00e9 deix\u00e1-la gratuita, at\u00e9 decidirmos de vez se vamos us\u00e1-la amplamente, al\u00e9m de deixar espa\u00e7o para manobras e melhorias: queremos suporte ao nosso <strong>JSON RPC 2.0<\/strong>, geradores para duas linguagens e ver o que mais. <\/p>\n<p>\u00c9 ir\u00f4nico que mesmo em um caso t\u00e3o feliz, onde existe uma empresa semelhante, a Zalando, que criou uma solu\u00e7\u00e3o parecida, n\u00e3o conseguimos us\u00e1-la de forma eficiente. <\/p>\n<p>Arquitetonicamente, no lan\u00e7amento, o padr\u00e3o \u00e9 o seguinte: lemos diretamente do Kafka, mas escrevemos apenas atrav\u00e9s do events-bus. Para leitura no Kafka, existem muitas solu\u00e7\u00f5es prontas: corretores, balanceadores, e ele est\u00e1 mais ou menos pronto para escalonamento horizontal; isso gostar\u00edamos de manter. A grava\u00e7\u00e3o, por outro lado, decidimos envolver atrav\u00e9s de um Gateway, ou seja, Events-bus, e aqui est\u00e1 o porqu\u00ea.<\/p>\n<h3>Events-bus<\/h3>\n<p>\nOu \u00f4nibus de eventos. Isso \u00e9 apenas um gateway http stateless, que assume v\u00e1rios pap\u00e9is importantes:<\/p>\n<ul>\n<li><strong>Valida\u00e7\u00e3o de produ\u00e7\u00e3o<\/strong>\u00a0\u2014 verificamos se os eventos atendem \u00e0 nossa especifica\u00e7\u00e3o.<\/li>\n<li><strong>Sistema mestre de eventos<\/strong>, ou seja, este \u00e9 o sistema principal e \u00fanico na empresa, que responde \u00e0 pergunta de quais eventos com quais estruturas s\u00e3o considerados v\u00e1lidos. A valida\u00e7\u00e3o inclui apenas tipos de dados e enums para especifica\u00e7\u00f5es r\u00edgidas do conte\u00fado. <\/li>\n<li><strong>Fun\u00e7\u00e3o hash<\/strong> para sharding \u2014 a estrutura da mensagem Kafka \u00e9 key-value e \u00e9 a partir do hash da chave que se calcula onde isso deve ser colocado.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Por qu\u00ea<\/h3>\n<p>\nTrabalhamos em uma grande empresa com um processo estabelecido. Por que mudar algo? <strong>Este \u00e9 um experimento<\/strong>, e esperamos obter algumas vantagens.<\/p>\n<h4>Trocas 1:n+1 (um para muitos)<\/h4>\n<p>\nCom Kafka, \u00e9 muito f\u00e1cil conectar novos consumidores \u00e0 API. <\/p>\n<p>Suponha que voc\u00ea tenha um diret\u00f3rio que precisa ser mantido atualizado em v\u00e1rios sistemas ao mesmo tempo (e em alguns novos). Anteriormente, cri\u00e1vamos um bundle que implementava o set-API, e inform\u00e1vamos \u00e0 master-system os endere\u00e7os dos consumidores. Agora, a master-system envia atualiza\u00e7\u00f5es para um t\u00f3pico, e todos os interessados podem ler. Uma nova sistema surgiu \u2013 a inscrevemos no t\u00f3pico. Sim, tamb\u00e9m \u00e9 um bundle, mas mais simples.<\/p>\n<p>No caso da refund-tool, que \u00e9 um peda\u00e7o do BOB, \u00e9 conveniente para n\u00f3s mantermos a sincroniza\u00e7\u00e3o por meio do Kafka. A Payment informa que o dinheiro foi devolvido: BOB e RT ficam sabendo disso, mudam seus status, e o Fiscalization Service \u00e9 avisado e emite o recibo.<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/b01b22a333b58e87aeef0c52d40e6960.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTemos planos de criar um Notifications Service \u00fanico, que informaria o cliente sobre as atualiza\u00e7\u00f5es de seu pedido\/retornos. Atualmente, essa responsabilidade est\u00e1 espalhada entre os sistemas. Precisamos apenas ensinar o Notifications Service a capturar as informa\u00e7\u00f5es relevantes do Kafka e reagir a elas (e desativar essas notifica\u00e7\u00f5es nos demais sistemas). Nenhuma nova troca direta ser\u00e1 necess\u00e1ria.<\/p>\n<h4>Data-driven<\/h4>\n<p>\nA informa\u00e7\u00e3o entre os sistemas torna-se transparente - n\u00e3o importa qu\u00e3o \"sangrenta\" sua empresa seja ou qu\u00e3o volumoso seja seu backlog. Na Lamoda, h\u00e1 um departamento de Data Analytics que coleta dados dos sistemas e os apresenta de forma reutiliz\u00e1vel, tanto para neg\u00f3cios quanto para sistemas inteligentes. O Kafka permite rapidamente fornecer muitos dados a eles e manter esse fluxo de informa\u00e7\u00e3o atualizado.<\/p>\n<h4>Replication log<\/h4>\n<p>\nAs mensagens n\u00e3o desaparecem ap\u00f3s a leitura, como no RabbitMQ. Quando um evento cont\u00e9m informa\u00e7\u00f5es suficientes para processamento, temos um hist\u00f3rico das \u00faltimas mudan\u00e7as no objeto e, se desejarmos, a possibilidade de aplicar essas mudan\u00e7as.<\/p>\n<p>O per\u00edodo de armazenamento do replication log depende da intensidade de grava\u00e7\u00e3o nesse t\u00f3pico; o Kafka permite configurar flexivelmente os limites de tempo de armazenamento e o volume de dados. Para t\u00f3picos intensivos, \u00e9 importante que todos os consumidores consigam ler as informa\u00e7\u00f5es antes que elas desapare\u00e7am, mesmo em caso de breves interrup\u00e7\u00f5es. Normalmente, conseguimos armazenar dados por\u00a0<strong>v\u00e1rios dias<\/strong>, o que \u00e9 mais que suficiente para o suporte. <\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/0e08dd384155289123ebee96430c2370.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nA seguir, um breve resumo da documenta\u00e7\u00e3o para aqueles que n\u00e3o est\u00e3o familiarizados com o Kafka (a imagem tamb\u00e9m \u00e9 da documenta\u00e7\u00e3o)<\/p>\n<p>No AMQP, existem filas: escrevemos mensagens na fila para o consumidor. Normalmente, uma fila \u00e9 processada por um sistema com a mesma l\u00f3gica de neg\u00f3cios. Se for necess\u00e1rio notificar v\u00e1rios sistemas, pode-se ensinar o aplicativo a escrever em v\u00e1rias filas ou configurar um exchange com um mecanismo fanout que as clone.<\/p>\n<p>No Kafka, h\u00e1 uma abstra\u00e7\u00e3o semelhante <em>t\u00f3pico<\/em>, onde voc\u00ea escreve mensagens, mas elas n\u00e3o desaparecem ap\u00f3s a leitura. Por padr\u00e3o, ao se conectar ao Kafka, voc\u00ea recebe todas as mensagens, e tem a possibilidade de salvar o ponto em que parou. Ou seja, voc\u00ea l\u00ea continuamente, pode n\u00e3o marcar a mensagem como lida, mas salva o id de onde continuar\u00e1 a leitura. O id no qual voc\u00ea parou \u00e9 chamado de offset (deslocamento), e o mecanismo \u00e9 o commit offset. <\/p>\n<p>Consequentemente, \u00e9 poss\u00edvel implementar diferentes l\u00f3gicas. Por exemplo, n\u00f3s temos BOB existente em 4 inst\u00e2ncias para diferentes pa\u00edses - a Lamoda est\u00e1 na R\u00fassia, Cazaquist\u00e3o, Ucr\u00e2nia e Bielorr\u00fassia. Como s\u00e3o implantados separadamente, eles t\u00eam suas pr\u00f3prias configura\u00e7\u00f5es e l\u00f3gica de neg\u00f3cios. Indicamos na mensagem a que pa\u00eds ela se refere. Cada consumidor BOB em cada pa\u00eds l\u00ea com diferentes groupId, e, se a mensagem n\u00e3o lhe diz respeito, a ignora, ou seja, comita imediatamente o offset +1. Se o mesmo t\u00f3pico for lido pelo nosso Payment Service, ele faz isso com um grupo separado, e, portanto, os offsets n\u00e3o se sobrep\u00f5em.<\/p>\n<p><b>Requisitos para eventos:<\/b><\/p>\n<ul>\n<li><strong>Integridade dos dados. <\/strong>Gostar\u00edamos que o evento tivesse dados suficientes para que pudesse ser processado. <\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><strong>Consist\u00eancia. <\/strong>Delegamos ao Events-bus a verifica\u00e7\u00e3o de que o evento \u00e9 consistente e que pode ser processado.<\/li>\n<li><strong>A ordem \u00e9 importante. <\/strong>No caso de retornos, somos obrigados a trabalhar com o hist\u00f3rico. Com as notifica\u00e7\u00f5es, a ordem n\u00e3o \u00e9 importante, se forem notifica\u00e7\u00f5es homog\u00eaneas, o email ser\u00e1 o mesmo independentemente de qual pedido chegou primeiro. No caso de retorno, h\u00e1 um processo claro; se a ordem mudar, ocorrer\u00e3o exce\u00e7\u00f5es, o reembolso n\u00e3o ser\u00e1 criado ou processado - entraremos em outro status.<\/li>\n<li><strong>Coer\u00eancia. <\/strong>Temos um reposit\u00f3rio e, em vez de usar API, agora criamos eventos. Precisamos de uma maneira de transmitir rapidamente e de forma econ\u00f4mica informa\u00e7\u00f5es sobre novos eventos e sobre as mudan\u00e7as em eventos j\u00e1 existentes para nossos servi\u00e7os. Isso \u00e9 alcan\u00e7ado por meio de uma especifica\u00e7\u00e3o comum em um reposit\u00f3rio Git separado e geradores de c\u00f3digo. Portanto, nossos clientes e servidores em diferentes servi\u00e7os est\u00e3o alinhados.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Kafka na Lamoda<\/h2>\n<p>\nTemos tr\u00eas instala\u00e7\u00f5es do Kafka: <\/p>\n<ol>\n<li>Logs;<\/li>\n<li>P&amp;D;<\/li>\n<li>Events-bus.<\/li>\n<\/ol>\n<p>\nHoje estamos falando apenas sobre o \u00faltimo item. No events-bus, temos instala\u00e7\u00f5es n\u00e3o muito grandes - 3 brokers (servidores) e apenas 27 t\u00f3picos. Na maioria das vezes, um t\u00f3pico \u00e9 um processo. Mas esse \u00e9 um ponto sutil, e agora vamos abord\u00e1-lo.<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/f398852689b31429cc97b4cbffcabab5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAcima est\u00e1 o gr\u00e1fico de rps. O processo de reembolsos \u00e9 destacado pela linha turquesa (sim, aquele na eixo X), e o processo de atualiza\u00e7\u00e3o de conte\u00fado \u00e9 destacado em rosa. <\/p>\n<p>O cat\u00e1logo da Lamoda cont\u00e9m milh\u00f5es de produtos, e os dados est\u00e3o sempre sendo atualizados. Algumas cole\u00e7\u00f5es saem de moda, novas s\u00e3o lan\u00e7adas em seu lugar, e constantemente aparecem novos modelos no cat\u00e1logo. Tentamos prever o que ser\u00e1 interessante para nossos clientes amanh\u00e3, por isso estamos sempre comprando novas pe\u00e7as, fotografando-as e atualizando a vitrine. <\/p>\n<p>Os picos rosas representam a atualiza\u00e7\u00e3o de produtos, ou seja, as mudan\u00e7as nos itens. \u00c9 vis\u00edvel que a equipe fotografou, fotografou, e ent\u00e3o, de repente! \u2014 carregou um pacote de eventos.<\/p>\n<h2>Casos de uso do Lamoda Events<\/h2>\n<p>\nA arquitetura que constru\u00edmos \u00e9 utilizada para as seguintes opera\u00e7\u00f5es:<\/p>\n<ul>\n<li><strong>Rastreamento de status de reembolsos<\/strong>: call-to-action e rastreamento de status de todos os sistemas envolvidos. Pagamentos, status, fiscaliza\u00e7\u00e3o, notifica\u00e7\u00f5es. Aqui testamos a abordagem, criamos ferramentas, reunimos todas as falhas, escrevemos documenta\u00e7\u00e3o e mostramos aos colegas como usar.<\/li>\n<li><strong>Atualiza\u00e7\u00e3o de cart\u00f5es de produtos: <\/strong>configura\u00e7\u00e3o, metadados, caracter\u00edsticas. Lida com um sistema (que exibe), enquanto v\u00e1rios escrevem.<\/li>\n<li><strong>Email, push e sms<\/strong>: o pedido foi coletado, o pedido chegou, o retorno foi aceito, etc., s\u00e3o muitos. <\/li>\n<li><strong>Estoque, atualiza\u00e7\u00e3o de estoque<\/strong>\u00a0\u2014 atualiza\u00e7\u00e3o quantitativa de itens, n\u00fameros simples: entrada no estoque, retorno. \u00c9 necess\u00e1rio que todos os sistemas relacionados \u00e0 reserva de produtos operem com os dados mais atualizados. Atualmente, o sistema de atualiza\u00e7\u00e3o de estoque \u00e9 bastante complexo, e o Kafka permitir\u00e1 simplific\u00e1-lo.<\/li>\n<li><strong>An\u00e1lise de Dados<\/strong> (departamento de P&amp;D), ferramentas de ML, an\u00e1lise, estat\u00edsticas. Queremos que a informa\u00e7\u00e3o seja transparente \u2014 para isso, o Kafka \u00e9 uma boa op\u00e7\u00e3o.<\/li>\n<\/ul>\n<p>\nAgora vem a parte mais interessante sobre as li\u00e7\u00f5es aprendidas e as descobertas que ocorreram ao longo de seis meses.<\/p>\n<h2>Problemas de design<\/h2>\n<p>\nVamos supor que queremos criar algo novo \u2014 por exemplo, transferir todo o processo de entrega para o Kafka. Atualmente, parte do processo est\u00e1 implementada no Processamento de Pedidos no BOB. Depois que o pedido \u00e9 passado para o servi\u00e7o de entrega, movimentado para o armaz\u00e9m intermedi\u00e1rio e outros, existe um modelo de status. H\u00e1 um mon\u00f3lito inteiro, na verdade dois, al\u00e9m de uma s\u00e9rie de APIs dedicadas \u00e0 entrega. Eles sabem muito mais sobre a entrega. <\/p>\n<p>Parece que essas s\u00e3o \u00e1reas semelhantes, mas para o Processamento de Pedidos no BOB e para o sistema de entrega, os status s\u00e3o diferentes. Por exemplo, alguns servi\u00e7os de entrega n\u00e3o enviam status intermedi\u00e1rios, apenas finais: 'entregue' ou 'perdido'. Outros, ao contr\u00e1rio, informam detalhadamente sobre o movimento do produto. Cada um tem suas pr\u00f3prias regras de valida\u00e7\u00e3o: para alguns, um email v\u00e1lido significa que ser\u00e1 processado; para outros, um email inv\u00e1lido, ainda assim, o pedido ser\u00e1 processado, pois existe um telefone para contato, enquanto outros dir\u00e3o que tal pedido n\u00e3o ser\u00e1 processado de maneira alguma.<\/p>\n<h3>Fluxo de dados<\/h3>\n<p>\nNo caso do Kafka, surge a quest\u00e3o de como organizar o fluxo de dados. Essa tarefa est\u00e1 relacionada \u00e0 escolha de uma estrat\u00e9gia em v\u00e1rios pontos, vamos abord\u00e1-los todos.<\/p>\n<h4>Um t\u00f3pico ou v\u00e1rios?<\/h4>\n<p>\nTemos uma especifica\u00e7\u00e3o de evento. No BOB, escrevemos que um determinado pedido deve ser entregue, e indicamos: n\u00famero do pedido, seu conte\u00fado, alguns SKU e c\u00f3digos de barras, etc. Quando o produto chega ao armaz\u00e9m, a entrega pode obter status, timestamps e tudo que for necess\u00e1rio. Mas, depois, queremos no BOB receber atualiza\u00e7\u00f5es sobre esses dados. Surge um processo reverso de obten\u00e7\u00e3o de dados da entrega. \u00c9 o mesmo evento? Ou \u00e9 uma troca separada que merece um t\u00f3pico separado?<\/p>\n<p>Provavelmente, eles ser\u00e3o muito semelhantes, e a tenta\u00e7\u00e3o de criar um \u00fanico t\u00f3pico n\u00e3o \u00e9 infundada, pois um t\u00f3pico separado significa consumidores separados, configura\u00e7\u00f5es separadas, uma gera\u00e7\u00e3o separada de tudo isso. Mas n\u00e3o \u00e9 certo.<\/p>\n<h4>Novo campo ou novo evento?<\/h4>\n<p>\nMas se usarmos os mesmos eventos, surge outro problema. Por exemplo, nem todos os sistemas de entrega podem gerar um DTO que o BOB consiga processar. N\u00f3s enviamos o id para eles, mas eles n\u00e3o o guardam, pois n\u00e3o \u00e9 necess\u00e1rio para eles, e do ponto de vista do in\u00edcio do processo do event-bus, este campo \u00e9 obrigat\u00f3rio. <\/p>\n<p>Se estabelecermos uma regra para o event-bus de que este campo \u00e9 obrigat\u00f3rio, seremos for\u00e7ados a colocar regras de valida\u00e7\u00e3o adicionais no BOB ou no manipulador do evento inicial. A valida\u00e7\u00e3o come\u00e7a a se espalhar pelo servi\u00e7o \u2014 o que n\u00e3o \u00e9 muito conveniente.<\/p>\n<p>Outro problema \u00e9 a tenta\u00e7\u00e3o do desenvolvimento incremental. Nos dizem que precisamos adicionar algo ao evento, e talvez, se pensarmos bem, isso deveria ser um evento separado. Mas na nossa arquitetura, um evento separado \u00e9 um t\u00f3pico separado. Um t\u00f3pico separado envolve todo o processo que descrevi acima. O desenvolvedor sente a tenta\u00e7\u00e3o de simplesmente adicionar outro campo ao esquema JSON e regenerar.<\/p>\n<p>No caso dos reembolsos, chegamos a um evento de eventos em meio ano. T\u00ednhamos um meta-evento chamado atualiza\u00e7\u00e3o de reembolso, que continha um campo de tipo, descrevendo, na verdade, em que consistia essa atualiza\u00e7\u00e3o. A partir disso, tivemos \"\u00f3timos\" switches com validadores que indicavam como validar esse evento com esse tipo.<\/p>\n<h4>Versionamento de eventos<\/h4>\n<p>\nPara valida\u00e7\u00e3o de mensagens no Kafka, pode-se usar <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.confluent.io\/current\/schema-registry\/docs\/index.html\">Avro<\/a><\/noindex>, mas era necess\u00e1rio planejar isso desde o in\u00edcio e usar o Confluent. No nosso caso com o versionamento, precisamos ser cautelosos. Nem sempre ser\u00e1 poss\u00edvel reler mensagens do log de replica\u00e7\u00e3o, porque o modelo 'muda'. No geral, tentamos construir vers\u00f5es para que o modelo fosse retrocompat\u00edvel: por exemplo, tornar um campo temporariamente n\u00e3o obrigat\u00f3rio. Se as diferen\u00e7as forem muito grandes, come\u00e7amos a escrever em um novo t\u00f3pico e os clientes s\u00e3o migrados quando terminarem de ler o antigo.<\/p>\n<h4>Garantia de ordem de leitura das partitions<\/h4>\n<p>\nOs t\u00f3picos dentro do Kafka s\u00e3o divididos em partitions. Isso n\u00e3o \u00e9 muito importante enquanto projetamos entidades e trocas, mas \u00e9 crucial quando decidimos como consumir e escalar.<\/p>\n<p>Em uma situa\u00e7\u00e3o normal, voc\u00ea escreve um t\u00f3pico no Kafka. Por padr\u00e3o, \u00e9 usada uma \u00fanica parti\u00e7\u00e3o, e todas as mensagens desse t\u00f3pico v\u00e3o para ela. E o consumidor, consequentemente, l\u00ea essas mensagens de forma sequencial. Agora, suponha que precisamos expandir o sistema para que as mensagens sejam lidas por dois consumidores diferentes. Se voc\u00ea, por exemplo, est\u00e1 enviando um SMS, voc\u00ea pode pedir ao Kafka para criar uma parti\u00e7\u00e3o adicional, e o Kafka come\u00e7ar\u00e1 a dividir as mensagens em duas partes \u2013 metade para uma, metade para a outra. <\/p>\n<p>Como o Kafka as divide? Cada mensagem tem um corpo (onde armazenamos o JSON) e uma chave. A este chave pode ser aplicada uma fun\u00e7\u00e3o hash, que determinar\u00e1 em qual parti\u00e7\u00e3o a mensagem ir\u00e1.<\/p>\n<p>No nosso caso com reembolsos, isso \u00e9 importante, pois se pegarmos duas parti\u00e7\u00f5es, h\u00e1 uma chance de que um consumidor paralelo processe o segundo evento antes do primeiro, e isso causaria problemas. A fun\u00e7\u00e3o hash garante que mensagens com a mesma chave ir\u00e3o para a mesma parti\u00e7\u00e3o. <\/p>\n<h4>Eventos versus comandos<\/h4>\n<p>\nEsse \u00e9 mais um problema com o qual nos deparamos. Um evento \u00e9 algo que ocorreu: dizemos que algo aconteceu (something_happened), por exemplo, um item foi cancelado ou houve um reembolso. Se algu\u00e9m est\u00e1 ouvindo esses eventos, ent\u00e3o, ao ocorrer um \"item cancelado\", a entidade de reembolso ser\u00e1 criada, e \"houve um reembolso\" ser\u00e1 registrado em algum lugar nas configura\u00e7\u00f5es.<\/p>\n<p>Mas geralmente, quando voc\u00ea projeta eventos, voc\u00ea n\u00e3o quer escrev\u00ea-los em v\u00e3o - voc\u00ea conta que algu\u00e9m ir\u00e1 l\u00ea-los. H\u00e1 uma grande tenta\u00e7\u00e3o de n\u00e3o escrever algo_happened (item_canceled, refund_refunded), mas algo_should_be_done. Por exemplo, item pronto para retorno.<\/p>\n<p>Por um lado, isso sugere como o evento ser\u00e1 utilizado. Por outro lado, isso se parece muito menos com um t\u00edtulo normal de evento. Al\u00e9m disso, n\u00e3o est\u00e1 longe da ordem do_do_something. Mas voc\u00ea n\u00e3o tem garantia de que o evento foi lido por algu\u00e9m; e se foi lido, foi lido com sucesso; e se foi lido com sucesso, algo foi feito, e isso algo foi bem-sucedido. No momento em que o evento se torna do_something, um feedback se torna necess\u00e1rio, e isso \u00e9 um problema.<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/b755d91208092bd9791a41ce4633fb48.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNa troca ass\u00edncrona no RabbitMQ, quando voc\u00ea l\u00ea uma mensagem, vai at\u00e9 o http, e voc\u00ea tem uma resposta - pelo menos que a mensagem foi recebida. Quando voc\u00ea grava no Kafka, h\u00e1 uma mensagem de que voc\u00ea gravou no Kafka, mas sobre como isso foi processado, voc\u00ea n\u00e3o sabe nada. <\/p>\n<p>Portanto, no nosso caso, tivemos que introduzir um evento de resposta e configurar a monitora\u00e7\u00e3o para que, se ocorrerem certos eventos em um determinado per\u00edodo, deve haver o mesmo n\u00famero de eventos de resposta. Se isso n\u00e3o acontecer, parece que algo deu errado. Por exemplo, se enviamos o evento 'item_ready_to_refund', esperamos que o reembolso seja criado, o cliente receba o dinheiro de volta e um evento 'money_refunded' ocorra. Mas isso n\u00e3o \u00e9 exato, portanto, precisamos de monitora\u00e7\u00e3o.<\/p>\n<h3>Nuances<\/h3>\n<p>\nH\u00e1 um problema bastante \u00f3bvio: se voc\u00ea est\u00e1 lendo do t\u00f3pico sequencialmente e recebe uma mensagem errada, o consumidor falha e voc\u00ea n\u00e3o avan\u00e7a. Voc\u00ea precisa <strong>parar todos os consumidores<\/strong>, fazer o commit do offset para continuar lendo.<\/p>\n<p>Sab\u00edamos disso, previmos, e mesmo assim aconteceu. Isso ocorreu porque o evento era v\u00e1lido do ponto de vista do events-bus, o evento era v\u00e1lido na perspectiva do validador da aplica\u00e7\u00e3o, mas n\u00e3o era v\u00e1lido sob o ponto de vista do PostgreSQL, pois temos um sistema MySQL com UNSIGNED INT, e no sistema rec\u00e9m-criado, era apenas com INT no PostgreSQL. Seu tamanho \u00e9 um pouco menor e o ID n\u00e3o coube. O Symfony falhou com uma exce\u00e7\u00e3o. N\u00f3s, claro, capturamos a exce\u00e7\u00e3o, porque a previmos e est\u00e1vamos planejando fazer o commit desse offset, mas antes quer\u00edamos incrementar o contador de problemas, j\u00e1 que a mensagem foi processada com falha. Os contadores nesse projeto tamb\u00e9m est\u00e3o no banco de dados, e o Symfony j\u00e1 havia encerrado a comunica\u00e7\u00e3o com o banco, e uma segunda exce\u00e7\u00e3o destruiu todo o processo, sem chances de fazer o commit do offset.<\/p>\n<p>O servi\u00e7o ficou parado por um tempo \u2014 felizmente, com o Kafka isso n\u00e3o \u00e9 t\u00e3o grave, pois as mensagens permanecem. Quando o trabalho for restabelecido, ser\u00e1 poss\u00edvel ler os dados restantes. \u00c9 pr\u00e1tico.<\/p>\n<p>O Kafka tem a capacidade, atrav\u00e9s das ferramentas, de definir um offset arbitr\u00e1rio. Mas, para fazer isso, \u00e9 necess\u00e1rio parar todos os consumidores \u2014 no nosso caso, preparar uma vers\u00e3o separada, na qual n\u00e3o haver\u00e1 consumidores, para realizar o redeployment. Ent\u00e3o, no Kafka, atrav\u00e9s das ferramentas, \u00e9 poss\u00edvel mover o offset, e a mensagem passar\u00e1.<\/p>\n<p>Outro detalhe \u2014 <strong>replication log vs rdkafka.so<\/strong>\u00a0\u2014 est\u00e1 relacionado \u00e0 especificidade do nosso projeto. Temos PHP, e no PHP, como regra, todas as bibliotecas se comunicam com Kafka atrav\u00e9s do reposit\u00f3rio rdkafka.so, e depois vem algum tipo de wrapper. Pode ser que sejam nossas dificuldades pessoais, mas descobrimos que reler um trecho j\u00e1 lido n\u00e3o \u00e9 t\u00e3o simples assim. Em resumo, tivemos problemas de software.<\/p>\n<p>Voltando \u00e0s peculiaridades do trabalho com partitions, est\u00e1 escrito diretamente na documenta\u00e7\u00e3o <strong>consumers &gt;= topic partitions<\/strong>. Mas soube disso muito depois do que gostaria. Se voc\u00ea deseja escalar e ter dois consumidores, precisa de pelo menos duas partitions. Ou seja, se voc\u00ea tinha uma partition com 20 mil mensagens acumuladas, criar uma nova n\u00e3o far\u00e1 com que as mensagens sejam distribu\u00eddas igualmente t\u00e3o cedo. Portanto, para ter dois consumidores paralelos, \u00e9 necess\u00e1rio entender sobre partitions.<\/p>\n<h2>Monitoramento<\/h2>\n<p>\nAcredito que, pelo modo como monitoramos, ficar\u00e1 ainda mais claro quais problemas existem na abordagem atual.<\/p>\n<p>Por exemplo, contamos quantos produtos na base mudaram de status recentemente e, de acordo com essas mudan\u00e7as, eventos devem ter ocorrido, e enviamos esse n\u00famero para nosso sistema de monitoramento. Depois, do Kafka, recebemos um segundo n\u00famero, que \u00e9 quantos eventos realmente foram registrados. \u00c9 \u00f3bvio que a diferen\u00e7a entre esses dois n\u00fameros deve ser sempre zero.<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/07d8ed08514f2fb97d9019466b96342c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAl\u00e9m disso, \u00e9 necess\u00e1rio monitorar como est\u00e3o as coisas com o produtor, se o events-bus recebeu as mensagens, e como est\u00e1 o consumidor. Por exemplo, nos gr\u00e1ficos abaixo, o Refund Tool est\u00e1 funcionando bem, mas o BOB claramente apresenta alguns problemas (picos azuis).<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/57112dbe70d2b388c53f19c34cca6f63.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nJ\u00e1 mencionei o consumer-group lag. Grosso modo, isso \u00e9 o n\u00famero de mensagens n\u00e3o lidas. De maneira geral, nossos consumidores operam rapidamente, por isso o lag costuma estar em 0, mas \u00e0s vezes pode haver picos moment\u00e2neos. Kafka faz isso de maneira nativa, mas \u00e9 necess\u00e1rio definir algum intervalo. <\/p>\n<p>H\u00e1 um projeto <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/linkedin\/Burrow\">Burrow<\/a><\/noindex>, que fornecer\u00e1 mais informa\u00e7\u00f5es sobre Kafka. Ele simplesmente retorna o status por API do consumer-group, como est\u00e1 o desempenho desse grupo. Al\u00e9m de OK e Failed, h\u00e1 um aviso, e voc\u00ea poder\u00e1 saber que seus consumidores n\u00e3o est\u00e3o acompanhando o ritmo de produ\u00e7\u00e3o \u2014 n\u00e3o conseguem ler o que est\u00e1 sendo escrito. O sistema \u00e9 bastante inteligente e \u00e9 f\u00e1cil de usar. <\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/cece8495801e187b155487a802e1a35a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEsta \u00e9 a apar\u00eancia da resposta pela API. Aqui, o grupo bob-live-fifa, partition refund.update.v1, status OK, lag 0 \u2014 o \u00faltimo offset final \u00e9 este.<\/p>\n<p><img decoding=\"async\" alt=\"Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrona usando Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/1538b2ccc390e9b83075f54566f1039b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMonitoramento <strong>updated_at SLA (stuck)<\/strong> J\u00e1 mencionei. Por exemplo, o produto mudou para o status de que est\u00e1 pronto para devolu\u00e7\u00e3o. Definimos um Cron que indica que se, em 5 minutos, esse objeto n\u00e3o mudar para refund (devolvemos o dinheiro pelos sistemas de pagamento muito rapidamente), ent\u00e3o algo definitivamente deu errado e \u00e9 certamente um caso para o suporte. Portanto, basta usar o Cron que verifica essas situa\u00e7\u00f5es e, se houver mais de 0, envia um alerta.<\/p>\n<p><b>Em resumo, usar eventos \u00e9 conveniente quando<\/b>:<\/p>\n<ul>\n<li>a informa\u00e7\u00e3o \u00e9 necess\u00e1ria para v\u00e1rios sistemas;<\/li>\n<li>o resultado do processamento n\u00e3o \u00e9 importante;<\/li>\n<li>h\u00e1 poucos eventos ou eventos s\u00e3o pequenos. <\/li>\n<\/ul>\n<blockquote><p>\u00c0 primeira vista, o artigo tem um tema bastante espec\u00edfico - API ass\u00edncrono em Kafka, mas em rela\u00e7\u00e3o a ele, gostaria de recomendar muitas coisas imediatamente.<br \/>\nPrimeiramente, o pr\u00f3ximo <noindex><a rel=\"nofollow\" href=\"https:\/\/www.highload.ru\/\">HighLoad++<\/a><\/noindex> n\u00e3o precisa esperar at\u00e9 novembro, j\u00e1 em abril teremos sua vers\u00e3o em S\u00e3o Petersburgo, e em junho vamos falar sobre altas cargas em Novosibirsk.<br \/>\nEm segundo lugar, o autor da apresenta\u00e7\u00e3o, Sergey Zaika, faz parte do Comit\u00ea Program\u00e1tico da nossa nova confer\u00eancia sobre gest\u00e3o do conhecimento. <noindex><a rel=\"nofollow\" href=\"https:\/\/knowledgeconf.ru\/2019\">KnowledgeConf<\/a><\/noindex>A confer\u00eancia ser\u00e1 de um dia, ocorrer\u00e1 em 26 de abril, mas seu programa ser\u00e1 muito intenso.<br \/>\nE em maio teremos <noindex><a rel=\"nofollow\" href=\"https:\/\/phprussia.ru\/2019\">PHP Russia<\/a><\/noindex> e\u00a0<noindex><a rel=\"nofollow\" href=\"https:\/\/ritfest.ru\/2019\">RIT++<\/a><\/noindex> (com DevOpsConf inclu\u00eddo) - ainda \u00e9 poss\u00edvel sugerir seu pr\u00f3prio tema, compartilhar sua experi\u00eancia e reclamar sobre suas dificuldades.<\/p><\/blockquote>\n<p>Fonte: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/oleg-bunin\/blog\/445424\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441\u00a0\u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438\u00a0\u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434? \u041c\u043e\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u043d\u0430\u044f: \u043e\u0442\u00a0\u0437\u0430\u043a\u043e\u043d\u043e\u0434\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u00a0\u043f\u0440\u0438\u0441\u0443\u0449\u0435\u0433\u043e \u0432\u0441\u0435\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u044f \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u041d\u043e\u00a0\u044d\u0442\u043e \u0432\u043e\u0432\u0441\u0435 \u043d\u0435\u00a0\u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u00a0\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0432\u044b\u0433\u043e\u0434\u0443. \u0412\u00a0\u0447\u0435\u043c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0438\u0433\u0440\u0430\u0442\u044c, \u0435\u0441\u043b\u0438 \u0432\u043d\u0435\u0434\u0440\u0438\u0442\u044c events-driven API \u043d\u0430\u00a0Kafka, \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u0442 \u0421\u0435\u0440\u0433\u0435\u0439 \u0417\u0430\u0438\u043a\u0430 (fewald). \u041f\u0440\u043e \u043d\u0430\u0431\u0438\u0442\u044b\u0435 \u0448\u0438\u0448\u043a\u0438 \u0438\u00a0\u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0442\u043e\u0436\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":22763,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-30778","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.3 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"pt_PT\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Experi\u00eancia no desenvolvimento do servi\u00e7o Refund Tool com API ass\u00edncrono em Kafka | ProHoster","description":"O que pode fazer uma empresa t\u00e3o grande como a Lamoda, com processos bem estabelecidos e dezenas de servi\u00e7os interconectados, mudar significativamente sua abordagem?","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pt_PT","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster","og:description":"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?","og:url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:37:23+00:00","article:modified_time":"2019-10-31T18:37:23+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"30778","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 02:58:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 03:29:56","updated":"2026-01-21 02:58:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/30778","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=30778"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/30778\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media\/22763"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=30778"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=30778"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=30778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}