Assim como várias outras start-ups, a Lunar Way iniciou com uma aplicação monolítica no seu backend, desenvolvida em Rails. Após sua entrada no mercado há 3 anos atrás, por razões técnicas e organizacionais, o time decidiu iniciar a migração para uma arquitetura orientada a microservices. Essa escolha gerou uma arquitetura event-driven composta por 75 microservices, utilizando mensagens assíncronas como seu principal padrão de comunicação.
Na recém realizada MicroCPH 2019 em Copenhagen, Thomas Bøgh Fangel e Emil Krog Ingerslev, ambos da fintech Lunar Way, que oferece serviços bancários mobile, descrevem os desafios encontrados em sua jornada na nova arquitetura. No ano passado o time decidiu migrar sua arquitetura para event-sourcing para resolver antigos problemas e garantir a consistência da plataforma. Em sua apresentação eles discutem os 4 principais desafios do projeto e como foram resolvidos, utilizando event-sourcing e event-streams.
Problema: Consistência
O primeiro problema é a consistência. Quando um serviço está fazendo um alteração em sua base de dados, um evento deve ser publicado na fila de mensagens, mas se o serviço parar de funcionar logo após a atualização do registro, o evento não é publicado - uma estratégia de entrega de mensagens de tudo ou nada. Este fato leva a um problema de consistência onde nem o produtor do evento ou o consumidor estão livres. Este evento também pode gerar tarefas de suporte que são realizadas reenviando eventos ou manualmente atualizando os dados no serviço consumidor.
A solução encontrada foi gerar a mudança de estado e a publicação de uma mensagem em uma única e atômica operação. A ideia foi resolver este problema utilizando event-sourcing, onde cada evento representa uma alteração de estado, que o torna um processo atômico. Ingerslev aponta que este é um evento interno do serviço e assim precisa ser publicado. Lendo cada evento, publicando e salvando um ponteiro, garantindo que todos os eventos são publicados externamente. Se o serviço quebrar ou reiniciar, ele continuará publicando eventos, inicializando de onde parou - entregando pelo menos uma vez.
Adicionando um novo serviço
O próximo problema era adicionar um novo serviço. Antes de iniciar, o serviço precisa da informação para outros serviços, mas a leitura dos eventos anteriores pode gerar uma falha na consistência dos dados. A solução é a utilização novamente de event-sourcing com eventos ordenados, com a possibilidade de releitura. Adicionando uma API no início do stream de eventos é possível habilitar um consumidor para ler todos os eventos de um tipo específico desde o início.
Auto Correção
O terceiro desafio era quebrar os microsserviços.O serviço recebe o evento, porém se o evento é perdido por alguma razão, o serviço consumidor não recebe a informação e acaba tornando o estado inconsistente. Para resolver isso, o consumidor precisa saber quando um evento foi perdido e ser capaz de receber esta informação. A solução inicial de entregar novamente os eventos solucionou o problema e foi implementada em todos os pontos do event-setrem, não apenas no início. A mesma técnica com o ponteiro foi utilizada aqui: quando o consumidor sabe o último evento recebido e a ordem dos eventos, ele pode detectar se o próximo evento está fora de ordem. O problema é quando o evento perdido não é detectado até a chegada de novos eventos, mas isso pode ser mitigado solicitando aos outros serviços os últimos eventos indicados pelo ponteiro.
Alterações
O último tópico é sobre alterações. O serviço pode ser alterado internamente, mas deve evitar a quebra da comunicação com outros serviços ou requerer uma série de migrações estilo "big bang" em uma série de serviços. Na sua primeira implementação eles chegaram ao consenso com os desenvolvedores que apenas seriam adicionadas informações aos eventos. e quando eles tinham que alterar a estrutura de um evento, também faziam migrações coordenadas de todos os serviços envolvidos. Na nova solução baseada em event-sourcing, eles usaram eventos para integrações. Existe somente um evento em cada serviço, mas é possível ter múltiplas projeções baseadas na evolução de cada modelo de dados. Consumidores podem iniciar tardiamente a leitura de um novo stream de integração e ter uma nova projeção. Desta maneira as migrações podem ser feitas sem a coordenação entre outros serviços.
Ingerslev resume que não existem muitas bibliotecas para event-sourcing disponíveis. Este trabalho levou o time a construir seu próprio framework, gerando um custo elevado. Ele destaca que utilizar um novo paradigma de design de serviços em todos os times é uma tarefa difícil e que custa tempo, mas enfatiza que o uso de padrões de event-sourcing trouxe benefícios para a customização e é de fácil evolução. Fangel conclui que houve um padrão percebido durante o trabalho realizado, onde eles saíram de uma situação problemática atuando de forma especial ou manual para casos específicos até a forma normal de operação do serviço e isso foi possível pois os desenvolvedores estavam focados no mais importante - o modelo de negócios.
As apresentações da conferência foram gravadas e estarão disponíveis nas próximas semanas.