Microsoft Azure - WebJobs

O Azure WebJobs é um ótimo artifício para executar processos em segundo plano dentro do contexto de um App Service (Web) ou App Mobile dentro do Microsoft Azure. Você pode publicar ou fazer upload de um aplicativo e executá-lo sob demanda a cada intervalo de tempo, de forma contínua ou sob agendamento (CRON).

Dando continuidade ao assunto relacionado ao Microsoft Azure Storage - Tables, vamos ver como é possível utilizar o storage para receber mensagens, processar e interpretar as mensagens a fim de podermos ler e escrever dentro do Storage Tables através do WebJob.

App Service

O WebJob precisa estar embaixo de um App Service para funcionar, isso significa que você precisa de um App Service (WebSite, veja imagem 9), pois, o fato dele poder ser executado de forma contínua e dentro de um contexto web faz com que ele necessite da execuçao do Framework (processo "w3wp.exe"). Além disso, para a execução contínua é necessário que o App Service esteja configurado como "Always On", isso significa que o processo w3wp.exe nunca sairá da mesmória mesmo que não hajam acessos dos usuários. Porém a propriedade "Always On" somente está disponível nas aplicações com planos pagos. Caso contrário, após instantes do WebJob sendo executado ele vai gerar uma exceção que ocasionará a parada de sua execução.
A imagem a seguir demonstra a configuração da propriedade "Always On".

Projeto

Através do Visual Studio (utilizo o 2015 Enterprise - Update 3), crie um novo projeto Azure WebJob. Ele é encontrado dentro dos templates já existentes conforme exibido na imagem a seguir.

Configuração

Após a criação do projeto, precisaremos configurar duas "strings de conexão", AzureWebJobsDashboard que será utilizado para armazenar os logs gerados pelo WebJob sendo que esses logs são gerados de forma automática, sem a influência da codificação feita pelo usuário e AzureWebJobsStorage que fará o armazenamento também dos logs mas o que foi gerado por intervenção da condificação do usuário. Ambas as strings de conexão devem estar relacionadas ao acesso ao storage, pois, todas as informações geradas pelo WebJob são armazenadas em um storage, porém nada impede que o armazenamento das informações seja feito em storages distintos. Para mais detalhes veja este post no kloud.com.au.

<connectionStrings>  
    <add name="AzureWebJobsDashboard" connectionString="DefaultEndpointsProtocol=https;AccountName=storage;AccountKey=L43uEWSiVqfKiCs9aVOOyfCwdHmWkGxNDbOVn1qAvNoea"/>
    <add name="AzureWebJobsStorage" connectionString="DefaultEndpointsProtocol=https;AccountName=storage;AccountKey=L43uEWSiVqfKiCs9aVOOyfCwdHmWkGxNDbOVn1qAvNoea"/>
</connectionStrings>  

Inicialização

Após a criação do projeto, dentro do arquivo "Programa.cs" precisamos ajustar o código conforme abaixo.

private static void Main()  
{
    var webJobsDashboard = ConfigurationManager.ConnectionStrings["AzureWebJobsDashboard"].ConnectionString;
    var webJobsStorage = ConfigurationManager.ConnectionStrings["AzureWebJobsStorage"].ConnectionString;

    if (string.IsNullOrWhiteSpace(webJobsDashboard) || string.IsNullOrWhiteSpace(webJobsStorage))
    {
        Console.WriteLine("Please add the Azure Storage account credentials in App.config");
        return;
    }

    var config = new JobHostConfiguration
    {
        StorageConnectionString = webJobsStorage,
        DashboardConnectionString = webJobsDashboard
    };

    using (var host = new JobHost(config))
    {
        // The following code ensures that the WebJob will be running continuously
        host.RunAndBlock();
    }
}

Codificação

Para o funcionamento da lógica, a fim de complementar o post relacionado ao Microsoft Azure Storage - Tables seguirei os seguintes passos:

  • Após o envio dos dados para a table será criado uma mensagem na fila do storage (queue);
  • O WebJob será acionado através da mensagem, fará a leitura dos dados e atualizará o registro na table;
  • Além disso, veremos como disparar um evento no WebJob de forma manual;
  • Por vim, veremos como fazer com que um evento do WebJob ao ser acionado já automaticamente receba por parâmetro os registros da table.

