Este documento detalha o foodtruacker, um projeto que implementa Domain-Driven Design (DDD), CQRS e Event Sourcing. Ele usa ASP.NET Core e se concentra em melhorar a capacidade de manutenção em domínios de negócios complexos. O projeto utiliza um caso de negócios fictício simplificado para fins ilustrativos. Esta explicação detalhada cobre as motivações, recursos, detalhes de implementação e tecnologias relevantes.
foodtruacker - Implementação de DDD, CQRS e Event Sourcing
Este projeto orientado a eventos utiliza princípios, estruturas e arquiteturas — todos centrados na ideia de melhorar a capacidade de manutenção ao lidar com sistemas que refletem domínios de negócios complexos. A API Web do aplicativo é construída sobre a estrutura ASP.NET Core da Microsoft e implementa Design orientado a domínio, bem como os padrões CQRS e Event Sourcing. Um caso de negócios fictício estabelece a base deste projeto e é o resultado de um workshop de tempestade de eventos.
Observação: o domínio comercial fictício introduzido neste projeto é bastante simplificado e deve ser visto apenas como um provedor de casos de uso relacionáveis.
Motivação
Como nem sempre é melhor usar operações CRUD e objetos POCO em projetos com domínios de negócios bastante complexos, decidi criar este projeto como uma implementação prática de minha pesquisa sobre — e interesse em — Design Orientado a Domínio (DDD). abordagem de desenvolvimento de software.
Como o caso de negócios fictício apresentado neste projeto é fortemente orientado a eventos, decidi implementar também os padrões CQRS e Event Sourcing. Ambos me chamaram a atenção enquanto fazia a pesquisa para esse projeto e combinam bem com o DDD.
Características
Visão geral
Este projeto consiste em um aplicativo Web API executável e vários componentes funcionais, cada um fornecido por meio de bibliotecas de classes. O código é organizado por namespaces. Em aplicativos ASP.NET Core criados no Visual Studio, os namespaces, por padrão, são criados automaticamente a partir da estrutura de pastas dos projetos. Veja o diagrama abaixo para uma visão geral da estrutura de pastas (e namespace) deste projeto:
Introdução
Tempestade de eventos
Um formato de workshop flexível para exploração colaborativa de domínios de negócios complexos, inventado por Alberto Brandolini. É uma metodologia extremamente leve para melhorar, visualizar, explorar e projetar rapidamente fluxos e processos de negócios dentro de sua organização.
O workshop consiste em um grupo de pessoas com diferentes conhecimentos que usam post-its coloridos para fazer o layout colaborativo de processos de negócios relevantes. É obrigatório para um workshop EventStorming ter as pessoas certas presentes e ter área de superfície suficiente para colocar os post-its. As pessoas necessárias normalmente incluem aquelas que sabem as perguntas a serem feitas (normalmente desenvolvedores) e aquelas que sabem as respostas (especialistas de domínio, proprietários de produtos).
O objetivo deste workshop é fazer com que os participantes aprendam uns com os outros, revelem e refutem equívocos e, por exemplo, neste projeto GitHub, criem as bases para o desenvolvimento de uma solução de software baseada em eventos que reflita um domínio de negócios correlacionado.
Design Orientado a Domínio (DDD)
Uma abordagem ao desenvolvimento de software que centra o desenvolvimento na programação de um modelo de domínio que possui uma compreensão rica dos processos e regras de um domínio de negócios correlato. O termo “Design orientado a domínio” foi cunhado por Eric Evans em seu livro de mesmo título.
O DDD visa facilitar a criação de aplicações complexas e concentra-se em três princípios fundamentais:
O livro de Eric Evans define alguns termos comuns para Domain-Driven Design:
Modelo de Domínio
Um sistema de abstrações que descreve os processos e políticas de um domínio de negócios e é usado para lidar com as tarefas necessárias associadas a esse domínio.
Linguagem onipresente
Palavras e declarações para certos elementos do domínio comercial. Para evitar novos equívocos, todos os membros da equipe devem adotar certos termos, normalmente aqueles usados pelos especialistas do domínio.
Contexto limitado
Um limite conceitual dentro do qual um modelo de domínio específico é definido e aplicável. Isso normalmente representa um subsistema ou um campo de trabalho. É principalmente uma delimitação linguística, com cada contexto delimitado tendo a sua própria Linguagem Ubíqua.
Ex: Gestão de Clientes onde um usuário é chamado de “cliente”.
O livro de Eric Evans diferencia ainda mais certas partes do modelo de domínio. Para citar alguns:
Entidade
Um objeto que é definido por sua identidade e não por seus atributos.
Ex.: Uma pessoa sempre será a mesma, não importa a escolha da jaqueta, da cor do cabelo ou do idioma falado em determinado momento.
Objeto de valor
Um objeto que é definido apenas pelo valor de seus atributos. Os objetos de valor são imutáveis e não possuem uma identidade única. Os Objetos de Valor podem ser substituídos por outros Objetos de Valor com os mesmos atributos.
Por exemplo: Ao focar em uma pessoa, um par de óculos de sol quebrado pode ser facilmente substituído por um novo par de óculos de sol com a mesma aparência.
Agregar
Um cluster de uma ou mais Entidades e Objetos de Valor opcionais, unificados para ser uma única unidade transacional. Uma Entidade formará a base do Agregado e, portanto, será declarada raiz Agregada. Todas as propriedades de suas Entidades colaboradoras e Objetos de Valor só podem ser acessíveis através desta Entidade base única. Um agregado deve estar sempre em um estado consistente. Na programação orientada a objetos, isso normalmente é feito usando setters privados e getters protegidos.
Ex.: Num contexto de venda de automóveis, um automóvel (Entidade) é definido pelo seu número de identificação do veículo. Este carro pode ter quatro rodas (Objetos de Valor), que podem precisar ser substituídas após um certo tempo.
Evento de Domínio
Um objeto criado como resultado da atividade no Modelo de Domínio. É usado para reter e encaminhar informações relacionadas a esta atividade. Os Eventos de Domínio são normalmente criados para atividades que os especialistas do domínio consideram relevantes.
Arquitetura hexagonal (portas e adaptadores)
Padrão de arquitetura utilizado no design de software, proposto por Alistair Cockburn em 2005. O padrão visa atingir um alto grau de manutenção e descreve uma aplicação em três camadas. Cada camada se comunica com as camadas adjacentes usando interfaces (portas) e implementações (adaptadores):
A regra principal neste padrão de arquitetura é que as dependências só podem apontar para dentro. Nada num círculo interno pode saber absolutamente alguma coisa sobre algo num círculo externo. Quaisquer dependências dispostas a apontar para fora, por exemplo, chamando um banco de dados da Camada de Aplicação, precisam ser instanciadas via inversão de Controle (IoC) ou Injeção de Dependência (DI).
CQRS usando MediatR (uma estrutura de mensagens pré-construída)
CQRS significa Segregação de Responsabilidade de Comando/Consulta e foi descrito pela primeira vez por Greg Young em 2010. É baseado no princípio Command Query Separation (CQS) e permite a separação de operações de leitura e gravação. O CQS declara:
A melhoria do CQRS em relação ao CQS é que esses comandos e consultas são tratados como modelos e não como métodos. Esses modelos podem ser despachados como objetos em um ponto, para então serem manipulados pelos respectivos manipuladores necessários em outro ponto do sistema, cada um retornando seus modelos de resposta para uma segregação clara de cada ação.
O padrão mediador permite implementar Comandos/Consultas e Manipuladores fracamente acoplados, utilizando um objeto mediador. Os objetos não se comunicam mais diretamente entre si, mas sim através do mediador.
A estrutura MediatR é uma implementação de código aberto do padrão mediador, criado por Jimmy Bogard. Ele será utilizado neste projeto para comunicação entre a Camada Framework e a Camada de Aplicação. Também será usado para projetar dados do banco de dados Command para o banco de dados Query.
Fonte de eventos
Um padrão de design arquitetônico para armazenar todas as alterações no estado de um aplicativo, em vez de armazenar apenas o estado atual dos dados em um domínio. Esse padrão foi introduzido por Greg Young e desde então teve inúmeras adoções.
O padrão pretende capturar cada mudança no estado de um aplicativo como um objeto de evento. Esses objetos de evento são então armazenados, na sequência de ocorrência, apenas de maneira anexada. Isto não só permite a recriação do estado atual de um objeto ao longo da sequência de eventos que aconteceram até agora, mas, em última análise, permite voltar no tempo e recriar o estado do objeto em qualquer momento.
Uma conta bancária pode ser um bom exemplo do princípio Event Sourcing. Cada vez que o dinheiro é sacado ou depositado, em vez de apenas atualizar o saldo atual, o valor do troco é registrado. O saldo atual é então calculado analisando a sequência de eventos, com as informações correspondentes sobre quanto dinheiro foi sacado ou depositado de cada vez.
O Event Sourcing funciona bem com o Domain-Driven Design, pois é uma ótima opção para armazenar eventos de domínio, acionados pelo modelo de domínio a cada solicitação de mudança.
O Event Sourcing também se beneficia muito do CQRS. Em vez de ter que fazer uma consulta no banco de dados Event Sourcing, que teria que passar por todos os eventos registrados relacionados ao objeto solicitado para recriar o estado atual, essa consulta pode ser feita em um banco de dados de consulta dedicado. Este banco de dados de consulta é atualizado por seus próprios manipuladores de eventos, ouvindo os mesmos eventos sendo despachados logo após serem anexados ao banco de dados de fornecimento de eventos. Esses processos de atualização são chamados de Projeções.
Essa separação de bancos de dados também abre caminho para um enorme potencial em escalabilidade e otimização de desempenho. Várias instâncias do banco de dados Query podem ser criadas e mantidas sincronizadas simplesmente fazendo com que seus manipuladores de eventos ouçam os eventos sendo despachados do Event Sourcing Database Client logo após ocorrer uma alteração relevante no estado de um aplicativo. A escolha do tipo de banco de dados, bem como o grau de desnormalização dos dados, otimizado por consulta, podem melhorar bastante o desempenho.
Essa atualização constante do modelo de leitura pode acontecer de forma síncrona ou assíncrona. Este último tem o custo de uma eventual consistência, com o modelo de leitura ficando fora de sincronia com o modelo de gravação por um pequeno intervalo de tempo (geralmente milissegundos).
Kernel Compartilhado
Uma biblioteca comum para a camada de domínio, que contém classes base específicas comuns de design orientado a domínio, entidades de domínio, objetos de valor, etc., que são compartilhados entre contextos limitados.
Começando
Para colocar este projeto em funcionamento como está, sinta-se à vontade para seguir estas etapas:
Pré-requisitos
Configurar
Inicie https://localhost:5001/swagger/index.html em seu navegador para visualizar a documentação do Swagger de sua API.
Use Swagger, Postman ou qualquer outro aplicativo para enviar uma solicitação POST para https://localhost:5001/api/Administration/Register para registrar sua conta de administrador inicial. Envie o seguinte objeto:
Examine o aplicativo de console ou qualquer saída reconfigurada para os logs do aplicativo. Após qualquer registro bem-sucedido de um usuário, deverá haver um link de verificação de e-mail - fornecido pelo EmailService - escrito nos logs. Copie e cole este URL em seu navegador e pressione Enter para concluir o registro. Sinta-se à vontade para alterar ou desenvolver esta implementação inadequada de um serviço de e-mail ;-)
Está tudo pronto. Faça login em seguida.
Inicie http://localhost:2113/ em seu navegador para visualizar a GUI do EventStoreDB. Abra a guia “Stream Browser” para ver todos os eventos armazenados.
Os testes podem ser executados executando:
Tecnologias
Este projeto utiliza os seguintes pacotes Technologies/NuGet:
Recursos/Leitura Recomendada
Alberto Brandolini:
https://www.eventstorming.com
Vaughn Vernon:
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_1.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_2.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_3.pdf
Alistair Cockburn:
https://web.archive.org/web/20180822100852/http://alistair.cockburn.us/Hexagonal+architecture
Robert C. Martin (Tio Bob):
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
César de la Torre, Bill Wagner, Mike Rousos:
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/
Greg Jovem
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
https://cqrs.wordpress.com/documents/building-event-storage/
https://msdn.microsoft.com/en-us/library/jj591559.aspx
Martin Fowler:
https://www.martinfowler.com/bliki/CQRS.html
Jimmy Bogard:
https://github.com/jbogard/MediatR
https://www.youtube.com/watch?v=SUiWfhAhgQw
Design orientado por domínio:
https://dddcommunity.org
https://thedomaindrivendesign.io
https://dotnetcodr.com/2013/09/12/a-model-net-web-service-based-on-domain-driven-design-part-1-introduction/
https://dotnetcodr.com/2015/10/22/domain-driven-design-with-web-api-extensions-part-1-notifications/
Arquitetura Hexagonal:
https://fideloper.com/hexagonal-architecture
https://herbertograca.com/2017/09/14/ports-adapters-architecture/
Créditos
http://www.andreavallotti.tech/en/2018/01/event-sourcing-and-cqrs-in-c/
https://www.exceptionnotfound.net/real-world-cqrs-es-with-asp-net-and-redis-part-1-overview/
https://buildplease.com/pages/fpc-1/
https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/
https://itnext.io/why-and-how-i-implemented-cqrs-and-mediator-patterns-in-a-microservice-b07034592b6d