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 um comentário

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:

In this post, I would like to share my first attempt to create a (still) elementary search library. The source...
Não são raros os casos onde a distância entre um “desejo” e a “realização” é a “tentativa”. Ou seja, partindo...
O desenvolvimento de uma aplicação com ótima performance só é possível mediante considerações desde sua arquitetura. Otimizações pontuais de código,...
Mais preocupante que o posicionamento do presidente é sua falta de constância. Há dois dias, uma medida provisória proposta pelo...
Quando estamos desenvolvendo aplicações distribuídas, não devemos nos perguntar se teremos problemas de conectividade. No lugar disso, devemos nos perguntar...
Dando continuidade a uma jornada iniciada há mais de 20 anos, comunico a fundação da Eximia! Trata-se de uma empresa...