Ao final do envio de informações para uma table, vamos acrescentar o seguinte código, sendo que "queue-cliente" será o nome da fila que utilizaremos para enviar a mensagem. A mensagem enviada propriamente dita será a RowKey criada para o cadastro do objeto "Cliente".

using Microsoft.WindowsAzure.Storage.Queue;

var queueClient = storageAccount.CreateCloudQueueClient();  
var queue = queueClient.GetQueueReference("queue-cliente");  
queue.CreateIfNotExists();

queue.AddMessageAsync(new CloudQueueMessage(cliente.RowKey));  

Através do storage explorer será possível visualizar a mensagem antes que ela seja processada pelo WebJob conforme imagem a seguir.

Na criação do WebJob, automaticamente foi criada uma classe (e um arquivo) chamado "Functions". Dentro dele codificaremos 3 funções.
A primeira delas (código a seguir) é executada automaticamente quando uma mensagem chegar à fila que criamos anteriormente.
O método "LerQueue" recebe a RowKey por parâmetro e o WebJob fará a consulta dessa informações no storage table e em seguida atualizará a propriedade "Ler".
Existem diversas formas de fazer a leitura dos registros de uma table, para isso, sugiro ver mais informações aqui.

using Microsoft.Azure.WebJobs;  
using Microsoft.WindowsAzure.Storage;  
using Microsoft.WindowsAzure.Storage.Table;  
public static void LerQueue([QueueTrigger("queue-cliente")] string rowKey, TextWriter log)  
{
    Console.WriteLine($"LerQueue: {rowKey}");

    var storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);
    var tableClient = storageAccount.CreateCloudTableClient();
    var table = tableClient.GetTableReference("Clientes");
    table.CreateIfNotExists();

    var retrieveOperation = TableOperation.Retrieve<Cliente>("Cliente", rowKey);
    var cliente = ((Cliente)table.Execute(retrieveOperation).Result);
    Console.WriteLine($"2 - Email: {cliente.Email}");

    cliente.Ler = 1;
    var updateOperation = TableOperation.Replace(cliente);
    table.Execute(updateOperation);
    Console.WriteLine($"2 - Ler = 1");
}

Temos também um método chamado "InserirManual". Este método poderá ser executado de forma manual através das Functions do dashboard do WebJob, veremos mais adiante.

[NoAutomaticTrigger]
public static void InserirManual(  
[Table("Clientes")] ICollector<Cliente> table, TextWriter log)
{
    for (int i = 0; i < 5; i++)
    {
        table.Add(new Cliente
        {
            PartitionKey = "Cliente",
            RowKey = Guid.NewGuid().ToString(),
            Email = $"mail{i}@mail.com.br"
        });

        log.WriteLine($"RowKey: {i}");
    }
}

Por fim, teremos um método chamado "LerTudo" que receberá por parâmetro a lista dos registros da tabela "Clientes". Este método poderá ser executado de forma manual através das Functions do dashboard do WebJob, veremos mais adiante.

public static void LerTudo([Table("Clientes")] IQueryable<Cliente> tableBinding, TextWriter log)  
{
    foreach (Cliente person in tableBinding)
    {
        log.WriteLine($"PK: {person.PartitionKey}, RK: {person.RowKey}, Email: {person.Email}");
    }
}

Publicação do WebJob

Para publicar, clique com o botão direto sobre o projeto do WebJob e escolha a opção "Publish as Azure WebJob" conforme exibido na imagem a seguir.

Em seguida, será exibida a tela de configuração da execução do WebJob (conforme imagem a seguir). Você poderá escolher a forma de execução do WebJob, contínuo, sob demanda ou programado. Após a confirmação, será criado o arquivo "webjob-publish-settings.json" dentro das properties do projeto do WebJob com a configuração desejada. Mais detalhes sobre esta configuração podem ser encontradas aqui.

