O avanço das arquiteturas de microsserviços levou a uma reorganização das responsabilidades entre serviços. Isso impulsionou a adoção de contêineres e consequentemente, soluções de orquestração como o Kubernetes. No entanto, essa abordagem trouxe desafios, como o acoplamento excessivo entre aplicações e serviços externos.
Para mitigar isso, surgiu o Dapr, um runtime que permite comunicação desacoplada entre aplicações. Este artigo foca em apresentar o Dapr e como configurá-lo no Azure Container Apps, abordando funcionalidades essenciais e procedimentos práticos para uma integração bem-sucedida.
O Azure Container Apps é a solução de orquestração de contêineres do Azure. Ele permite que você implante e gerencie contêineres sem se preocupar com a infraestrutura subjacente. O Azure Container Apps é baseado no Azure Kubernetes Service, mas oferece uma experiência simplificada para implantar e gerenciar contêineres.
Podemos definir o Azure Container Apps como sendo uma solução serverless de Kubernetes.
Esse serviço é um ótimo acelerador para empresas que estão iniciando sua jornada de adoção de contêineres, pois permite que você implante e gerencie contêineres sem se preocupar com a infraestrutura.
O Dapr codifica as melhores práticas para a construção de aplicativos baseados em microsserviços em APIs abertas e independentes chamadas de building block, que permitem que você crie aplicativos portáteis com a linguagem e o framework de sua escolha. Cada building block é totalmente independente, e você pode usar um, alguns ou todos eles em seu aplicativo.
Usando o Dapr, você pode migrar gradualmente seus aplicativos existentes para uma arquitetura de microsserviços, adotando padrões nativos da nuvem, como dimensionamento sob demanda, resiliência e implantações independentes.
Além disso, o Dapr é independente de plataforma, o que significa que você pode executar seus aplicativos localmente, em qualquer cluster Kubernetes, em máquinas virtuais ou físicas e em outros ambientes de hospedagem que o Dapr integra. Isso permite que você construa aplicativos baseados em microsserviços que podem ser executados na nuvem e na borda.
Um building block
é uma API HTTP
ou gRPC
, que pode ser chamada a partir do seu código e utiliza um ou mais componentes do Dapr. O Dapr é composto por um conjunto de unidades de construção de API, com a capacidade de adicionar novas unidades de construção para estender suas funcionalidades.
Abaixo está uma lista dos principais building blocks do Dapr:
State:
State
permite que os aplicativos armazenem e recuperem estado de maneira confiável. Ele oferece suporte a várias opções de estado, incluindo estado persistente e temporário.Pub/Sub:
Pub/Sub
(Publicação/Assinatura) facilita a comunicação entre os diferentes componentes de um aplicativo distribuído. Os aplicativos podem publicar eventos e se inscrever para receber notificações quando esses eventos ocorrem.Bindings:
Bindings
permitem que os aplicativos interajam facilmente com recursos externos, como bancos de dados, sistemas de mensagens e serviços da nuvem, por meio de adaptadores pré-construídos.Secrets:
Secrets
gerencia e fornece acesso seguro a segredos sensíveis, como chaves de autenticação e senhas, para os aplicativos.Actors:
Actors
é um modelo de programação baseado em atores que facilita a criação de aplicativos de estado escaláveis e com estado isolado.Observability:
Observability
é uma parte fundamental do Dapr que fornece recursos de rastreamento, métricas e registro para facilitar a monitoração e solução de problemas de aplicativos.Middleware:
Middleware
permite a adição de funcionalidades personalizadas a aplicativos Dapr, como autenticação, autorização e manipulação de solicitações HTTP.HTTP API:
Na perspectiva das aplicações em execução no Kubernetes
, esses building blocks
são implementados como sidecars de contêineres
. Isso significa que cada building block
é executado como um contêiner adjacente à aplicação principal.
Um building block
é exposto através de um contêiner sidecar
associado a cada aplicação, atuando de forma isolada para fornecer uma funcionalidade específica, como gerenciamento de estado ou comunicação de eventos.
Por outro lado, um componente do Dapr é uma peça central que lida com a complexidade da comunicação com o serviço final. Em vez de ser um contêiner sidecar
individual, ele serve como uma camada intermediária entre a aplicação e os serviços externos.
Portanto, enquanto os building blocks
oferecem funcionalidades específicas diretamente para cada aplicação por meio de sidecars
, os componentes do Dapr desempenham um papel mais central e abrangente.
A imagem abaixo representa claramente o papel do componente:
Os componentes usam um design modular, e podem ser compartilhados entre aplicações. Eles são executados como um processo separado, fora do escopo da aplicação.
Os componentes são configurados em nível de ambiente do Container App, mesmo que a plataforma se responsabilize por criar e configurar o Dapr, ainda precisaremos configurar os componentes.
Essa configuração é feita através de arquivos de definição yaml
, cada tipo de componente conterá suas próprias propriedades.
No artigo, estamos demonstrando a integração do componente de pub/sub do Dapr com o serviço do Service Bus.
Existe uma diferença entre o arquivo utilizado para criação de componentes direto em um cluster Kubernetes e o arquivo utilizado para criação de componentes no ambiente do Container App.
Todos os componentes de código aberto do Dapr seguem o seguinte esquema básico:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: [COMPONENT-NAME]
namespace: [COMPONENT-NAMESPACE]
spec:
type: [COMPONENT-TYPE]
version: v1
initTimeout: [TIMEOUT-DURATION]
ignoreErrors: [BOOLEAN]
metadata:
- name: [METADATA-NAME]
value: [METADATA-VALUE]
No Azure Container Apps, o esquema acima foi ligeiramente simplificado para suportar os componentes do Dapr e remover campos desnecessários, incluindo apiVersion, kind e propriedades redundantes de metadados e especificações.
componentType: [COMPONENT-TYPE]
version: v1
initTimeout: [TIMEOUT-DURATION]
ignoreErrors: [BOOLEAN]
metadata:
- name: [METADATA-NAME]
value: [METADATA-VALUE]
No nosso exemplo, o arquivo ficou da seguinte forma:
componentType: pubsub.azure.servicebus.queues
version: v1
ignoreErrors: false
secrets:
- name: connectionstring
value: <VALOR DA CONNECTION-STRING>
metadata:
- name: connectionString
secretRef: connectionstring
scopes:
- app-publisher
- app-subscriber
A propriedade
scopes
define quais aplicações terão acesso ao componente, portanto é obrigatória.
Para criar o componente no ambiente do Container App
, rode o comando:
az containerapp env dapr-component set --name ENVIRONMENT_NAME --resource-group RESOURCE_GROUP_NAME --dapr-component-name pubsub --yaml "./pubsub.yaml"
Se o comando funcionou corretamente, verá um resultado parecido com:
{
"id": "/subscriptions/60e0dc1e-a3f7-44cb-8561-17980fce2670/resourceGroups/tdc-huebr/providers/Microsoft.App/managedEnvironments/tdc-huebr-env/daprComponents/pubsubgeneric",
"name": "pubsub",
"properties": {
"componentType": "pubsub.azure.servicebus.queues",
"ignoreErrors": false,
"metadata": [
{
"name": "connectionString",
"secretRef": "connectionstring"
}
],
"scopes": [
"app-publisher",
"app-subscriber"
],
"secrets": [
{
"name": "connectionstring"
}
],
"version": "v1"
},
"resourceGroup": "RESOURCE_GROUP_NAME",
"systemData": {
"createdAt": "2023-09-13T13:08:13.2180324Z",
"createdBy": "<SEU-EMAIL>",
"createdByType": "User",
"lastModifiedAt": "2023-09-13T13:08:13.2180324Z",
"lastModifiedBy": "<SEU-EMAIL>",
"lastModifiedByType": "User"
},
"type": "Microsoft.App/managedEnvironments/daprComponents"
}
Para demonstrar o uso do Dapr, criamos um projeto de exemplo que utiliza o componente de pub/sub
do Dapr para publicar e consumir mensagens de um tópico do Service Bus
. O projeto é composto por duas aplicações, uma que publica mensagens e outra que consome as mensagens.
O building block
de publicação e inscrição do Dapr fornece um framework
de API agnóstico à plataforma para enviar e receber mensagens. Seus serviços publicam mensagens em um tópico. Seus serviços se inscrevem em um tópico para consumir mensagens. O serviço faz chamadas à API
de publicação/assinatura (pub/sub)
no sidecar
do Dapr. O sidecar
então faz chamadas para o componente do Dapr criado previamente que encapsula a lógica para comunicação com o Service Bus
.
Utilizamos o Dapr .NET Sdk, para lidar com a complexidade da comunicação com o container sidecar
do Dapr. O código abaixo mostra como configurar o DaprClient
para publicar mensagens no tópico TOPICO
:
using Dapr.Client;
var daprClient = new DaprClientBuilder().Build();
await daprClient.PublishEventAsync<int>("NOME_DO_COMPONENTE", "TOPICO", new object());
Esse trecho de código será responsável por encapsular a chamada HTTP
ou gRPC
para o sidecar
do Dapr, essa chamada vai acontecer no Endpoint
:
http://localhost:<dapr-port>/v1.0/publish/<pub-sub-name>/<topic>
Essa aplicação vai funcionar de forma passiva, onde o sidecar
do Dapr vai ficar escutando o tópico TOPICO
e quando uma mensagem for publicada, o sidecar
vai fazer uma chamada HTTP
ou gRPC
para a aplicação consumidora.
No início, o tempo de execução do Dapr chamará o aplicativo em um ponto de extremidade conhecido para identificar e criar as assinaturas necessárias:
http://localhost:<appPort>/dapr/subscribe
O sidecar
vai utilizar da resposta desse endpoint
como insumo para mapear quais são os tópicos que a aplicação consome.
A biblioteca Dapr.AspNetCore deixa esse processo trivial para o desenvolvedor, onde com poucas linhas de código, podemos configurar esse endpoint
que o sidecar
vai usar posteriormente.
O primeiro passo é instalar o pacote Dapr.AspNetCore
:
dotnet add package Dapr.AspNetCore
Podemos utilizar a classe de atributo Topic
sobre o método que vai receber as mensagens do tópico TOPICO
, dessa forma:
[Topic("NOME_DO_COMPONENTE","TOPICO")]
[HttpPost("/count")]
public IActionResult Post(int value) {
...
return Ok();
}
Após isso, precisamos configurar a aplicação fazendo modificações na classe Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddDapr();
var app = builder.Build();
app.UseCloudEvents();
app.MapControllers();
app.MapSubscribeHandler();
app.Run();
O segmento de código .AddDapr()
registra os serviços necessários como o DaprClient
. O segmento de código MapSubscribeHandler()
registra o endpoint
que o sidecar
vai utilizar para identificar e criar as assinaturas necessárias, para atingir esse objetivo ele mapeia todos os endpoints decorados com o atributo Topic
.
A fins de curiosidade, a lógica que o método MapSubscribeHandler()
utiliza para identificar os endpoints decorados com o atributo Topic
é a seguinte:
private static IEndpointConventionBuilder CreateSubscribeEndPoint(IEndpointRouteBuilder endpoints, SubscribeOptions options = null)
{
if (endpoints is null)
{
throw new System.ArgumentNullException(nameof(endpoints));
}
return endpoints.MapGet("dapr/subscribe", async context =>
{
var logger = context.RequestServices.GetService<ILoggerFactory>().CreateLogger("DaprTopicSubscription");
var dataSource = context.RequestServices.GetRequiredService<EndpointDataSource>();
var subscriptions = dataSource.Endpoints
.OfType<RouteEndpoint>()
.Where(e => e.Metadata.GetOrderedMetadata<ITopicMetadata>().Any(t => t.Name != null))
.SelectMany(e =>
{
...
})
.Distinct()
.GroupBy(e => new { e.PubsubName, e.Name })
.Select(e => e.OrderBy(e => e.Priority))
.Select(e =>
{
...
})
.OrderBy(e => (e.PubsubName, e.Topic));
await context.Response.WriteAsync(JsonSerializer.Serialize(subscriptions,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}));
});
}
Utilizando a classe EndpointDatasource a lib consegue identificar todos os endpoints decorados com o atributo Topic
através do seguimento de código .Where(e => e.Metadata.GetOrderedMetadata<ITopicMetadata>().Any(t => t.Name != null))
. No final ele vai retornar um json
com todas as assinaturas que o sidecar
vai criar, no endpoint dapr/subscribe
.
Para saber mais veja o código fonte no repositório: Dapr.AspNetCore.
Para testar seu projeto, você pode executar as aplicações localmente, para isso, você precisa instalar o Dapr CLI.
Após instalar o Dapr CLI, você precisa inicializar o Dapr localmente, para isso, rode o comando:
dapr init
Após isso, você precisa configurar o componente, para isso entre na pasta .dapr
no local de instalação, depois na pasta components
, crie o arquivo pubsub.yaml
e cole o conteúdo abaixo:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.azure.servicebus.queues
version: v1
metadata:
- name: connectionString
value: "<CONNECTION_STRING>"
Após a criação do componente, basta rodar as aplicações, para isso, abra os terminais no diretório das aplicações e rode os comandos:
dapr run --app-id publisher -- dotnet run
dapr run --app-id subscriber -- dotnet run
Ao realizar a leitura deste artigo, você estará pronto para iniciar sua jornada de adoção do Dapr no Azure Container Apps. O Dapr é uma ferramenta poderosa para desacoplar aplicações, ele permite que você implante e gerencie contêineres sem se preocupar com a infraestrutura. Além disso, o Dapr é independente de plataforma, o que significa que você pode executar seus aplicativos localmente, em qualquer cluster Kubernetes, em máquinas virtuais ou físicas e em outros ambientes de hospedagem que o Dapr integra.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.