BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Aplicações .NET Core com Stack Netflix OSS usando Steeltoe

Aplicações .NET Core com Stack Netflix OSS usando Steeltoe

Pontos Principais

  • Exemplos de construção de uma aplicação madura, tirando proveito de elementos como Service Discovery, Circuit Breaking e Observability;
  • Como as APIs podem interagir junto ao Eureka para trabalharem com Service Discovery, tanto para se registrarem como também para consumirem;
  • Exemplos de exposição do Actuator, onde é possível monitor a saúde de APIs;
  • Exemplos de configuração do Zuul como API Gateway com base no Eureka;
  • Exemplos em como utilizar o conceito de Circuit-Breaker implementado pelo Hystrix.

Introdução

A plataforma ASP.NET Core vem sofrendo melhorias consideráveis nos últimos anos. Seu desempenho vem sendo continuamente melhorado e o baixo footprint a torna uma escolha racional para construção de microservices. Algumas alternativas interessantes a ferramentas tradicionais surgiram com o crescimento do ecossistema do asp.net core, entre elas podemos citar o Ocelot API Gateway, GraphQL . NET, BeatPulse entre outros. Se você estiver interessado em conhecer outras ferramentas para .NET Core, essa é a melhor compilação de bibliotecas e ferramentas que encontramos para plataforma .NET Core.

Porém em um ecossistema de microservices as soluções em geral são diversas e a unificação acontece por meio de tecnologias consolidadas no mercado. As ferramentas da stack Netflix OSS atualmente são adotadas e suportadas por diversas comunidades de linguagens diferentes, por isso é importante que os microservices desenvolvidos em .NET tenham facilitadores para interagir com essas ferramentas.

Nesse cenário o Steeltoe, desenvolvido pela Pivotal, é uma escolha razoável para quem deseja desenvolver com .Net Core em um ecossistema de microservices heterogêneo que faz uso intensivo das ferramentas da stack Netflix e Spring Cloud.

Apesar da biblioteca não contemplar algumas funcionalidades que temos disponíveis em um ambiente Spring Cloud, já é possível construir uma aplicação madura, tirando proveito de elementos como Service Discovery, Circuit Breaking e Observability.

Para ilustrar um exemplo de uso, construímos uma aplicação básica onde temos um proxy/gateway, service discovery e duas APIs dependentes, além do Spring Boot Admin, para facilitar o monitoramento do ecossistema.

Aplicação Exemplo

Para apresentar a utilização do Steeltoe com a plataforma .Net Core criamos uma conjunto de APIs exemplo e uma comunicação entre elas permitindo que fosse possível apresentar:

  • Como as APIs interagem junto ao Eureka para trabalharem com Service Discovery, tanto para se registrarem como também para consumirem;
  • A exposição do Actuator, onde seria possível monitoramos a saúde de nossas APIs;
  • A configuração do Zuul como nosso API Gateway com base no Eureka;
  • E como utilizar o conceito de Circuit-Breaker implementado pelo Hystrix.

Todo o código fonte dos exemplos apresentados neste artigo podem ser encontrado no repositório do projeto.

APIs de exemplo

As APIs foram construídas de forma básica seguindo o padrão REST e as bibliotecas padrões do .Net Core, apenas foi incluído o Swagger como forma de facilitar a manutenção das APIs.

Em nosso exemplo, a API-A é composta por um endoint contendo:

  • POST/api/Example, o qual serve para emular a chamada entre a API-A e a API-B afim de demonstrar a utilização do Zuul, Eureka e Hystrix

Já a API-B é composta por três endpoints que contém:

  • POST /api/Status/{status}, que serve para alterar o status da API, permitindo que o status dela possa ser apresentado no Actuator e também possa refletir sua alteração na API-A
  • POST /api/example, é um post que permite simular time-outs ou erros internos na api, para que possa refletir no Hystrix da API-B

Exemplo com o Eureka

Para integrar as APIs de exemplo ao Eureka, é necessário que elas estejam referenciando a dependência Steeltoe.Discovery.ClientCore, que possui a implementação necessária para registro da API no Eureka e visibilidade do seu endereço e porta. O Eureka é um service registry que disponibilizará as informações de endereço das instâncias do seu microserviço para outras aplicações. Dessa forma os outros serviços poderão acionar a sua API sem configurações fixas de endereços nos configs da aplicação.

Para que as APIs se registrem é necessário que no startup da aplicação sejam adicionadas linhas de código e configuração, seja via variável de ambiente ou via appSettings.json, conforme o exemplo abaixo:


