Neste post, compartilho mais algumas ideias que tenho adotado, com êxito, em meus projetos envolvendo Microsserviços e que podem ajudar você a ser mais efetivo em suas implementações.
Se você ainda não leu, recomendo que acesse um post anterior sobre esse tema, com mais recomendações, antes de avançar.
1. Comece pelos comandos e pelas consultas que vai suportar
Antes de definir qualquer aspecto mais técnico, ter clareza sobre quais comandos e consultas seu microsserviço deverá suportar irá ajudar você a ser mais efetivo e mais alinhado com as necessidades do negócio.
No exemplo, apenas ilustrativo, o microsserviço irá manter um “contador”.
Tenho optado (não faz muito tempo) por usar o Mediatr para intermediar a chamada dos handlers, orquestrar validação das mensagegens, etc.
using System.Threading; using System.Threading.Tasks; using CountingMicroservice.Application.Services; using MediatR; namespace CountingMicroservice.Application.CommandSide.Commands { public class IncrementCommandHandler : IRequestHandler<IncrementCommand, bool> { private readonly ICounterRepository _repository; public IncrementCommandHandler(ICounterRepository repository) { _repository = repository; } public Task Handle( IncrementCommand request, CancellationToken cancellationToken ) { if (request == null) { return Task.FromResult(false); } _repository.Increment(); return Task.FromResult(true); } } }
2. Mantenha sua WebApi simples (se essa for sua interface)
Manter um projeto específico para a aplicação permite que o projeto da Web API seja extremamente simplificado. Caberá a WebAPI, apenas, fazer as devidas inicializações e prover features que “chamam” os comandos e consultas no momento apropriado.
Aliás, para aliviar acoplamento, tenho adotado por hábito fazer inicializações específicas da aplicação no código da aplicação (fora da web api)
using CountingMicroservice.Application.Services; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using ServiceStack.Redis; namespace CountingMicroservice.Application { public static class StartupExtensions { public static IServiceCollection AddCounterApplication( this IServiceCollection services, IConfiguration configuration ) { var redisConnectionString = configuration["REDIS_CONNECTIONSTRING"] ?? "-counter.data"; services .AddSingleton(sp => new RedisManagerPool(redisConnectionString) ) .AddSingleton<ICounterRepository, RedisCounterRepository>(); return services; } public static IApplicationBuilder UseCounterApplication( this IApplicationBuilder app ) { return app; } } }
Fica dentro da WebAPI apenas inicializações que são específicas dela (Swagger, por exemplo)
using System; using Autofac; using Autofac.Extensions.DependencyInjection; using CountingMicroservice.Application; using CountingMicroservice.Application.CommandSide.Commands; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Swashbuckle.AspNetCore.Swagger; namespace CountingMicroservice.WebApi { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.RespectBrowserAcceptHeader = true; }); services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info() { Title = "Counting Microservice", Version = "v1", Description = "Basic Microservice Example" }); }); services.AddCounterApplication(Configuration); var container = new ContainerBuilder(); container.RegisterModule( MediatrModule.Create(typeof(StartupExtensions).Assembly) ); container.Populate(services); return new AutofacServiceProvider(container.Build()); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvcWithDefaultRoute(); app .UseSwagger() .UseSwaggerUI(options => { options.SwaggerEndpoint( "/swagger/v1/swagger.json", "Counting Microservice v1" ); }); app.UseCounterApplication(); } } }
3. Use e abuse do Docker
Provavelmente seu microsserviço irá rodar em um container;. Aproveite-se do suporte do Visual Studio para Docker e já comece seu projeto rodando e depurando em containers.
O efeito positivo dessa escolha é que ficará muito mais fácil gerenciar dependências de softwares tecerceiros. Veja o docker-compose.yml da aplicação exemplo
version: '3' services: countingmicroservice.webapi: image: countingmicroservice.webapi build: context: . dockerfile: CountingMicroservice.WebApi/Dockerfile depends_on: - redis redis: image: redis
No exemplo, não preciso mais me preocupar em ter um servidor redis rodando em meu Windows. Ao começar a rodar minha aplicação, uma imagem do Redis irá ser baixada e um container iniciado.
4. Use variáveis de ambiente para configuração
É muito mais fácil configurar sua aplicação usando variáveis de ambiente do que utilizando arquivos de configuração. No Asp.net Core, é simples fazer isso.
namespace CountingMicroservice.WebApi { public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .ConfigureAppConfiguration((builderContext, config) => { config.AddEnvironmentVariables(); }) .Build(); } }
Além disso, variáveis de ambiente são perfeitas combinadas ao Docker. No exemplo, configuro endereço do Redis no arquivo de composição. Isso simplificará muito o deploy mais tarde.
version: '3' services: countingmicroservice.webapi: environment: - ASPNETCORE_ENVIRONMENT=Development - REDIS_CONNECTIONSTRING=redis ports: - "5000:80" redis: ports: - "6379:6379"
Concluindo
Novamente, “são as cicatrizes que contam a história do guerreiro”.
- Sempre achei tedioso fazer o link entre meus Controllers e o código do domínio. Mediatr é uma “mão na roda” e a separação do microsserviço em projetos “adaptadores” e um “core” me ajuda a manter focado no negócio.
- A ideia de implementar comandos e consultas me faz manter o foco nas histórias de negócio ao invés de dados colecionados.
- Sempre achei péssimo instalar bancos de dados e outros artefatos em meu computador apenas para desenvolver – Docker é vida!
- Sempre odiei o web.config! Felizmente, não preciso mais dele. Adoro a ideia de poder usar simples variáveis de ambiente para configurar minha aplicação.
Novamente, há bem mais que poderia ser compartilhado. Entretanto, mais uma vez, é hora de pedir a sua opinião.
16 respostas
Mediatr é muito amor 🙂
Concordo! E Docker é vida!
Elemar este MediaR eu não conhecia mas estes dias eu tenho visto falar bem dele.
Seus exemplos que envolve CQRS são interessantes, você vai disponibilizar o código destes exemplos no seu Github?
Vou organizar o código e disponibilizo sim.
Gosto muito da sua abordagem pé no chão e baseada no mundo real, Elemar. Gostaria muito de ver esses conceitos aplicados em um sistema completo. Fiquei animado quando anunciou o desenvolvimento de um CRM, em que pé está esse projeto?
Um abraço!
Obrigado pelo feedback. Quanto ao projeto, está andando muito mais devagar do que eu gostaria. 🙂
Opa!
Se precisar de braços estamos aí. (y)
Habilita code review, e vamos nessa!
Gostei de ver os commands e commandhandles na mesma pasta
Elemar, vc faria as mesmas recomendações pra uma aplicação web corporativa?
Pensando em Microsserviços, com certeza!
Muito bom Elemar! Parabéns pelos ótimos artigos.
Já venho adotando MediatR em meus projetos há algum tempo e só tenho elogios à ele. Como o Angelo Belchior mencionou anteriormente, MediatR é MUITO amor.. =D
Tem planos de disponibilizar seus exemplos no Github?
Abraços!
Andei refletindo bastante em relação à sua recomendação de manter feature folders. Gosto bastante dessa abordagem.
A maneira “tradicional” de se pensar em arquitetura é investir no design das camadas da aplicação e suas responsabilidades. Em aplicações data-driven (que é a esmagadora maioria de aplicações corporativas), acaba-se dando um
foco muito grande na camada de repositório, a ponto desse padrão ter se tornado uma piada interna entre desenvolvedores, arquitetos e MVPs que dominam melhor o DDD. O que sempre vejo acontecer é a criação de um repositório
genérico, com operações básicas, baseadas em CRUD. Na minha opinião, isso se dá pelo fato de que em muitos momentos a arquitetura é algo feito antes do projeto começar, sem ainda ter todos os requisitos de negócio definidos. Outra realidade de projetos que já trabalhei é resolver as regras de negócio por meio da base de dados, com queries complexas, triggers, views, stored procedures, etc.
Esse é o ponto principal. Como ainda não se sabe exatamente o que a aplicação vai fazer, toma-se o caminho do CRUD, que é o tipo de funcionalidade mais comum e muito provável de ser necessário em algum ponto do projeto. Muitos
deles serão basicamente CRUD com relatórios, inclusive.
Montar a arquitetura com base nas features muda radicalmente essa abordagem. O pensamento horizontal muda para vertical e essa mudança é muito poderosa.
Dessa forma, em vez de pensar em camadas abstratas, sem saber exatamente como entregarão valor, já se começa a pensar em funcionalidades concretas do negócio e as desenvolvemos de fim a fim. Essa é a abordagem sugerida no livro Growing Object-Oriented Software, Guided by Tests. Dessa forma, fica mais fácil escrever testes unitários, de integração e fim a fim para validar aquela feature concreta de negócio e não perde-se tempo provendo métodos de repositório que não sabemos se serão utilizados.
Em vez de pensarmos em camadas, passamos a pensar em fatias e camadas não entregam valor de negócio de forma direta. Fatias (ou funcionalidades completas), sim.
A sua recomendação de começar definindo as consultas e comandos que o serviço vai suportar também sugere uma aplicação data-driven, mas com a diferença fundamental de que as funcionalidades que irão tocar o banco estarão definidas sob medida com base nos requisitos de negócio existentes. Dessa forma, elimina-se a ideia de repositório genérico, que citei anteriormente.
Acredito que essa mudança de paradigma seja importantíssima para escrever aplicações mais coesas e alinhadas com o problema que elas se propõe a resolver.
Gostaria de saber a sua opinião a respeito dessas reflexões.
Acabei de ver que o meu longo comentário anterior ficou muito mal formatado na publicação… 🙁
Para mim aparece bem. Pode me mandar um screenshot por favor?
Enviei para o seu e-mail.
Sobre as variáveis de ambiente, não é mais necessário adicionar o AddEnvironmentVariables(), visto que ele já é chamado internamente pelo WebHost.CreateDefaultBuilder(args)