Mais 4 Recomendações Práticas para Projetos de Microsserviços em .NET

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.

 

Compartilhe este insight:

16 respostas

  1. 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?

  2. 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!

  3. 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!

  4. 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.

  5. Sobre as variáveis de ambiente, não é mais necessário adicionar o AddEnvironmentVariables(), visto que ele já é chamado internamente pelo WebHost.CreateDefaultBuilder(args)

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

[tweet]Uma dos ganhos mais notáveis da Arquitetura Corporativa é a conexão que ela propicia entre a Estratégia e a Rotina[/tweet]....
Sou privilegiado. Há anos, em função do meu trabalho, tenho a oportunidade de viajar para fora do país. Recentemente, passei,...
Há pouco menos de um ano, aceitei o desafio de liderar o time de tecnologia e estratégia de negócios da...
Em minha experiência, a inovação acontece a partir de um dos seguintes gatilhos: A área de negócios identifica uma demanda...
Este post foi originalmente publicado em 2015 (infelizmente, a postagem original não está mais disponível) Nesse post vamos implementar, passo-a-passo,...
Uma das causas mais comuns para problemas de performance em .NET é o descuido com o Garbage Collector. Mesmo funções...