using Steeltoe.Common.Http.Discovery;
using Steeltoe.Discovery.Client;
...
namespace exemplo_api_a
{
    public class Startup
    {
        ...
        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddDiscoveryClient(Configuration);
            ...
        }
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            ...
            app.UseDiscoveryClient();

            ...
        }
    }
}

"spring": {
   "application": {
     "name": "api-a" //Nome da aplicação a ser registrada no Eureka
   }
 },
 "eureka": {
   "client": {
     "serviceUrl": "http://localhost:9091/eureka/", //Endereço do Eureka
     "shouldFetchRegistry": true
   },
   "instance": {
     "hostName": "localhost",
     "port": 5001,
     "statusPageUrlPath": "/actuator/info", //Endpoint da API para que o Eureka possa checar as suas inforamções
     "HealthCheckUrlPath": "/actuator/health" //Endpoint da API para que o Eureka possa verificaar a sua saúde 
   }
 }

Com essas configurações é possível verificar no Eureka que as APIs são capazes de se registrar, conforme a imagem abaixo.

Tela do Eureka com o registro das APIs

Como parte do Service Discovery, a API-A também se torna capaz de acessar a API-B por meio das informações registradas no Eureka, apenas utilizando o nome do serviço que posteriormente é alterado para o endereço registrado. No código abaixo são apresentadas as injeções necessárias e o registro do serviço no HttpClientFactory, para consumo da requisição e também a declaração do HystrixCommand de chamada da API-B.



using Steeltoe.Common.Http.Discovery;
using Steeltoe.Discovery.Client;
...
namespace exemplo_api_a
{
    public class Startup
    {
        ...
        public void ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddTransient();           
            services.AddHttpClient("zuul-server", c =>
            {
                c.BaseAddress = new Uri("http://zuul-server/hello/");
            })
            .AddHttpMessageHandler();
            services.AddDiscoveryClient(Configuration);
            ...
        }
    }
}

namespace exemplo_api_a.HystrixCommands
{
    public class ApiBCallCommand
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public ApiBCallCommand(IHttpClientFactory httpClientFactory)
        {
            this._httpClientFactory = httpClientFactory;
        }
        protected override async Task RunAsync(Request request)
        {
            //var httpClient = _httpClientFactory.CreateClient("api-b")
            var httpClient = _httpClientFactory.CreateClient("zuul-server");         
            var res = await httpClient.PostAsync("api/Example", _request ?? new Request(), new JsonMediaTypeFormatter());
            var content = await res.Content.ReadAsStringAsync();

            ...
        }
    }
}

Exemplo de Management Endpoints (utilizando Actuator)

Para que as APIs fossem capaz de expor informações de saúde, ambiente entre outras foi necessário incluir a dependência Steeltoe.Management.CloudFoundryCore, onde com a inclusão dos services, configurações e ajuste no appSettings.json a seguir ele já é capaz de começar a retornar informações da sua saúde.



using Steeltoe.Management.CloudFoundry;
namespace exemplo_api_a
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // Coletores de métricas próprios
            services.AddSingleton();
            services.AddSingleton();

            services.AddCloudFoundryActuators(Configuration);

            services.AddHealthActuator(Configuration);
            services.AddInfoActuator(Configuration);
            services.AddLoggersActuator(Configuration);
            services.AddTraceActuator(Configuration);
            services.AddRefreshActuator(Configuration);
            services.AddEnvActuator(Configuration);
            services.AddMappingsActuator(Configuration);
            services.AddMetricsActuator(Configuration);
        }
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.MapWhen(context => context.Request.Method.Equals("options", StringComparison.OrdinalIgnoreCase), HandleHead);
            app.UseCors(options => options.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin());
            app.UseHealthActuator();
            app.UseInfoActuator();
            app.UseLoggersActuator();
            app.UseTraceActuator();
            app.UseRefreshActuator();
            app.UseEnvActuator();
            app.UseMappingsActuator();
            app.UseMetricsActuator();
            app.UseCloudFoundryActuators();
        }
        private static void HandleHead(IApplicationBuilder app)
        {
            app.Run(async context =>
            {
                context.Response.StatusCode = 200;
                await context.Response.WriteAsync($"Up to head!");
            });
        }
    }
}
###

###
"management": {
   "security": {
     "enabled":  false
   },
   "endpoints": {
     "path": "/actuator",
     "health": { "path": "/actuator/health" },
     "cloudfoundry": {
       "validateCertificates": false
     },
     "actuator": {
       "exposure": {
         "include": "*"
       }

     }
   }
 }

