Analizar las limitaciones de una aplicación web basada en sondeo
La arquitectura actual de la aplicación notifica información sobre acciones mediante la recuperación de toda la información sobre el precio de las acciones desde el servidor en función de un temporizador. Este diseño se suele denominar diseño basado en sondeo.
Server
La información sobre el precio de las acciones se almacena en una base de datos de Azure Cosmos DB. Cuando se desencadena mediante una solicitud HTTP, la función getStocks
devuelve todas las filas de la base de datos.
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,
};
},
});
- Obtener datos: La primera sección de código, cosmosInput, obtiene todos los elementos de la tabla
stocks
, con la consultaSELECT * from c
, en la base de datosstocksdb
de Cosmos DB. - Devolver datos: La segunda sección del código, app.http, recibe esos datos en la función como entrada en
context.extraInputs
y, a continuación, los devuelve como el cuerpo de la respuesta al cliente.
Remoto
El cliente de ejemplo usa Vue.js para componer la interfaz de usuario y el cliente Fetch para controlar las solicitudes a la API.
La página HTML usa un temporizador para enviar una solicitud al servidor cada cinco segundos para solicitar existencias. La respuesta devuelve una matriz de acciones, que, luego, se muestran al usuario.
<!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();
}
});
Una vez que el método startPoll
comienza el sondeo, se llama al método update
cada cinco segundos. Dentro del método update
, se envía una solicitud GET al punto de conexión de la API de /api/getStocks
y el resultado se establece en app.stocks
, lo que actualiza la interfaz de usuario.
El código de cliente y servidor es relativamente sencillo: obtener todos los datos, mostrar todos los datos. Como encontramos en nuestro análisis, esta simplicidad aporta algunas limitaciones.
Análisis de la solución prototipo
Como ingeniero de Tailwind Traders, ha identificado algunas de las desventajas de este enfoque de sondeo basado en temporizador.
Solicitudes de API innecesarias: En el prototipo de sondeo basado en temporizador, la aplicación cliente se pone en contacto con el servidor tanto si hay cambios en los datos subyacentes como si no.
Actualizaciones de página innecesarias: Una vez que se devuelven datos desde el servidor, la lista completa de existencias se actualiza en la página web, incluso si no ha cambiado ningún dato. Este mecanismo de sondeo es una solución ineficaz.
Intervalos de sondeo: La selección del mejor intervalo de sondeo para el escenario también supone un reto. El sondeo obliga a elegir entre el costo de cada llamada al back-end y la rapidez con la que se quiere que la aplicación responda a los nuevos datos. Los retrasos a menudo existen entre el momento en que los nuevos datos están disponibles y el momento en que la aplicación los detecta. En la ilustración siguiente se muestra el problema.
En el peor de los casos, el retraso potencial para detectar datos nuevos es igual que el intervalo de sondeo. Así que, ¿por qué no usar un intervalo menor?
Cantidad de datos: A medida que la aplicación crece, la cantidad de datos intercambiada entre el cliente y el servidor se convierte en un problema. Cada encabezado de solicitud HTTP incluye cientos de bytes de datos junto con la cookie de la sesión. Toda esta sobrecarga, especialmente si hay una carga elevada, crea recursos desperdiciados y afecta innecesariamente al servidor.
Ahora que está más familiarizado con el prototipo, es el momento de ejecutar la aplicación en la máquina.
Compatibilidad de CORS
En el archivo local.settings.json de la aplicación de Functions, la sección Host
incluye la siguiente configuración.
{
"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
}
}
Esta configuración permite que una aplicación web que se ejecuta en localhost:3000 realice solicitudes a la aplicación de función que se ejecuta en localhost:7071. La propiedad CORSCredentials
indica a la aplicación de función que acepte las cookies de credencial de la solicitud.
El uso compartido de recursos entre orígenes (CORS) es una característica de HTTP que permite que una aplicación web que se ejecuta en un dominio acceda a recursos de otro dominio. Los exploradores web implementan una restricción de seguridad denominada directiva del mismo origen que impide que una página web llame a las API de otro dominio diferente; CORS proporciona una forma segura de permitir que un dominio (el dominio de origen) llame a las API de otro dominio.
Cuando se ejecuta localmente, CORS se configura automáticamente en el archivo local.settings.json del ejemplo, que nunca se publica. Al implementar la aplicación cliente (unidad 7), también debe actualizar la configuración de CORS en la aplicación de funciones para permitir el acceso desde la aplicación cliente.