O Equinox Project é um projeto open-source desenvolvido em ASP.NET Core que implementa uma série de tecnologias e abordagens muito utilizadas em grandes projetos.
O Equinox Project é a mais recente contribuição que eu entrego a comunidade técnica e espero que seja de grande ajuda para servir de referência nos futuros projetos escritos em ASP.NET Core.
O Equinox Project (versão 1.0) é o resultado de quase duas semanas de estudos e desenvolvimento que dediquei ao criar uma aplicação funcional implementando diversas tecnologias e abordagens na nova plataforma ASP.NET Core.
Por ser totalmente desenvolvido com .NET Core esta aplicação pode rodar em ambientes Windows, Linux e OSX (Mac).
Tecnologias/Recursos Implementados
- ASP.NET Core 1.1 (com .NET Core)
- ASP.NET MVC Core
- ASP.NET Identity Core
- Isolado do MVC e Autenticando via
Facebook ou Cadastro
- Isolado do MVC e Autenticando via
- Entity Framework Core
- AutoMapper
- .NET Core Native DI (Isolado do MVC)
- Unit of Work
- Repository e Generic Repository
- FluentValidator
Arquitetura
- Arquitetura completa com separação de responsabilidades, SOLID e Clean Code
- DDD – Domain Driven Design (Camadas e Domain Model Pattern)
- Domain Events
- Domain Notifications
- CQRS (Com consistência imediata)
- Event Sourcing
Versão 1.0
O Equinox Project na versão 1.0 implementa um cadastro de clientes (CRUD) com regras de negócio e validações de dados.
- Toda escrita de dados ocorre através de Commands e CommandHandlers que são processados por um Bus em memória (podendo ser adaptado para um Message Queue por exemplo).
- Após a execução de um Command é disparado um Evento que realiza alguma ação informativa e também é persistido na base (Event Sourcing).
- A leitura de dados ocorre de forma mais simples dispensando algumas camadas de negócio.
- Todas as ações são autorizadas pelo mecanismo do ASP.NET Identity que baseia-se em Claims para permitir a leitura e escrita dos dados.
- As validações de consistência dos dados são realizadas nos Commands e utilizam o FluentValidator como mecanismo.
- Todos os erros no processamento ou na validação dos dados são disparados através de Domain Events/Notifications e são informados ao usuário de forma personalizada.
- É possível visualizar a história da entidade através da aplicação que informa desde a criação até a exclusão as mudanças dos dados que ocorreram e o usuário que as executou.
Estes são alguns dos recursos implementados, existem diversos outros em toda extensão da aplicação. Cada um destes recursos eu irei tratar em artigos individuais em uma nova série sobre ASP.NET Core que irei iniciar muito em breve.
Aviso
- Este projeto não pretende ser uma solução definitiva para todos os cenários.
- Algumas versões utilizadas (inclusive do ASP.NET Core 1.1) estão em Beta ou Pre-Release.
- Cuidado ao utilizar este projeto na sua produção. Analise bem os riscos de ser um Early Adopter.
- Talvez você não irá precisar de muitos dos recursos implementados, procure evitar o OverEngineering
Sobre o futuro
A versão 2.0 do Equinox Project será uma aplicação bem mais extensa com os recursos a seguir:
- Aplicação completa de aluguel (Booking) utilizando Domain Model Pattern, CQRS e Event Sourcing.
- ASP.NET Identity trabalhando através de serviços WebAPI com Bearer Token
- Novo front-end
- Bancos separados para leitura e gravação de dados
- Testes de Unidade
Acompanhe os detalhes que serão atualizados no RoadMap do projeto.
Sugestões?
Tem uma boa ideia sobre implementação ou gostaria de ver algo implementado?
Sugestões e críticas serão muito bem vindas!
Por que Equinox?
O Equinócio (Equinox) é um evento astronômico em que o plano do equador da Terra passa pelo centro do Sol. Este evento ocorre duas vezes por ano em torno de 20 de Março e 23 de Setembro. Wikipedia
Equinox é também uma série de publicações (subtítulo: “The Review of Scientific Illuminism”) em forma de livro que serve como o órgão oficial da A∴A∴, uma ordem iniciática fundada por Aleister Crowley Wikipedia
Estamos Online
O projeto está publicado *orgulhosamente* no Microsoft Azure, experimente!
Código Fonte
Caso esteja interessado em conhecer mais sobre ASP.NET, DDD, Padrões de Arquitetura como CQRS, Event Sourcing e boas práticas de desenvolvimento não deixe de conferir as ementas dos meus cursos:
Vamos continuar a troca de experiências, deixe seu comentário abaixo. Se gostou e concorda com o artigo, compartilhe com seus colegas para transmitirmos o conhecimento para o máximo de pessoas possíveis.
Parabéns pela iniciativa, sempre ajudando a comunidade e ensinando a fazer as coisas de modo correto
Muito obrigado Paulo!
Parabéns Eduardo. Não conhecia seu trabalho até o momento. Vou começar a acompanhar.
Muito obrigado Renan! Tem muita coisa já feita e muito mais por vir 😉
Olá Eduardo,
Simplesmente fantástico!!!
Um abraço.
Muito obrigado Douglas! Abs!
Incentivando os demais e disseminando conhecimento, muito boa a sua contribuição com a comunidade.
Muito obrigado Thiago!
Parabéns Eduardo Pires pela sua iniciativa, como sempre nos ajudando bastante.
E a iniciativa é bem promissora.
Muito obrigado Legolas!
Parabéns Eduardo, este projeto sem dúvida auxiliará muitos desenvolvedores que procuram se aprofundar no DDD. Uma única dúvida, o Equinox aborda a utilização de Bounded Context?
Muito obrigado Carlos! Aborda sim, ele possui um Shared Kernel (Domain.Core) e ele mesmo é um BC. Está simples, mas está no caminho certo para crescer.
Obrigado pelo feedback e parabéns novamente.
Parabens! Show o projeto!!
Muito obrigado Luis!
Bom trabalho! Show!
Muito obrigado
Legal Eduardo.
Por que não utilizou MediatR para notification?
Sobre o asp.net identity com bearer token, por que não implementa um server hibrido com identity server? ou até mesmo separado, já que você já separou o asp.net identity
O que quis dizer com 2 bancos de dados?
Ficou show o projeto, espero que de continuidade
Olá Rodrigo!
MediatR irei implementar quando os serviços REST fizerem o papel da camada de Application, por hora não achei necessário.
É uma boa ideia, deixar o projeto preparado para o Identity Server é uma boa.
No CQRS nós temos bancos de leitura e de escrita separados, seria esta a ideia.
Abs!
Ia ser lindo ter mediatr para notifications e identityserver 😛
Ah, um cachemanager também seria bom hehe
Sobre o CQRS, então, ele deixa 1 banco desnormalizado para leitura? e um banco normalizado para escrita
Excelente material para estudar! TFA
Obrigado Tiozão!
TFA
Parabéns! Excelente material para estudar! Obrigado!
Obrigado Ademilson!
Abs!
Curti demais…. vou dar um star lá no Git e vou depois dar um fork nesse projeto!! Show de bola Eduardo!!
Muito obrigado Glaucia! Se tiver uma boa ideia pode fazer pull-requests 🙂
Abs!
Muito bom o projeto, tenho uma dúvida, o CustomerCommandHandler sería o Service? Como poderia fazer os teste desta classe?
Olá Dariel, não seria bem o service, mas atua neste papel também.
Uma vez que delegamos as ações para os comandos é neste formato que trabalhamos.
É fácil testar, por ex mockando através do IBus.
Obrigado pela aclaração, tem data de inicio para o curso de ASP com Angular?
Inicialmente Parabéns pelo pelo projeto, está incrível! Agora tenho uma dúvida… Com o EF Core não é mais possível criar uma classe com as configurações das entidades?
Muito obrigado Diego!
Ainda não, mas acredito que no futuro será possível.
Abs!
Eu fiz aqui no meu projeto, da mesma forma que seu outro projeto, quando trabalhava DDD no Framework 4.6
Provavelmente usando ASP.NET Core com Full Framework, nesse projeto é tudo .NET Core
Vou passar aqui como fiz, mas a princípio, tudo .NET Core.
public class BusinessFunctionConfig
{
public BusinessFunctionConfig(EntityTypeBuilder businessFunction)
{
businessFunction.HasKey(c => c.Id);
businessFunction.Property(c => c.Id).ValueGeneratedOnAdd();
businessFunction.Property(c => c.Name)
.IsRequired()
.HasMaxLength(100);
businessFunction.Property(c => c.Code)
.IsRequired();
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
new BusinessFunctionConfig(modelBuilder.Entity());
}
Que código lindo!
Cara você é o melhor! Eu já vi exemplos conceituais, mas tudo muito incompleto. É nessa hora que separamos os homens dos garotos kkk!
Parabéns!
Opa! Obrigadão 😀
Projeto massa Eduardo!
Já dei a minha estrela e fiz um fork vamos que vamos em 2017!
Boa!!! Vamos nessa que tem muita coisa por vir!
Abs!
Como posso inicializar o:
InMemoryBus.ContainerAccessor = () => accessor.HttpContext.RequestServices;
desde um ConsoleApplication, é que estou fazendo alguns testes no projeto e para não criar todo o projeto ASP.Net criei um projeto Console
Obrigado
Olá Dariel!
Um consoleApp não possui o contexto HTTP, nesse caso você precisa inicializar isso com o Child Container.
Abs!
Eduardo tenho uma dúvida, você no projeto não esta utilizando o SimpleInjector, porque, tem alguma razão em especial.
este trecho de código na classe SimpleInjectorBootStrapper como eu posso fazer ele no SimpleInjector?
services.AddSingleton(Mapper.Configuration);
services.AddScoped(sp => new Mapper(sp.GetRequiredService(), sp.GetService));
Cómo devería ser feita a injeção de dependencias do mapper com o SimpleInjector?
Preferi o DI nativo para deixar mais “natural” (apesar de ter esquecido a classe com o nome de simpleinjector).
Dá para fazer com o simpleinjector também, só vai precisar registrar as dependencias do Identity na mão (obrigatoriamente)
Abs!
Eduardo, eu também tenho uma dúvida, vi que o projeto de Data (os outros não olhei) tu usa o xproj, appsettings.json e project.json, ou seja a estrutura nova do .net core. Mas quando eu crio um novo projeto class library, ele não vem com essa estrutura. Como eu crio um projeto vazio do formato do .net core? Parece bem básico isso mas eu não achei como faz =P. Obrigado
Qual a sua versão do VS?
Eduardo,
Você tem planos de implementar o Angular?
A interface nova que está no Roadmap seria o Angular Material?
Você implementou o UoW, mas vi que chamou os contextos diretamente no repositório, é alguma técnica?
TFA.’.
Olá Eduardo, fiz o download do projeto mais não consigo executar. Aparece a seguinte mensagem da saída:
O programa “[9868] dotnet.exe” foi fechado com o código -2147450749 (0x80008083).
O programa “[11952] iisexpress.exe” foi fechado com o código 0 (0x0).
Podes me ajudar
Oi Diego, eu estava com o mesmo problema.
Baixei o .NET Core SDK aqui https://www.microsoft.com/net/core#windowscmd e reinstalei.
Após isso a aplicação rodou sem problemas.
Obrigado Perez, funcionou
Parabéns Eduardo! Cara como Sugestão, que tal um artigo sobre Unity of Work?
Tenho duvidas e tem uma galera que também tem.
Parabéns
Artigo muito bom! Visto que você pediu sugestões e afirmou sobre a V2. Deixo a minha: “Bancos separados para leitura e gravação de dados” utilizando o DocumentDB para leitura dos dados.
Forte abraço!
Comecei a compreender melhor vários conceitos os vídeos no teu canal do youtube.
Vou acompanhar o projeto.
Valeu Eduardo !
Boa tarde pessoal,
Será que alguém poderia me ajudar vou descrever meus problemas.
Tentei abrir o projeto no VS2015 e deu problema. Preciso saber se o projeto pode ser aberto no VS2015 e o que seria preciso para que isso aconteça.
Depois disso eu abrir o projeto no VS2017 e ele conseguiu abrir o projeto porem nao consigo compilar da o seguinte erro.
Cannot find project info for… This can indicate a missing project reference
Olá. parabéns pelo projeto….
Gostaria que abordasse algo relacionado a multitenance, ou como filtrar dados de uma tabela para um determinado usuário.. é possível ?
Objetivo.: Manter apenas 1 banco de dados…
Abcs e parabéns novamente
Eu venho utilizando o https://aspnetboilerplate.com/
Mas vou baixar e testar 😉
Edu, teria como colocar no projeto a opção para multilanguage?
Boa tarde, antes do mais quero agradecer pelos esforço prestado na comunidade Brasileira que acabou se reflectindo aqui em Angola e já somos vários Dev seguindo seus ensinamentos. Graças ao seu mega video sobre DDD consegue aprender perfeitamente o uso do DDD e não pude deixar de reparar o projecto Equinox que esta crescendo e já usando o Asp.net Core 2 e outras Tecnologias interessantes que seria uma grande valia se tivesse uma explicação sobre o Equinox que para mim é uma lenda. Consigo, entender até certo ponto…..
Em nome da comunidade de Desenvolvedores Angolanos, agradecemos por tudo e não temos palavras para expressar.
This project is very helpful, especially the domain notification implementation. Is there anywhere where the flow of notifications from the handler through the BaseController to the SummaryComponent is explained?
I’m wondering how the SummaryViewComponents list of notifications gets populated. I am assuming it is being populated from the BaseController list of notifications. Thank you
Boa noite Eduardo! Como vai?
Estou tentando desenvolver algo para fins de estudo e estou rcebendo o seguinte erro:
An error occurred while starting the application.
TypeLoadException: Method ‘Handle’ in type ‘Hermes.Domain.Core.Notifications.DomainNotificationHandler’ from assembly ‘Hermes.Domain.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ does not have an implementation.
Hermes.Infra.CrossCutting.IoC.NativeInjectorBootStrapper.RegisterServices(IServiceCollection services)
TypeLoadException: Method ‘Handle’ in type ‘Hermes.Domain.Core.Notifications.DomainNotificationHandler’ from assembly ‘Hermes.Domain.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ does not have an implementation.
Hermes.Infra.CrossCutting.IoC.NativeInjectorBootStrapper.RegisterServices(IServiceCollection services)
Hermes.UI.Site.Startup.RegisterServices(IServiceCollection services) in Startup.cs
+
NativeInjectorBootStrapper.RegisterServices(services);
Hermes.UI.Site.Startup.ConfigureServices(IServiceCollection services) in Startup.cs
+
RegisterServices(services);
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
O método em questão está lá pois estou usando o seu código.
Saberia me dizer qual seria o problema?
Parabéns pela inciativa Eduardo! Muito bom o material! Só uma duvida: no projeto a camada de aplicação conversa direto com o repositório mas nesse vídeo: https://youtu.be/i9Il79a2uBU entre o repositório e a camada de aplicação tem uma camada de serviço. Qual seria a melhor abordagem?
Boa Tarde Eduardo!
Baixei a nova versão e ao editar um customer ou ver o trackingchanges da erro :
EquinoxProject/src/Equinox.Infra.Data/Repository/EventSourcing/EventStoreSQLRepository.cs
public void Store(StoredEvent theEvent)
{
_context.StoredEvent.Add(theEvent);
_context.SaveChanges();
}
Pelo que vi, no migrations ele nao criou a tabela StoredEvent
Boa tarde Eduardo!
Ótima iniciativa! Ótimo projeto!
Eu gostaria de saber se você da algum curso que ensina as tecnologias e os padrões usado no projeto equinox?
Att.
Bom dia Eduardo, ótimo artigo sempre acompanho suas publicações.
Estou estudando sobre event sourcing e CQRS para aplicar em uma solução, a minha duvida é. Para aplicar consistência eventual usando como bus o Rabbitmq, que mudanças deveria aplicar no projeto?
Eduardo,
Pode me ajudar! Baixei o código, mas tem 2 projetos que não consegue carregar?
WEB e API, pode me ajudar?
Grato.
Olá Eduardo!
Uma dúvida com relação aos valores de objetos, como eles são representados nas Views Models e commands? Será representado por DTO ou seguiram as estruturas representadas nas tabelas?
Exemplo:
public class AddressVO : ValueObject
{
public string Street { get; private set; }
public string HouseNumber { get; private set; }
public string Locality { get; private set; }
public string City { get; private set; }
public string FederationUnity { get; private set; }
public ZipVO Zip { get; private set; }
public AddressVO(string street, string houseNumber, string locality, string city, string federationUnity, string zipCode)
{
Street = street;
HouseNumber = houseNumber;
Locality = locality;
City = city;
FederationUnity = federationUnity;
Zip = new ZipVO(zipCode);
}
}
public class Person : Entity
{
public string Name { get; private set; }
public CpfVO Cpf { get; private set; }
public AddressVO Address { get; private set; }
}
View Model / Command
public class AddressDTO
{
public string Street { get; set; }
public string HouseNumber { get; set; }
public string Locality { get; set; }
public string City { get; set; }
public string FederationUnity { get; set; }
public ZipTO Zip { get; set; }
}
public class PersonDTO
{
public string Name { get; set; }
public CpfDTO Cpf { get; set; }
public AddressDTO Address { get; set; }
}
View Model / Command (Tabela)
public class PersonDTO
{
public string Name { get; set; }
public string Cpf { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string Locality { get; set; }
public string City { get; set; }
public string FederationUnity { get; set; }
public string Zip { get; set; }
}