Práticas recomendadas de desenvolvimento para a Web (criando aplicativos de nuvem Real-World com o Azure)

por Rick Anderson, Tom Dykstra

Baixar Corrigir Projeto ou Baixar Livro Eletrônico

O livro eletrônico Building Real World Cloud Apps with Azure é baseado em uma apresentação desenvolvida por Scott Guthrie. Ele explica 13 padrões e práticas que podem ajudá-lo a desenvolver aplicativos Web para a nuvem com êxito. Para obter informações sobre o livro eletrônico, consulte o primeiro capítulo.

Os três primeiros padrões foram sobre a configuração de um processo de desenvolvimento ágil; o restante é sobre arquitetura e código. Esta é uma coleção de práticas recomendadas de desenvolvimento para a Web:

Essas práticas são válidas para todo o desenvolvimento na Web, não apenas para aplicativos de nuvem, mas são especialmente importantes para aplicativos de nuvem. Eles trabalham juntos para ajudá-lo a fazer o uso ideal do dimensionamento altamente flexível oferecido pelo ambiente de nuvem. Se você não seguir essas práticas, encontrará limitações ao tentar dimensionar seu aplicativo.

Camada da Web sem estado atrás de um balanceador de carga inteligente

A camada da Web sem estado significa que você não armazena nenhum dado de aplicativo na memória do servidor Web ou no sistema de arquivos. Manter sua camada da Web sem estado permite que você forneça uma melhor experiência do cliente e economize dinheiro:

  • Se a camada da Web estiver sem estado e estiver atrás de um balanceador de carga, você poderá responder rapidamente às alterações no tráfego do aplicativo adicionando ou removendo servidores dinamicamente. No ambiente de nuvem em que você paga apenas pelos recursos do servidor, desde que realmente os use, essa capacidade de responder a alterações na demanda pode se traduzir em grandes economias.
  • Uma camada da Web sem estado é arquitetônicamente muito mais simples para escalar horizontalmente o aplicativo. Isso também permite que você responda às necessidades de dimensionamento mais rapidamente e gaste menos dinheiro em desenvolvimento e teste no processo.
  • Os servidores de nuvem, como servidores locais, precisam ser corrigidos e reinicializados ocasionalmente; e se a camada da Web for sem estado, o redirecionamento do tráfego quando um servidor ficar inativo temporariamente não causará erros ou comportamento inesperado.

A maioria dos aplicativos do mundo real precisa armazenar o estado de uma sessão da Web; o ponto main aqui é não armazená-lo no servidor Web. Você pode armazenar o estado de outras maneiras, como no cliente em cookies ou fora do processo do lado do servidor em ASP.NET estado de sessão usando um provedor de cache. Você pode armazenar arquivos no Armazenamento de Blobs do Windows Azure em vez do sistema de arquivos local.

Como exemplo de como é fácil dimensionar um aplicativo nos Sites do Windows Azure se a camada da Web não tiver estado, confira a guia Escala para um Site do Windows Azure no portal de gerenciamento:

Guia Escala

Se você quiser adicionar servidores Web, basta arrastar o controle deslizante de contagem de instâncias para a direita. Defina-o como 5 e clique em Salvar e, em segundos, você tem cinco servidores Web no Windows Azure manipulando o tráfego do seu site.

Cinco instâncias

Você pode definir facilmente a contagem de instâncias como 3 ou voltar para 1. Quando você volta, você começa a economizar dinheiro imediatamente porque o Windows Azure cobra por minuto, não por hora.

Você também pode instruir o Windows Azure a aumentar ou diminuir automaticamente o número de servidores Web com base no uso da CPU. No exemplo a seguir, quando o uso da CPU ficar abaixo de 60%, o número de servidores Web diminuirá para um mínimo de 2 e, se o uso da CPU ultrapassar 80%, o número de servidores Web será aumentado até um máximo de 4.

Dimensionar por uso da CPU

Ou e se você souber que seu site só estará ocupado durante o horário de trabalho? Você pode instruir o Windows Azure a executar vários servidores durante o dia e diminuir para uma única noite de servidor, noites e finais de semana. A série de capturas de tela a seguir mostra como configurar o site para executar um servidor fora do horário comercial e quatro servidores durante o horário de trabalho das 8h às 17h.

Dimensionar por agendamento

Definir horários de agendamento

Agenda do dia

Agenda da noite de semana

Agenda de fim de semana

E, claro, tudo isso pode ser feito em scripts, bem como no portal.

A capacidade do aplicativo de escalar horizontalmente é quase ilimitada no Windows Azure, desde que você evite impedimentos para adicionar ou remover dinamicamente VMs do servidor, mantendo a camada da Web sem estado.

Evitar o estado de sessão

Geralmente, não é prático em uma aplicativo em nuvem real evitar o armazenamento de alguma forma de estado para uma sessão de usuário, mas algumas abordagens afetam o desempenho e a escalabilidade mais do que outras. Se você precisar armazenar o estado, a melhor solução será manter o estado em uma quantidade pequena e armazená-lo em cookies. Se isso não for viável, a próxima melhor solução será usar ASP.NET estado de sessão com um provedor para cache distribuído na memória. A pior solução de um ponto de vista de escalabilidade e desempenho é usar um provedor de estado de sessão com backup em um banco de dados.

Usar uma CDN para armazenar em cache ativos de arquivo estático

CDN é um acrônimo para Rede de Distribuição de Conteúdo. Você fornece ativos de arquivo estáticos, como imagens e arquivos de script para um provedor cdn, e o provedor armazena esses arquivos em cache em data centers em todo o mundo para que, onde quer que as pessoas acessem seu aplicativo, eles obtenham resposta relativamente rápida e baixa latência para os ativos armazenados em cache. Isso acelera o tempo de carregamento geral do site e reduz a carga em seus servidores Web. As CDNs são especialmente importantes se você estiver alcançando um público amplamente distribuído geograficamente.

O Windows Azure tem uma CDN e você pode usar outras CDNs em um aplicativo executado no Windows Azure ou em qualquer ambiente de hospedagem na Web.

Usar o suporte assíncrono do .NET 4.5 para evitar o bloqueio de chamadas

O .NET 4.5 aprimorou as linguagens de programação C# e VB para tornar muito mais simples lidar com tarefas de forma assíncrona. O benefício da programação assíncrona não é apenas para situações de processamento paralelo, como quando você deseja iniciar várias chamadas de serviço Web simultaneamente. Ele também permite que o servidor Web tenha um desempenho mais eficiente e confiável em condições de alta carga. Um servidor Web tem apenas um número limitado de threads disponíveis e, em condições de alta carga quando todos os threads estão em uso, as solicitações de entrada precisam aguardar até que os threads sejam liberados. Se o código do aplicativo não tratar tarefas como consultas de banco de dados e chamadas de serviço Web de forma assíncrona, muitos threads serão desnecessariamente vinculados enquanto o servidor aguarda uma resposta de E/S. Isso limita a quantidade de tráfego que o servidor pode lidar em condições de alta carga. Com a programação assíncrona, os threads que estão aguardando um serviço Web ou banco de dados retornar dados são liberados para atender a novas solicitações até que os dados sejam recebidos. Em um servidor Web ocupado, centenas ou milhares de solicitações podem ser processadas imediatamente, o que, de outra forma, estaria aguardando a liberação de threads.

Como você viu anteriormente, é tão fácil diminuir o número de servidores Web que manipulam seu site como é para aumentá-los. Portanto, se um servidor puder obter uma taxa de transferência maior, você não precisará de tantos deles e poderá diminuir seus custos porque precisa de menos servidores para um determinado volume de tráfego do que seria de outra forma.

O suporte para o modelo de programação assíncrona do .NET 4.5 está incluído no ASP.NET 4.5 para Web Forms, MVC e API Web; no Entity Framework 6 e na API de Armazenamento do Microsoft Azure.

Suporte assíncrono no ASP.NET 4.5

No ASP.NET 4.5, o suporte para programação assíncrona foi adicionado não apenas à linguagem, mas também às estruturas MVC, Web Forms e API Web. Por exemplo, um ASP.NET método de ação do controlador MVC recebe dados de uma solicitação da Web e passa os dados para uma exibição que, em seguida, cria o HTML a ser enviado para o navegador. Frequentemente, o método de ação precisa obter dados de um banco de dados ou serviço Web para exibi-los em uma página da Web ou salvar dados inseridos em uma página da Web. Nesses cenários, é fácil tornar o método de ação assíncrono: em vez de retornar um objeto ActionResult, você retorna Task<ActionResult> e marca o método com o palavra-chave assíncrono. Dentro do método , quando uma linha de código inicia uma operação que envolve o tempo de espera, você a marca com a palavra-chave await.

Aqui está um método de ação simples que chama um método de repositório para uma consulta de banco de dados:

public ActionResult Index()
{
    string currentUser = User.Identity.Name;
    var result = fixItRepository.FindOpenTasksByOwner(currentUser);

    return View(result);
}

