Analisar as limitações de uma aplicação Web baseada em consulta

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. Este design é frequentemente designado de design com base em consultas.

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 stocks tabela, com a consulta SELECT * from c, no stocksdb banco de dados no Cosmos DB.
  • Dados de retorno: A segunda seção do código, app.http, recebe esses dados na função como uma entrada e, em context.extraInputs seguida, os retorna como o corpo de 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 devolve uma matriz de ações que, em seguida, são apresentadas ao 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 começa a startPoll sondagem, o método é chamado a update cada cinco segundos. Dentro do update método, uma solicitação GET é enviada para o /api/getStocks ponto de extremidade da API e o resultado é definido como app.stocks, que atualiza a interface do usuário.

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 ações é atualizada na página da Web, mesmo que nenhum dado tenha sido alterado. Este mecanismo de consulta é uma solução ineficaz.

  • Intervalos de sondagem: selecionar o melhor intervalo de sondagem para o seu cenário também é um desafio. A consulta força-o a optar entre o valor de cada chamada de back-end e a rapidez com que pretende que a sua aplicação 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 seguinte ilustração 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 possível atraso para a deteção de novos dados é igual ao intervalo da consulta. Então, porque é que não se utiliza 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 pedido HTTP inclui centenas de bytes de dados, juntamente com o cookie da sessão. Esta sobrecarga, especialmente sob muita carga, cria o desperdício de recursos e esgota desnecessariamente o servidor.

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

Suporte para CORS

No arquivo local.settings.json do aplicativo Functions, a Host seção 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 em execução em localhost:3000 faça solicitações para o aplicativo de função em execução em localhost:7071. A propriedade CORSCredentials informa o aplicativo de função para aceitar cookies de credenciais da solicitação.

O CORS (partilha de recursos de várias origens) é uma funcionalidade HTTP que permite a execução de uma aplicação Web num domínio para aceder a recursos noutro domínio. Os browsers implementam uma restrição de segurança conhecida como política de origem idêntica, que impede uma página Web de chamar APIs num domínio diferente. O CORS é uma forma segura de permitir que um domínio (o domínio de origem) chame APIs de outro domínio.

Quando executado localmente, o CORS é configurado para você no arquivo 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.