
Compartilhar:
Sou ator formado, com uma dissertação sobre stand-up comedy, e comecei a me dedicar ao desenvolvimento em PHP por meio dos encontros da comunidade. Você pode me encontrar dando palestras e escrevendo sobre tecnologia, ou ouvindo e comprando discos curiosos da minha coleção de vinil.
Devo usar o Event Source?
Tempo de leitura: 7 minutos
Se a década de 2020 demonstrou alguma coisa no mundo da tecnologia, a palavra “blockchain” certamente está entre as que suscitaram uma infinidade de interpretações positivas e negativas. Se considerarmos os aspectos “bons” ou “úteis” de uma blockchain, eles costumam estar ligados ao aproveitamento das vantagens de sua natureza imutável. O que passou, passou.
Então, você tem uma ideia e pensa: “a blockchain seria perfeita para isso”, mas também precisa reconhecer o elefante na sala: a web3, as blockchains e a tecnologia de criptomoedas são tecnologias difíceis de integrar. Se você vem de uma abordagem tradicional em engenharia de software, boa notícia! Existe um padrão de projeto de dados que já existe há bastante tempo (principalmente em áreas como finanças) e que adota uma abordagem semelhante para rastrear o histórico dos dados. Neste artigo, vamos explorar o Event Sourcing.
As origens do Event Sourcing
All Together Now: Data is the new OilO conceito de Event Sourcing surgiu da necessidade de registrar dados de forma transacional e de acompanhar as transformações que ocorrem nos dados ao longo do tempo. Instituições como bancos precisariam ser capazes de visualizar um “instantâneo” de como uma determinada entidade se apresentava em um momento preciso. Utilizando o conceito de Domain-Driven-Design (DDD), o surgimento mais formal do Event Sourcing ocorreu com o lançamento de Segregação de Responsabilidades entre Comando e Consulta (ou CQRS), comumente utilizada em ambientes baseados na Microsoft. Essa segregação não se concentra nos dados, mas nas ações que ocorrem quando seu estado muda. Essas ações são registradas como entidades imutáveis, e é esse padrão que define o Event Sourcing.
Uma aplicação típica na área financeira
It's a Bull Market, but how do you audit it?Tomemos, por exemplo, algo simples como o saldo de uma conta. Em uma configuração tradicional, você teria algo como um banco de dados relacional e um aplicativo que extrairia dados dessa conta por meio de uma instrução SQL pré-definida. O valor do saldo poderia ser obtido tanto se as transações mantivessem o saldo da conta atualizado a cada transação, quanto se o aplicativo tentasse agregar todos os dados. É aí que entra o Event Sourcing: e se você tiver milhões de linhas e quiser ver o status de um saldo em um momento exato no passado? Você precisa torcer para que todos os dados transacionais estejam estejam corretos, e que nenhuma migração posterior tenha ocorrido, ou que não tenha havido uma interrupção exatamente no momento em que se tenta incluir o valor total do saldo em um registro de transação.
Com o Event Sourcing, você “reproduz o histórico”, pois os dados armazenados referem-se às alterações no estado dos dados. Portanto, para aplicar isso, você criaria alguns modelos de Event personalizados com nomes adequados, como, por exemplo:
class AccountCreatedEvent extends Event
class AccountDepositEvent extends Event
class AccountWithdrawEvent extends EventEssa série de eventos é chamada de um fluxo. A maneira de lidar com a obtenção do saldo é por meio de um agregador, que se chama um projetor. Uma classe projetora receberia um argumento e, em seguida, leria os eventos (consultados anteriormente por meio de uma chave primária, como o ID da conta) que lhe fossem passados para calcular o estado:
class AccountBalanceProjector {
public static int projectBalance(List<Event> events) {
int balance = 0;
boolean accountCreated = false;
for (Event event : events) {
if (event instanceof AccountCreatedEvent) {
balance = ((AccountCreatedEvent) event).initialBalance;
accountCreated = true;
} else if (accountCreated && event instanceof AccountDepositEvent) {
balance += ((AccountDepositEvent) event).amount;
} else if (accountCreated && event instanceof AccountWithdrawEvent) {
balance -= ((AccountWithdrawEvent) event).amount;
}
}
return balance;
}
}E é isso aí. Você teria um método no seu Mapeador Objeto-Relacional (ORM) que recuperaria todos os eventos de um accountIde, em seguida, passaria esses eventos para AccountBalanceProjector.projectBalance(events), e aí vem o seu saldo. Não é extraído um saldo que já tenha sido pré-criado em algum lugar, mas sim recriado.
Utilização do Event Sourcing com os dados da Vonage
Controlling Data Can Be TrickyVamos ver um exemplo usando dados gerados durante o uso do Vonage. Analisaremos a Voice API, que gera um webhook de status de evento sempre que ocorre uma ação durante uma chamada de voz. De acordo com documentação sobre Webhooks da Voice API, os seguintes estados podem ser atribuídos:
Se eu inserir esses estados na IA e pedir ao prompt para gerar minhas classes, posso obter um toque mágico de automação para gerar minha lista de eventos:
public class CallStarted extends BaseVoiceEvent {
public CallStarted(String callId) { super(callId); }
@Override public String getName() { return "started"; }
}
public class CallRinging extends BaseVoiceEvent {
public CallRinging(String callId) { super(callId); }
@Override public String getName() { return "ringing"; }
}
public class CallAnswered extends BaseVoiceEvent {
private String answerType;
public CallAnswered(String callId, String answerType) {
super(callId);
this.answerType = answerType;
}
@Override public String getName() { return "answered"; }
@Override public Map<String, Object> toMap() {
Map<String, Object> map = super.toMap();
map.put("answer_type", answerType);
return map;
}
}
public class CallBusy extends BaseVoiceEvent {
public CallBusy(String callId) { super(callId); }
@Override public String getName() { return "busy"; }
}
public class CallCancelled extends BaseVoiceEvent {
public CallCancelled(String callId) { super(callId); }
@Override public String getName() { return "cancelled"; }
}
public class CallUnanswered extends BaseVoiceEvent {
public CallUnanswered(String callId) { super(callId); }
@Override public String getName() { return "unanswered"; }
}
public class CallDisconnected extends BaseVoiceEvent {
public CallDisconnected(String callId) { super(callId); }
@Override public String getName() { return "disconnected"; }
}
public class CallRejected extends BaseVoiceEvent {
public CallRejected(String callId) { super(callId); }
@Override public String getName() { return "rejected"; }
}
public class CallFailed extends BaseVoiceEvent {
public CallFailed(String callId) { super(callId); }
@Override public String getName() { return "failed"; }
}
public class CallHumanMachineDetected extends BaseVoiceEvent {
private String detectionResult;
public CallHumanMachineDetected(String callId, String detectionResult) {
super(callId);
this.detectionResult = detectionResult;
}
@Override public String getName() { return "human_machine"; }
@Override public Map<String, Object> toMap() {
Map<String, Object> map = super.toMap();
map.put("detection_result", detectionResult);
return map;
}
}
public class CallTimeout extends BaseVoiceEvent {
public CallTimeout(String callId) { super(callId); }
@Override public String getName() { return "timeout"; }
}
public class CallCompleted extends BaseVoiceEvent {
public CallCompleted(String callId) { super(callId); }
@Override public String getName() { return "completed"; }
}// etc., etc., omitido por motivos de extensão
Você vai perceber que esses Eventos são derivados de uma classe base, que definimos como abstrata:
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
public abstract class BaseVoiceEvent {
protected String callId;
protected Instant timestamp;
public BaseVoiceEvent(String callId) {
this.callId = callId;
this.timestamp = Instant.now();
}
public BaseVoiceEvent(String callId, Instant timestamp) {
this.callId = callId;
this.timestamp = timestamp;
}
public abstract String getName();
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("event", getName());
map.put("call_id", callId);
map.put("timestamp", timestamp.toString());
return map;
}
}Agora você já tem a estrutura inicial de uma arquitetura de Event Sourcing. As etapas finais consistiriam em implementar controladores para lidar com a criação de eventos em um endpoint HTTP do seu aplicativo e, em seguida, um Projetor de Status de Chamada. Esse projetor, semelhante ao exemplo que vimos anteriormente, seria capaz de receber um fluxo de eventos, aceitar um carimbo de data/hora opcional e, a partir disso, calcular o status correto do callID correspondente ao carimbo de data/hora fornecido (ou ao mais recente, por padrão).
Quais são as vantagens do Event Sourcing?
Arquitetura orientada a eventos pode ser refatorada. Como o sistema está desacoplado dos dados de estado que chegam, é possível reexecutar seus novos controladores com uma nova lógica (por exemplo, para lidar com um novo tipo de evento) e reconstruir seus fluxos de dados para projeção.
Registros completos de Audit, independentemente do tempo: isso é muito importante para setores altamente regulamentados, como vimos em áreas como Finanças e Saúde.
Separação entre leituras e gravações: com o CQRS mencionado anteriormente, o acesso e a gravação de dados são separados. Ao otimizar uma aplicação que exige um alto volume de entrada/saída de dados, é possível dimensionar aspectos distintos da arquitetura de acordo com a necessidade.
Quais são as desvantagens do Event Sourcing?
Eu não poderia me considerar um engenheiro sensato sem mencionar quais são as desvantagens, e elas são bem significativas
Compatibilidade com versões anteriores: Mencionei como pode ser rápido aplicar novas alterações em seus modelos e reexecutar seus manipuladores de eventos, mas não falei sobre quando algo se torna obsoleto. É preciso refletir sobre o tratamento de eventos, o versionamento semântico e os comportamentos, o que muito provavelmente levará a coleções consideráveis de dados antigos que precisarão ser armazenados, além da necessidade de realizar procedimentos complexos de ETL.
Complexidade do sistema: Esse é o maior obstáculo a ser superado. Já trabalhei com sistemas orientados a eventos que estavam em expansão no passado, e a maior parte dessa complexidade é semelhante à dos microsserviços. Se você não projetou seu sistema de eventos corretamente desde o início, posso garantir que uma enumeração alterada está apenas esperando para derrubar todo o seu sistema
Depuração: Uma das maiores frustrações que já encontrei em qualquer aspecto do design de software é quando as ferramentas de depuração adequadas não estão disponíveis. Já falei várias vezes sobre minha consternação por não ter o xdebug, de Derick Rethan, como parte de uma pilha de PHP; simplesmente não consigo imaginar como alguém possa programar sem ele, em vez de interromper a execução e exibir arrays ou strings individualmente. Da mesma forma, a depuração de microsserviços, o que me leva aos ambientes de Event Sourcing: para depurar fluxos com precisão, você provavelmente precisará escrever ferramentas personalizadas adicionais sobre o seu sistema para quando as coisas derem errado
Armazenamento: Isso pode parecer um pouco óbvio, mas, em vez de ter uma única fonte de verdade (no nosso exemplo, era o armazenamento dos webhooks da Voice API da Vonage), agora você tem várias tabelas adicionais que contêm muitos dados adicionais sendo criados além dos dados originais
Faça do seu jeito
Pelo que vimos, a estrutura padrão para fazer isso é bastante complexa. A boa notícia é que existem bibliotecas disponíveis para nos ajudar a implementá-la. Aqui estão minhas recomendações para cada linguagem de backend:
Java: Axon
PHP: Verbos (Laravel), EventSauce (genérico/Symfony)
Node.js: Kurrent
NET/C#: EventFlow
Python: origem de eventos
Ruby: Rails Event Store
Elixir: Commanded. Quer saber por que incluí o Elixir nessa lista? Você já ouviu falar do Elixir? Dê uma olhada no meu artigo sobre uma Breve História do Erlang.
Conclusão
Assim como acontece com todo novo padrão de engenharia que surge, costumo ser cético quanto à utilidade dessas coisas na prática. Já assisti a palestras sobre arquitetura hexagonal e sobre o padrão de herança inversa em Applications front-end do React, e simplesmente não consigo entender por que os desenvolvedores iriam quereriam esse nível de complexidade. No entanto, no caso do Event Sourcing, sinto que a situação é bem diferente. Sim, é difícil de implementar e arquitetar, mas, como em tudo, qual é o seu caso de uso? Se você estiver trabalhando em um mercado vertical que exija altos níveis de análise de dados, então adotar o Event Sourcing desde o início vai poupar sua empresa de possíveis grandes dores de cabeça no futuro.
Tem alguma dúvida ou quer compartilhar o que está criando?
Inscreva-se no Boletim Informativo para Desenvolvedores
Siga-nos no X (antigo Twitter) para ficar por dentro das novidades
Assista aos tutoriais no nosso canal do YouTube
Conecte-se conosco na página de desenvolvedores da Vonage no LinkedIn
Fique conectado e acompanhe as últimas notícias, dicas e eventos para desenvolvedores.
Compartilhar:
Sou ator formado, com uma dissertação sobre stand-up comedy, e comecei a me dedicar ao desenvolvimento em PHP por meio dos encontros da comunidade. Você pode me encontrar dando palestras e escrevendo sobre tecnologia, ou ouvindo e comprando discos curiosos da minha coleção de vinil.