E aqui está o mesmo método que manipula a chamada de banco de dados de forma assíncrona:

public async Task<ActionResult> Index()
{
    string currentUser = User.Identity.Name;
    var result = await fixItRepository.FindOpenTasksByOwnerAsync(currentUser);

    return View(result);
}

Nos covers, o compilador gera o código assíncrono apropriado. Quando o aplicativo faz a chamada para FindTaskByIdAsync, ASP.NET faz a solicitação FindTask e, em seguida, desenrola o thread de trabalho e o disponibiliza para processar outra solicitação. Quando a solicitação FindTask é feita, um thread é reiniciado para continuar processando o código que vem após essa chamada. Durante o intervalo entre quando a solicitação FindTask é iniciada e quando os dados são retornados, você tem um thread disponível para fazer um trabalho útil que, de outra forma, seria vinculado à espera da resposta.

Há alguma sobrecarga para código assíncrono, mas em condições de baixa carga, essa sobrecarga é insignificante, enquanto em condições de alta carga você é capaz de processar solicitações que, de outra forma, seriam mantidas aguardando threads disponíveis.

Foi possível fazer esse tipo de programação assíncrona desde ASP.NET 1.1, mas foi difícil escrever, propenso a erros e difícil de depurar. Agora que simplificamos a codificação em ASP.NET 4.5, não há mais razão para não fazer isso.

Suporte assíncrono no Entity Framework 6

Como parte do suporte assíncrono na versão 4.5, enviamos suporte assíncrono para chamadas de serviço Web, soquetes e E/S do sistema de arquivos, mas o padrão mais comum para aplicativos Web é atingir um banco de dados e nossas bibliotecas de dados não dão suporte à assíncrona. Agora, o Entity Framework 6 adiciona suporte assíncrono para acesso ao banco de dados.

No Entity Framework 6, todos os métodos que fazem com que uma consulta ou comando seja enviado ao banco de dados têm versões assíncronas. O exemplo aqui mostra a versão assíncrona do método Find .

public async Task<FixItTask> FindTaskByIdAsync(int id)
{
    FixItTask fixItTask = null;
    Stopwatch timespan = Stopwatch.StartNew();

    try
    {
        fixItTask = await db.FixItTasks.FindAsync(id);
        
        timespan.Stop();
        log.TraceApi("SQL Database", "FixItTaskRepository.FindTaskByIdAsync", timespan.Elapsed, "id={0}", id);
    }
    catch(Exception e)
    {
        log.Error(e, "Error in FixItTaskRepository.FindTaskByIdAsynx(id={0})", id);
    }

    return fixItTask;
}

E esse suporte assíncrono funciona não apenas para inserções, exclusões, atualizações e localizações simples, mas também funciona com consultas LINQ:

public async Task<List<FixItTask>> FindOpenTasksByOwnerAsync(string userName)
{
    Stopwatch timespan = Stopwatch.StartNew();

    try
    {
        var result = await db.FixItTasks
            .Where(t => t.Owner == userName)
            .Where(t=>t.IsDone == false)
            .OrderByDescending(t => t.FixItTaskId).ToListAsync();

        timespan.Stop();
        log.TraceApi("SQL Database", "FixItTaskRepository.FindTasksByOwnerAsync", timespan.Elapsed, "username={0}", userName);

        return result;
    }
    catch (Exception e)
    {
        log.Error(e, "Error in FixItTaskRepository.FindTasksByOwnerAsync(userName={0})", userName);
        return null;
    }
}

Há uma Async versão do ToList método porque, nesse código, esse é o método que faz com que uma consulta seja enviada ao banco de dados. Os Where métodos e OrderByDescending configuram apenas a consulta, enquanto o ToListAsync método executa a consulta e armazena a resposta na result variável .

Resumo

Você pode implementar as melhores práticas de desenvolvimento para a Web descritas aqui em qualquer estrutura de programação da Web e em qualquer ambiente de nuvem, mas temos ferramentas no ASP.NET e no Windows Azure para facilitar. Se você seguir esses padrões, poderá escalar horizontalmente facilmente sua camada da Web e minimizará suas despesas, pois cada servidor poderá lidar com mais tráfego.

O próximo capítulo analisa como a nuvem habilita cenários de logon único.

Recursos

Para obter mais informações, consulte os recursos a seguir.

Servidores Web sem estado:

CDN:

Programação assíncrona:

Para obter práticas recomendadas adicionais de desenvolvimento para a Web, consulte os seguintes recursos: