Este projeto tem o intuito de servir como meio de cadastros de Veículos, Usuários e Aluguel de Veículos por esses usuários.
Acima, podemos ver a arquitetura que foi desenhada e implementada no código deste repositório.
Optei por usar o RabbitMQ como broker, com a ideia de trabalhar de forma assíncrona e distribuída. Adotei a estratégia de separar a escrita da leitura, a fim de que a escrita e a leitura não sejam concorrentes entre si. O banco escolhido para a escrita foi o PostgreSQL, e o banco de leitura escolhido foi o MongoDB.
Abaixo, explicarei um pouco mais sobre cada projeto, mas primeiro gostaria de explicar como se dá o fluxo de input de dados no sistema.
Por exemplo, ao receber o comando CreateVehiclesCommand, responsável por criar um Veículo, seu receptor salva os dados enviados neste comando na tabela Commands e dispara o evento CreateVehiclesEvent, que, por sua vez, é responsável por salvar os dados dos veículos na tabela Veículos. O receptor do evento CreateVehiclesEvent valida se o conjunto de dados recebidos está respeitando as regras de negócio do domínio Veículo antes de persistir a informação na tabela Veículos.
Caso esteja respeitando todas as regras de negócio do domínio Veículo, esse registro será salvo e um novo evento CreateVehiclesProjectionEvent será disparado. O receptor deste evento CreateVehiclesProjectionEvent é responsável por obter os dados referentes ao Veículo salvo anteriormente no banco de escrita e copiar esses dados do banco de escrita para o banco de leitura.
Esta aplicação é a nossa porta de entrada, é através dela que inputamos os dados dos domínios acima citados. Este projeto não possui uma grande quantidade de regras de negócio, pois ele serve apenas como uma ponte entre o usuário e os domínios de Veículo, Usuário e Aluguel de Veículos. Como dito acima, por ter optado por trabalhar de maneira assíncrona e distribuída, este projeto recebe comandos através dos verbos POST, PUT e DELETE e os envia para o broker, acessando os dados salvos em nosso banco de leitura através do verbo GET.
Esta aplicação é responsável por receber os comandos e salvá-los em nosso banco de escrita, além de disparar o evento de consolidação deste domínio em sua respectiva tabela no banco de escrita.
Esta aplicação é responsável por receber os eventos de consolidação dos domínios e salvá-los em nosso banco de escrita, caso estejam respeitando as regras de negócio. Se o evento respeitar todas as regras, seu receptor dispara o evento de consolidação deste domínio em sua respectiva tabela no banco de leitura. Caso contrário, o processo é interrompido.
Esta aplicação é responsável por consolidar os dados dos eventos em nosso banco de escrita e por consolidar esses mesmos dados dos eventos salvos em nosso banco de escrita em nosso banco de leitura.
Este projeto é de suma importância, pois vai fornecer insumos para que o endpoint api/event/{sagaId} funcione.
NOTA:
As aplicações
Rent.Vehicles.Consumer - Commands,Rent.Vehicles.Consumer - EventseRent.Vehicles.Consumer - Events/Events Projectionsão deploys do mesmo projetoRent.Vehicles.Consumer. O que as diferencia é um conjunto de parâmetros que são passados em sua inicialização. Segue abaixo uma imagem do conjunto de dados que pode ser passado para o projetoRent.Vehicles.Consumer.
Para efetuar o aluguel de algum Veiculo é necessário passar o id do plano de aluguel, para isso liste os planos através do endpoint api/rent/rentalplanes
Todos os endpoints que utilizam os verbos POST,PUT,DELETE, retornam o status code 202 acompanhado por um response headers como o abaixo
Para saber o status dos eventos, basta fazer um requisição get para o endpoint retornado no header location
docker compose -f docker-compose.yaml -f docker-compose-infra.yaml up -dCertifique-se de que as dependências estejam de pé:
docker compose -f docker-compose-infra.yaml up -dApós rodar o commando acima, rode o comando:
dotnet testOs veículos do ano 2024 também estão sendo salvos na tabela vehiclesforspecificyearprojections no banco NoSql
A fim de reduzir a replicação de código e manter o comportamento das aplicações coeso entre si, optei por utilizar a interface ISerializer. Desta forma, nos momentos em que preciso serializar ou desserializar o dado, não preciso da implementação concreta do método que faz tais ações. Dessa maneira, deixo meu código flexível e aberto a mudanças de comportamento sem ter que alterar os códigos que dependem desta interface.
O mesmo se aplica para a interface IConsumer. Consigo, através dela, alterar o comportamento e até mesmo o broker que estamos utilizando sem ter que alterar o código fonte.
NOTA:
É importante lembrar que o código concreto que a interface
IConsumervenha a abstrair necessita bloquear a thread em que está contido, pois a abordagem adotada deEvent Loop Consumerpreconiza essa afirmação. Abaixo podemos ver um exemplo:Neste caso, a thread ficará aguardando o recebimento de alguma mensagem, sem causar qualquer prejuízo de performance ou provocar algum loop infinito.
Bibliotecas que dão suporte a esta abordagem.