As imagens a seguir, exemplificam os endpoints de Management (Actuator) do Steeltoe, onde podemos observar o estado da aplicação (health), traces e variáveis de ambiente.

Na API A, conforme é possível ver no ConfigServer do Startup, foram incluídos os seguintes pontos:

  • Uma informação de saúde, ApiBCheck, onde caso a API B estivesse instável ou fora, a API A saberia como se comportar, nesse caso, informando que está com WARNING. A verificação da saúde de uma dependência externa ao serviço abre caminho para um nível maior de maturidade de monitoramento e algumas facilidades de troubleshooting no momento da operação.
  • Uma exposição de informações adicionais, onde nesse exemplo retorna um valor qualquer.


public class ApiBCheck : IHealthContributor
   {
       private readonly IHttpClientFactory _httpClientFactory;
       public string Id => "ApiBCheck";
       public static HealthStatus Status { get; set; } = HealthStatus.UP;
       public ApiBCheck(IHttpClientFactory httpClientFactory)
       {
           _httpClientFactory = httpClientFactory;
       }
       public HealthCheckResult Health()
       {
           var result = new HealthCheckResult()
           {
               Status = HealthStatus.UP,
               Description = "API B OK!"
           };
           try
           {
              
               HttpClient client = _httpClientFactory.CreateClient("zuul-server");
               HttpRequestMessage request = new HttpRequestMessage();
               var response = client.GetStringAsync("health").Result;
               dynamic health = JObject.Parse(response);
               if (health.status != HealthStatus.UP.ToString())
               {
                   result = new HealthCheckResult()
                   {
                       Status = HealthStatus.WARNING,
                       Description = $"API B: {health.status}"
                   };
               }
           }
           catch (Exception ex)
           {
               result = new HealthCheckResult()
               {
                   Status = HealthStatus.WARNING,
                   Description = "Falha ao verificar API B"
               };
               result.Details.Add("fail", ex.Message);
           }
           result.Details.Add("status", result.Status.ToString());
           result.Details.Add("details", result.Description);
           return result;
       }
   }
###

###
public class InfoSomeValue : IInfoContributor
   {
       public void Contribute(IInfoBuilder builder)
       {
           // pass in the info
           builder.WithInfo("arbitraryInfo", new { someProperty = "someValue" });
       }
   }



Nas imagens abaixo temos um exemplo do endpoint de health check customizado com informações da API-B, visto que a API-B é uma dependência da API-A. Além disso é possível expor informações customizadas através do endpoint de "Info".

Exemplo com Spring Admin

Com o Actuator e o Eureka funcionando, é possível utilizar o Spring Boot Admin, onde apenas foi necessário configurar o endereço Eureka para que ele descobrisse os demais serviços, conforme configuração abaixo no properties dele.


spring.application.name=Boot-Admin
server.port=8093
security.user.name=admin
security.user.password=admin
management.endpoints.web.exposure.include=*
eureka.client.serviceUrl.defaultZone=http://localhost:9091/eureka
spring.boot.admin.routes.endpoints=[env,metrics,info,trace,refresh,loggers,hystrix.stream]
spring.boot.admin.monitor.status-interval=5000
spring.boot.admin.ui.title="Springboot Admin with C# Services!!"

Com isso, já é possível visualizar os serviços, seus status e informações, conforme imagens a seguir.

Exemplo utilizando Zuul

Para o funcionamento do Zuul é necessário apenas configurar o endereço do Eureka para conhecer os serviços e endereços, e os direcionamentos dos paths, conforme o arquivo yaml a seguir:



server:
  port: 8762
spring:
  application:
    name: zuul-server
eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:9091/eureka}
zuul:
  ignoredServices: '*'
  routes:
    apiB:
      path: /hello/**
      serviceId: api-b
      #url: http://localhost:57722/
management:
  endpoints:
    web:
      exposure:
        include: routes,filters,health,env,metrics,trace,info,refresh,loggers
logging:
  pattern: 
    console: "%d{yyyy-MM-dd HH:mm:ss} ${LOG_LEVEL_PATTERN:-%5p} %m%n"


A seguir, apresentamos um exemplo chamando o Zuul para realização de uma chamada para a API-B.

Exemplo com o Hystrix

Para utilização do Hystrix é necessário a inclusão da depêndencia do Hystrix.Dotnet.AspNetCore. Assim, podemos configurar os Commands, que são responsáveis por encapsular as chamadas que devem ser controladas e monitoradas. A seguir descrevemos o código de implementação de um command utilizado na API-A encapsulando uma chamada para API-B, sua configuração no startup da aplicação e sua utilização, nesse caso na própria controller da API-A.


using Steeltoe.CircuitBreaker.Hystrix;
namespace exemplo_api_a.HystrixCommands
{
    public class ApiBCallCommand : HystrixCommand
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private Request _request;
        public ApiBCallCommand(IHystrixCommandOptions options, IHttpClientFactory httpClientFactory)
            : base(options)
        {
            this._httpClientFactory = httpClientFactory;
        }
        public void Input(Request request)
        {
            _request = request;
        }
        protected override async Task RunAsync()
        {
            var httpClient = _httpClientFactory.CreateClient("zuul-server");
            var res = await httpClient.PostAsync("api/Example", _request ?? new Request(), new JsonMediaTypeFormatter());
            var content = await res.Content.ReadAsStringAsync();
            if (res.StatusCode == HttpStatusCode.InternalServerError)
                throw new Exception(content);
            if (res.StatusCode != HttpStatusCode.OK)
                return $"{res.StatusCode} - {content}";
            return await res.Content.ReadAsStringAsync();
        }
        protected override string RunFallback()
        {
            return "Circuito Aberto";
        }
    }
}
###
###
using exemplo_api_a.HystrixCommands;
using Steeltoe.CircuitBreaker.Hystrix;
namespace exemplo_api_a
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHystrixCommand("ApiBCallGroup", Configuration);
            services.AddHystrixMetricsStream(Configuration);
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseHystrixRequestContext();
            app.UseHystrixMetricsStream();
        }
    }
}
###
###
using exemplo_api_a.HystrixCommands;
namespace exemplo_api_a.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ExampleController : ControllerBase
    {
        private readonly ApiBCallCommand _apiBCall;
        public ExampleController(ApiBCallCommand apiBCall)
        {
            this._apiBCall = apiBCall;
        }
        [HttpPost]
        public string ApiBCallCommand(Request request)
        {
            _apiBCall.Input(request);
            return _apiBCall.Execute();
        }
    }
}

Também é necessário configurar as regras do CircuitBreaker que podem ser gerais como nesse caso ou específicas com Group ou Command.


"hystrix": {
   "command": {
     "default": {
       "execution": {
         "isolation": {
           "thread": {
             "timeoutInMilliseconds": 5000
           }
         }
       },
       "circuitBreaker": {
         "errorThresholdPercentage": 75,
         "sleepWindowInMilliseconds": 1000
       },
       "metrics": {
         "rollingStats": {
           "timeInMilliseconds": 30000
         }
       }
     }
   }
 }

Com a aplicação é possível então realizar disparos contra o endpoint da API-A e monitorar seu comportamento via o HystrixDashboard, conforme imagens a seguir.

Observe que a versão mais recente no GitHub do Hystrix permite integrar diretamente com o Eureka, no entanto optamos por uma versão mais simplificada para disponibilizar junto aos exemplos.

Nesse gráfico são apresentados informações se os circuitos estão fechados e operando normalmente ou abertos, bem como taxas de sucesso e falhas, latência, entre outros, conforme detalhamento abaixo.

fonte: https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki

Para exemplificar a funcionalidade do CircuitBreaker, se a API-B por algum motivo cair ou começar a retornar erro, conforme configuração, caso passe a uma taxa de 75% de erro o circuito se abre e começa a retornar uma resposta padrão ou simplesmente retornar o erro de forma instantânea, sem ter que esperar timeout ou ficar estressando a API-B.

Mesmo depois que a API-B retorna a operar, o circuito ainda continua aberto, a fim de dar tempo e permitir que a API-B se restabeleça da sua falha e possa retornar a normalidade. Nas imagens abaixo é possível ver no console que enquanto a API-A está recebendo requisições a API-B está 100% online e irá aguardar alguns instantes até que retorne a ser chamada, bem como também pode ser monitorado pelos HystrixDashboard conforme exemplos a seguir.

O Steeltoe possui diversas funcionalidades além das que usamos neste exemplo. Vale a pena conferir a sua documentação.

Os pontos que trazem mais ganhos e merecem ser explorados logo de início, são as bibliotecas de discovery, circuit breaking e management. Existem outros pontos importantes como os conectores que facilitam interação com diversas tecnologias como MySQL, RabbitMQ, Redis, MongoDB entre outras.

Service Discovery

Um Service Registry expõe uma "base de endereços" de serviços ativos e suas instâncias. O Steeltoe possui implementações de clientes para integração com o Eureka e Consul, o que torna mais fácil a construção de um microservice construído em .NET em um ecossistema JAVA que já utiliza Eureka como service registry por exemplo.

Circuit Break

Em ambientes .NET é normal utilizar ferramentas como o Polly para gerir Circuit Break, políticas de retry, timeout e etc. Porém a utilização do Steeltoe pode ir um pouco além em alguns aspectos, pois a implementação baseada no Hystrix traz junto o monitoramento da operação e também ganhos a observabilidade do ambiente. Uma ferramenta interessante para trabalhar em conjunto é o Hystrix Dashboard, que utilizamos em nosso exemplo.

Actuator

Os endpoints de monitoramento do Actuator podem trazer alguns ganhos na operação, mas isso depende de como sua equipe irá operacionalizar a monitoria sobre sua aplicação. No Steeltoe o Actuartor é tratado como "Management" e em alguns casos temos uma mistura de dependências entre as dependências do Steeltoe e do CloudFoundry, no momento em que escrevemos esse artigo, acreditamos que esse é um dos pontos fracos do projeto, mas que não inviabiliza seu uso.

Conclusão

O Steeltoe é um projeto que vale a pena acompanhar de perto, principalmente se você tem um ecossistema de microservices que faz uso de Java e .NET. Para integrações com o Zuul, Eureka, Hystrix e Config Server, ele já é uma ferramenta que trará muitos ganhos ao seu projeto.

Com relação ao uso do Actuator, encontramos algumas dificuldades com integrações com ferramentas construídas pela comunidade, como o Springboot Admin. Para equipes de baixa maturidade no entanto, o uso do Actuator em conjunto com o Springboot Admin pode trazer visibilidade para operação do ecossistema de microservices. Equipes mais maduras provavelmente irão preferir fazer uso diferente do Actuator, integrando com ferramentas com maiores capacidades.

O objetivo desse artigo foi incrementar a caixa de ferramentas de arquitetos e desenvolvedores que trabalham em ambientes com uso intensivo de Java e .NET no seu dia a dia. Para aqueles que querem estudar o código ou contribuir melhorando o projeto para facilitar estudos futuros da comunidade, basta acessar o repositório no Github.

Para aqueles que querem usar o Steeltoe em ambiente produtivo, levem em consideração que o projeto ainda está em maturação e em geral algumas funcionalidades não são cobertas em comparação ao Spring Cloud. Por isso analise com cuidado as funcionalidades que pretende usar.

Depois de todo renascimento do ecossistema .NET, abertura, colaboração e todas as melhorias que vem acontecendo, esse é um momento muito empolgante para desenvolvedores .NET. O amadurecimento de projetos como Steeltoe e a criação de diversos outros projetos voltados a resolução de problemas de microservices é um sinal de que a comunidade continua aquecida.

Tendo em vista tudo isso, se você ainda não está considerando .NET Core como uma boa plataforma para seus projetos, acho que esse é o momento de repensar.

O repositório do código pode ser encontrado no GitHub

Giovani Barili (LinkedIn) é graduado em Engenharia da Computação com mestrado em Artificial Inteligência pela UNISINOS/RS. Atua desde 2009 com tecnologia nas área de desenvolvimento para as áreas de varejo, aplicações móveis, financeiro, saúde e customização de sistemas ERPs. Desde 2017 é Arquiteto de Aplicações na Via Varejo S.A. com foco em padrões de projetos, microservices, desenvolvimento ágil, liderança de equipes e qualidade de software.

João Bosco Seixas (LinkedIn) MBA em gestão empresarial pela FGV e formado em computação. Atua promovendo transformação de equipes e empresas na área de tecnologia e desenvolvimento de software. Apaixonado por tecnologia, inovação e futurismo. Especialista em arquitetura de software com experiência em sistemas de alta complexidade, microservices e plataformas em cloud. Atualmente é Especialista em arquitetura de soluções digitais no Banco Itaú.

Marcelo Costa (LinkedIn) é pós-graduado em Engenharia de Software pela UNICAMP. Atua em sistemas de alta complexidade desde 2002, liderando equipes multidisciplinares no desenvolvimento de soluções de software nas áreas de varejo, aeroespacial, logística, educação, saúde e finanças. Especializa-se em liderança de equipes e arquiteturas de soluções, na coleta inteligente de informações na Internet e de conteúdo eletronicamente disponível; atualmente é consultor em Arquitetura de Soluções.

 

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT