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

Concluído

Aplicação Web baseada em sondagem.

A arquitetura atual do aplicativo relata informações de estoque buscando todas as informações de preço de ações do servidor com base em um temporizador. Esse design é frequentemente chamado de design baseado em sondagem.

Servidor

As informações de preço das ações são armazenadas em um banco de dados do Azure Cosmos DB. Quando acionada 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 do código, cosmosInput, obtém todos os itens na tabela stocks, com a consulta SELECT * from c, no banco de dados stocksdb no Cosmos DB.
  • Retornar dados: A segunda seção do código, app.http, recebe esses dados na função como uma entrada no context.extraInputs depois os retorna como o corpo da resposta de volta ao cliente.

Cliente

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

A página HTML usa um temporizador para enviar uma solicitação ao servidor a cada cinco segundos para solicitar estoques. A resposta retorna uma lista de stocks, que são então exibidos para o utilizador.

<!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();
    }
});

Uma vez que o método startPoll começa a sondagem, o método update é chamado a cada cinco segundos. Dentro do método update, um pedido GET é enviado para o endpoint da API /api/getStocks e o resultado é definido como app.stocks, que atualiza a interface de utilizador.

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

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

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 entra em contato com o servidor se existem ou não alterações nos dados subjacentes.

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

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

    Uma ilustração mostrando uma linha do tempo e um gatilho de sondagem verificando novos dados a cada cinco minutos. Novos dados ficam disponíveis após sete minutos. O aplicativo não está ciente dos novos dados até a próxima pesquisa, que ocorre aos 10 minutos.

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

  • Quantidade de dados: À medida que o aplicativo é dimensionado, a quantidade de dados trocados entre o cliente e o servidor torna-se um problema. Cada cabeçalho de solicitação HTTP inclui centenas de bytes de dados junto com o cookie da sessão. Toda essa sobrecarga, especialmente quando sob carga pesada, cria desperdício de recursos e sobrecarrega desnecessariamente o servidor.

Agora que você está mais familiarizado com o protótipo, é hora de executar o aplicativo em sua máquina.

Apoio ao CORS

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

{
  "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 executado em localhost:3000 faça solicitações para o aplicativo de função em execução em localhost:7071. A propriedade CORSCredentials informa ao aplicativo de função que aceite cookies de credenciais da solicitação.

O compartilhamento de recursos entre origens (CORS) é um recurso HTTP que permite que um aplicativo Web executado 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 impede que uma página da Web chame 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.

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