Analisar as limitações de um aplicativo Web baseado em sondagem

Concluído

Aplicativo Web baseado em sondagem.

A arquitetura atual do aplicativo relata as informações de ações buscando todas as informações de preços de ações do servidor com base em um temporizador. Frequentemente, este design é chamado de design baseado em sondagem.

Servidor

As informações de preços de ações são armazenadas em um banco de dados do Azure Cosmos DB. Quando disparada por uma solicitação HTTP, a função getStocks retorna todas as linhas do banco de dados.

import { app, input } from "@azure/functions";

const cosmosInput = input.cosmosDB({
    databaseName: 'stocksdb',
    containerName: 'stocks',
    connection: 'COSMOSDB_CONNECTION_STRING',
    sqlQuery: 'SELECT * from c',
});

app.http('getStocks', {
    methods: ['GET'],
    authLevel: 'anonymous',
    extraInputs: [cosmosInput],
    handler: (request, context) => {
        const stocks = context.extraInputs.get(cosmosInput);
        
        return {
            jsonBody: stocks,
        };
    },
});
  • Obter dados: A primeira seção de código, cosmosInput, obtém todos os itens da tabela stocks, com a consulta SELECT * from c, no banco de dados stocksdb no Cosmos DB.
  • Retornar dados: A segunda seção de código, app.http, recebe esses dados na função como uma entrada em context.extraInputs e, em seguida, retorna-os como o corpo da resposta de volta para o cliente.

Cliente

O cliente de exemplo usa o Vue.js para compor a interface do usuário e o cliente de busca para manipular as solicitações para a API.

A página HTML usa um temporizador para enviar uma solicitação ao servidor a cada cinco segundos a fim de solicitar as ações. A resposta retorna uma matriz de ações, que são exibidas ao usuário.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" integrity="sha256-8B1OaG0zT7uYA572S2xOxWACq9NXYPQ+U5kHPV1bJN4=" crossorigin="anonymous" />
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
    <title>Stocks | Enable automatic updates in a web application using Azure Functions and SignalR</title>
</head>
<body>
    
    <!-- BEGIN: Replace markup in this section -->
    <div id="app" class="container">
        <h1 class="title">Stocks</h1>
        <div id="stocks">
            <div v-for="stock in stocks" class="stock">
                <div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
                <div class="change">Change:
                    <span :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
                        {{ stock.changeDirection }}{{ stock.change }}
                    </span></div>
            </div>
        </div>
    </div>
    <!-- END  -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
    <script src="bundle.js" type="text/javascript"></script>
</body>
</html>
import './style.css';

function getApiUrl() {

    const backend = process.env.BACKEND_URL;
    
    const url = (backend) ? `${backend}` : ``;
    return url;
}

const app = new Vue({
    el: '#app',
    interval: null,
    data() { 
        return {
            stocks: []
        }
    },
    methods: {
        async update() {
            try {
                
                const url = `${getApiUrl()}/api/getStocks`;
                console.log('Fetching stocks from ', url);

                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                app.stocks = await response.json();
            } catch (ex) {
                console.error(ex);
            }
        },
        startPoll() {
            this.interval = setInterval(this.update, 5000);
        }
    },
    created() {
        this.update();
        this.startPoll();
    }
});

Depois que o método startPoll inicia a sondagem, o método update é chamado a cada cinco segundos. Dentro do método update, uma solicitação GET é enviada ao ponto de extremidade de API /api/getStocks e o resultado é definido como app.stocks, o que atualiza a interface do usuário.

O código do servidor e do cliente é relativamente simples: obter todos os dados e exibir todos eles. Conforme descobrimos em nossa análise, essa simplicidade gera algumas limitações.

Análise da solução de protótipo

Como engenheiro da Tailwind Traders, você identificou algumas das desvantagens dessa abordagem de sondagem baseada em temporizador.

  • Solicitações de API desnecessárias: No protótipo de sondagem baseado em temporizador, o aplicativo cliente contata o servidor havendo ou não alterações nos dados subjacentes.

  • Atualizações de página desnecessárias: Depois que os dados são retornados do servidor, toda a lista de ações é atualizada na página da Web, mesmo que nenhum dado tenha sido alterado. Esse mecanismo de sondagem é uma solução ineficiente.

  • Intervalos de sondagem: Selecionar o melhor intervalo de sondagem para seu cenário também é um desafio. A sondagem força você a escolher entre quanto cada chamada para o back-end custa e com que rapidez você deseja que seu aplicativo responda a novos dados. Os atrasos geralmente existem entre o momento em que novos dados ficam disponíveis e o momento em que o aplicativo os detecta. A ilustração a seguir mostra esse problema.

    Uma ilustração que mostra uma linha do tempo e um gatilho de sondagem verificando se há novos dados a cada cinco minutos. Os novos dados ficam disponíveis após sete minutos. O aplicativo não fica ciente dos novos dados até a próxima sondagem, que ocorre em dez minutos.

    Na pior das hipóteses, o atraso potencial para detectar novos dados é igual ao intervalo de sondagem. Sendo assim, por que não usar um intervalo menor?

  • Volume de dados: Conforme o aplicativo é dimensionado, a quantidade de dados trocados entre o cliente e o servidor se torna um problema. Cada cabeçalho de solicitação HTTP inclui centenas de bytes de dados, bem como o cookie da sessão. Toda essa sobrecarga, especialmente sob carga pesada, gera desperdício de recursos e carrega o servidor desnecessariamente.

Agora que você está mais familiarizado com o protótipo, é hora de executar o aplicativo no computador.

Como dar suporte ao CORS

No arquivo local.settings.json do aplicativo de funções, a seção Host inclui as configurações a seguir.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "<STORAGE_CONNECTION_STRING>",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
    "COSMOSDB_CONNECTION_STRING": "<COSMOSDB_CONNECTION_STRING>"
  },
  "Host" : {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:3000",
    "CORSCredentials": true
  }
}

Essa configuração permite que um aplicativo Web em execução em localhost:3000 faça solicitações ao aplicativo de funções em execução em localhost:7071. A propriedade CORSCredentials instrui o aplicativo de funções a aceitar cookies de credencial da solicitação.

O CORS (compartilhamento de recurso entre origens) é um recurso HTTP que permite que um aplicativo Web em execução em um domínio acesse recursos em outro domínio. Os navegadores da Web implementam uma restrição de segurança conhecida como política de mesma origem que evita que uma página da Web chame as APIs em um domínio diferente; o CORS fornece uma maneira segura de permitir que um domínio (o domínio de origem) chame APIs em outro domínio.

Durante a execução local, o CORS é configurado para você no arquivo local.settings.json da amostra, que nunca é publicado. Ao implantar o aplicativo cliente (unidade 7), você também precisa atualizar as configurações do CORS no aplicativo de funções para permitir o acesso do aplicativo cliente.