Depois disso, utilize suas credenciais de configuração da conta do Microsof Azure, ou o arquivo de configuração de publicação do App Service para publicar o WebJob.
Um pequeno detalhe que eu gostaria de compartilhar com todos aqui é sobre a publicação. Eventualmente o Visual Studio demora para exibir a mensagem "Web App was published successfully ..." (conforme imagem a seguir), portanto, aguarde até que esse processo seja concluído.

App Service - Application Settings

É importante que a sua aplicação web onde o WebJob está vinculado ou que você configure de forma manual as 2 strings de conexão (conforme imagem a seguir) que vimos no começo AzureWebJobsDashboard e AzureWebJobsStorage. Isso fará com que sejam habilitadas as funcionalidades completas do dashboard do WebJob dentro do Microsoft Azure.

Caso contrário, no dashboard você poderá visualizar um alerta conforme imagem a seguir que não impedirá o funcionamento do WebJob.

Logs e Run

Após a configuração das strings de conexão, podemos iniciar o WebJob e monitorar sua execução. Para iniciar utiliza o botão Run e para visualizar o dashboard utilize o botão Logs conforme imagem a seguir. Para ambos os casos o menu de contexto também funciona.

Após iniciar o WebJob e ir para a o dashboard através do botão Logs, poderemos visualizar a execução e uma espécie de console onde podemos escrever informações do WebJob durante sua execução para monitoramento. A imagem a seguir demonstra como as informações estão dispostas.

Após aproximadamente 3 minutos, você vai perceber que o serviço vai falhar a sua execução. Isso ocorre por conta do "Always On" não estar habilitado no App Service.

Neste primeiro momento, como podemos perceber, houve a execução do método que verifica a fila de mensagens e como enviamos uma mensagem para a fila "queue-cliente", o WebJob identifica que há uma mensagem para o método correspondente e executa o código desenvolvido. Veja que no console, as últimas três linhas exibem dados customizados que colocamos através do "Console.WriteLine".

Por fim, no topo do dashboard, temos um menu chamado "Functions", através dele é possível visualizar os métodos do WebJob e também os métodos que podem ser executados de forma manual, conforme exibido na imagem a seguir.

Particularidades

Quando falamos das filas, é importante destacar que o WebJob ao encontrar alguma falha na execução do método ele automaticamente faz 5 tentativas de execução, após isso, ele joga a mensagem em uma fila chamada "queue-cliente-poison". Através do dashboard é possível visualizar os erros que ocorreram.
Com algumas linhas de configuração, você consegue alterar características importantes do WebJob.

static void Main(string[] args)  
{
    JobHostConfiguration config = new JobHostConfiguration();
    config.Queues.BatchSize = 8;
    config.Queues.MaxDequeueCount = 4;
    config.Queues.MaxPollingInterval = TimeSpan.FromSeconds(15);
    JobHost host = new JobHost(config);
    host.RunAndBlock();
}

Veja mais sobre essa configuração (na seção Configure QueueTrigger settings) e outras aqui.

Debug

É permitido realizar o debug remoto do WebJob dentro do Azure desde que ele esteja publicado em modo debug. Veja o vídeo de como fazer isso aqui.

Dicas

Veja a Introdução ao Armazenamento de Filas;
Outras informações sobre execução manual de métodos com [Storage Tables e WebJobs] (https://azure.microsoft.com/en-us/documentation/articles/websites-dotnet-webjobs-sdk-storage-tables-how-to/);

Conclusão

Como falamos sobre Microsoft Azure Storage - Tables e o complementamos com a soma da funcionalidade dos WebJobs, podemos dizer que eles se complementam, na forma de utilizarem o storage e também como se adaptam facilmente à manipulação das informações do storage.
Sobre a questão de aplicabilidade, podemos utilizar o WebJob para disparar um evento através de uma mensagem na fila para processar dados vindos de uma integração gravando-os diretamente no banco de dados e atualização a table no storage.
Podemos também utilizá-lo para eventualmente realizar processamentos agendandos durante a madrugada, quando o número de acessos ao sistema é menor bem como a utilização do banco de dados.
Além disso, o próprio WebJob pode gerar informações para serem armazenadas como log ou auditoria de execução de determinadas funções dentro das tables do storage.