Creación de una aplicación de notificación de precios de acciones utilizando React, Apollo GraphQL y Hasura

En este artículo, aprenderemos cómo crear una aplicación basada en eventos y enviar una notificación web cuando se activa un evento en particular. Configuraremos tablas de base de datos, eventos y activadores programados en el motor Hasura GraphQL y conectaremos el punto final GraphQL a la aplicación de front-end para registrar la preferencia de precio de las acciones del usuario.
El concepto de recibir una notificación cuando ha ocurrido el evento de su elección se ha vuelto popular en comparación con estar pegado al flujo continuo de datos para encontrar usted mismo ese suceso en particular. Las personas prefieren recibir correos electrónicos/mensajes relevantes cuando ha ocurrido su evento preferido en lugar de estar enganchados a la pantalla esperando a que suceda ese evento. La terminología basada en eventos también es bastante común en el mundo del software.
¿Qué maravilloso sería si pudieras obtener las actualizaciones del precio de tus acciones favoritas en tu teléfono?
En este artículo, vamos a crear una aplicación Notificador de precios de acciones utilizando los motores React, Apollo GraphQL y Hasura GraphQL. Comenzaremos el proyecto a partir de un create-react-app
código repetitivo y construiremos todo desde cero. Aprenderemos cómo configurar las tablas de la base de datos y los eventos en la consola Hasura. También aprenderemos cómo conectar los eventos de Hasura para obtener actualizaciones del precio de las acciones mediante notificaciones push web.
Aquí hay un vistazo rápido a lo que estaríamos construyendo:
¡Vámonos!
Una descripción general de lo que se trata este proyecto
Los datos de las acciones (incluidas métricas como máximo , mínimo , apertura , cierre , volumen ) se almacenarían en una base de datos de Postgres respaldada por Hasura. El usuario podrá suscribirse a una acción en particular en función de algún valor o puede optar por recibir notificaciones cada hora. El usuario recibirá una notificación web una vez que se cumplan sus criterios de suscripción.
Esto parece un montón de cosas y obviamente habrá algunas preguntas abiertas sobre cómo construiremos estas piezas.
Aquí hay un plan sobre cómo llevaríamos a cabo este proyecto en cuatro pasos:
- Obteniendo los datos de las acciones usando un script NodeJs
Comenzaremos obteniendo los datos de las acciones usando un script NodeJs simple de uno de los proveedores de API de acciones: Alpha Vantage . Este script obtendrá los datos de una acción en particular en intervalos de 5 minutos. La respuesta de la API incluye alto , bajo , apertura , cierre y volumen . Estos datos luego se insertarán en la base de datos de Postgres que está integrada con el back-end de Hasura. - Configuración del motor Hasura GraphQL
Luego configuraremos algunas tablas en la base de datos de Postgres para registrar puntos de datos. Hasura genera automáticamente los esquemas, consultas y mutaciones GraphQL para estas tablas. - Front-end usando React y Apollo Client
El siguiente paso es integrar la capa GraphQL usando el cliente Apollo y el proveedor Apollo (el punto final GraphQL proporcionado por Hasura). Los puntos de datos se mostrarán como gráficos en la interfaz. También crearemos las opciones de suscripción y activaremos las mutaciones correspondientes en la capa GraphQL. - Configuración de activadores de eventos/programados
Hasura proporciona una excelente herramienta para solucionar los activadores. Agregaremos activadores de eventos y programados en la tabla de datos de acciones. Estos activadores se establecerán si el usuario está interesado en recibir una notificación cuando los precios de las acciones alcancen un valor particular (activador de evento). El usuario también puede optar por recibir una notificación de una acción en particular cada hora (activación programada).
Ahora que el plan está listo, ¡pongámoslo en acción!
Aquí está el repositorio de GitHub para este proyecto. Si te pierdes en algún lugar del código siguiente, consulta este repositorio y vuelve a la velocidad.
Obteniendo los datos de acciones usando un script de NodeJs
¡Esto no es tan complicado como parece! Tendremos que escribir una función que obtenga datos utilizando el punto final Alpha Vantage y esta llamada de recuperación debe activarse en un intervalo de 5 minutos (lo adivinó bien, tendremos que colocar esta llamada a función setInterval
).
Si todavía te preguntas qué es Alpha Vantage y solo quieres sacarte esto de la cabeza antes de pasar a la parte de codificación, aquí lo tienes:
Alpha Vantage Inc. es un proveedor líder de API gratuitas para datos históricos y en tiempo real sobre acciones, divisas (FX) y criptomonedas/digitales.
Utilizaríamos este punto final para obtener las métricas requeridas de una acción en particular. Esta API espera una clave API como uno de los parámetros. Puede obtener su clave API gratuita desde aquí . Ahora estamos listos para pasar a la parte interesante: ¡comencemos a escribir código!
Instalación de dependencias
Cree un stocks-app
directorio y cree un server
directorio dentro de él. Inicialícelo como un proyecto de nodo usando npm init
y luego instale estas dependencias:
npm i isomorphic-fetch pg nodemon --save
Estas son las únicas tres dependencias que necesitaríamos para escribir este script para obtener los precios de las acciones y almacenarlos en la base de datos de Postgres.
Aquí hay una breve explicación de estas dependencias:
isomorphic-fetch
Facilita su usofetch
isomórfico (de la misma forma) tanto en el cliente como en el servidor.pg
Es un cliente PostgreSQL sin bloqueo para NodeJs.nodemon
Reinicia automáticamente el servidor ante cualquier cambio de archivo en el directorio.
Configurando la configuración
Agregue un config.js
archivo en el nivel raíz. Agregue el siguiente fragmento de código en ese archivo por ahora:
const config = { user: 'DATABASE_USER', password: 'DATABASE_PASSWORD', host: 'DATABASE_HOST', port: 'DATABASE_PORT', database: 'DATABASE_NAME', ssl: 'IS_SSL', apiHost: 'https://www.alphavantage.co/',};module.exports = config;
Los user
, password
, host
, port
, database
, ssl
están relacionados con la configuración de Postgres. ¡Volveremos para editar esto mientras configuramos la parte del motor Hasura!
Inicialización del grupo de conexiones de Postgres para consultar la base de datos
A connection pool
es un término común en informática y a menudo escuchará este término cuando trabaje con bases de datos.
Al consultar datos en bases de datos, primero deberá establecer una conexión con la base de datos. Esta conexión toma las credenciales de la base de datos y le brinda un enlace para consultar cualquiera de las tablas de la base de datos.
Nota : Establecer conexiones a bases de datos es costoso y también desperdicia importantes recursos. Un grupo de conexiones almacena en caché las conexiones de la base de datos y las reutiliza en consultas posteriores. Si todas las conexiones abiertas están en uso, se establece una nueva conexión y luego se agrega al grupo.
Ahora que está claro qué es el grupo de conexiones y para qué se utiliza, comencemos creando una instancia del pg
grupo de conexiones para esta aplicación:
Agregue pool.js
el archivo en el nivel raíz y cree una instancia de grupo como:
const { Pool } = require('pg');const config = require('./config');const pool = new Pool({ user: config.user, password: config.password, host: config.host, port: config.port, database: config.database, ssl: config.ssl,});module.exports = pool;
Las líneas de código anteriores crean una instancia Pool
con las opciones de configuración establecidas en el archivo de configuración. Todavía tenemos que completar el archivo de configuración, pero no habrá ningún cambio relacionado con las opciones de configuración.
Ahora hemos sentado las bases y estamos listos para comenzar a realizar algunas llamadas API al punto final Alpha Vantage.
¡Vayamos a lo interesante!
Obteniendo los datos de las acciones
En esta sección, obtendremos los datos bursátiles del punto final Alpha Vantage. Aquí está el index.js
archivo:
const fetch = require('isomorphic-fetch');const getConfig = require('./config');const { insertStocksData } = require('./queries');const symbols = [ 'NFLX', 'MSFT', 'AMZN', 'W', 'FB'];(function getStocksData () { const apiConfig = getConfig('apiHostOptions'); const { host, timeSeriesFunction, interval, key } = apiConfig; symbols.forEach((symbol) = { fetch(`${host}query/?function=${timeSeriesFunction}symbol=${symbol}interval=${interval}apikey=${key}`) .then((res) = res.json()) .then((data) = { const timeSeries = data['Time Series (5min)']; Object.keys(timeSeries).map((key) = { const dataPoint = timeSeries[key]; const payload = [ symbol, dataPoint['2. high'], dataPoint['3. low'], dataPoint['1. open'], dataPoint['4. close'], dataPoint['5. volume'], key, ]; insertStocksData(payload); }); }); })})()
A los efectos de este proyecto, consultaremos los precios solo de estas acciones: NFLX (Netflix), MSFT (Microsoft), AMZN (Amazon), W (Wayfair), FB (Facebook).
Consulte este archivo para conocer las opciones de configuración. ¡ La función IIFE getStocksData
no está haciendo mucho! Recorre estos símbolos y consulta el punto final Alpha Vantage ${host}query/?function=${timeSeriesFunction}symbol=${symbol}interval=${interval}apikey=${key}
para obtener las métricas de estas acciones.
La insertStocksData
función coloca estos puntos de datos en la base de datos de Postgres. Aquí está la insertStocksData
función:
const insertStocksData = async (payload) = { const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)'; pool.query(query, payload, (err, result) = { console.log('result here', err); });};
¡Eso es todo! Hemos obtenido puntos de datos de la acción de la API Alpha Vantage y hemos escrito una función para colocarlos en la base de datos de Postgres en la stock_data
tabla. ¡Solo falta una pieza para que todo esto funcione! Tenemos que completar los valores correctos en el archivo de configuración. Obtendremos estos valores después de configurar el motor Hasura. ¡Vayamos a eso ahora mismo!
Consulte el server
directorio para obtener el código completo sobre cómo obtener puntos de datos del punto final Alpha Vantage y completarlos en la base de datos de Hasura Postgres.
Si este enfoque de configurar conexiones, opciones de configuración e insertar datos usando la consulta sin formato parece un poco difícil, ¡no se preocupe por eso! ¡Aprenderemos cómo hacer todo esto de manera fácil con una mutación GraphQL una vez que el motor Hasura esté configurado!
Configuración del motor Hasura GraphQL
¡Es realmente sencillo configurar el motor Hasura y poner en funcionamiento los esquemas, consultas, mutaciones, suscripciones, activadores de eventos y mucho más de GraphQL!
Haga clic en Probar Hasura e ingrese el nombre del proyecto:
Estoy usando la base de datos de Postgres alojada en Heroku. Cree una base de datos en Heroku y vincúlela a este proyecto. Entonces debería estar todo listo para experimentar el poder de la consola Hasura rica en consultas.
Copie la URL de la base de datos de Postgres que obtendrá después de crear el proyecto. Tendremos que poner esto en el archivo de configuración.
Haga clic en Iniciar consola y será redirigido a esta vista:
Comencemos a construir el esquema de tabla que necesitaríamos para este proyecto.
Crear esquema de tablas en la base de datos Postgres
¡Vaya a la pestaña Datos y haga clic en Agregar tabla! Comencemos a crear algunas de las tablas:
symbol
mesa
Esta tabla se utilizaría para almacenar la información de los símbolos. Por ahora, he mantenido dos campos aquí: id
y company
. El campo id
es una clave principal y company
es de tipo varchar
. Agreguemos algunos de los símbolos en esta tabla:
symbol
mesa. ( Vista previa grande )
stock_data
mesa
La stock_data
tabla almacena id
, y symbol
las time
métricas como high
, low
, open
, close
, volume
. El script NodeJs que escribimos anteriormente en esta sección se utilizará para completar esta tabla en particular.
Así es como se ve la tabla:
stock_data
mesa. ( Vista previa grande )
¡Limpio! ¡Vayamos a la otra tabla del esquema de la base de datos!
user_subscription
mesa
La user_subscription
tabla almacena el objeto de suscripción con el ID de usuario. Este objeto de suscripción se utiliza para enviar notificaciones push web a los usuarios. Más adelante en el artículo aprenderemos cómo generar este objeto de suscripción.
Hay dos campos en esta tabla: id
la clave principal es de tipo uuid
y el campo de suscripción es de tipo jsonb
.
events
mesa
Este es el importante y se utiliza para almacenar las opciones de eventos de notificación. Cuando un usuario opta por recibir actualizaciones de precios de una acción en particular, almacenamos la información de ese evento en esta tabla. Esta tabla contiene estas columnas:
id
: es una clave principal con la propiedad de incremento automático.symbol
: es un campo de texto.user_id
: es de tipouuid
.trigger_type
: se utiliza para almacenar el tipo de activador de evento:time/event
.trigger_value
: se utiliza para almacenar el valor de activación. Por ejemplo, si un usuario ha optado por el activador de eventos basado en el precio: quiere actualizaciones si el precio de la acción ha alcanzado 1000, entonces seríatrigger_value
1000 ytrigger_type
seríaevent
.
Estas son todas las tablas que necesitaríamos para este proyecto. También tenemos que establecer relaciones entre estas tablas para tener un flujo de datos y conexiones fluidos. ¡Vamos a hacer eso!
Establecer relaciones entre tablas.
La events
tabla se utiliza para enviar notificaciones push web según el valor del evento. Por lo tanto, tiene sentido conectar esta tabla con la user_subscription
tabla para poder enviar notificaciones automáticas sobre las suscripciones almacenadas en esta tabla.
events.user_id → user_subscription.id
La stock_data
tabla está relacionada con la tabla de símbolos como:
stock_data.symbol → symbol.id
También tenemos que construir algunas relaciones sobre la symbol
mesa como:
stock_data.symbol → symbol.idevents.symbol → symbol.id
¡Ahora hemos creado las tablas requeridas y también hemos establecido las relaciones entre ellas! ¡Cambiemos a la GRAPHIQL
pestaña de la consola para ver la magia!
Hasura ya configuró las consultas GraphQL basadas en estas tablas:
Es simplemente sencillo consultar estas tablas y también puede aplicar cualquiera de estos filtros/propiedades ( ,,,, ) distinct_on
para obtener los datos deseados.limit
offset
order_by
where
Todo esto se ve bien, pero todavía no hemos conectado nuestro código del lado del servidor a la consola Hasura. ¡Completemos esa parte!
Conexión del script NodeJs a la base de datos Postgres
Coloque las opciones requeridas en el config.js
archivo en el server
directorio como:
const config = { databaseOptions: { user: 'DATABASE_USER', password: 'DATABASE_PASSWORD', host: 'DATABASE_HOST', port: 'DATABASE_PORT', database: 'DATABASE_NAME', ssl: true, }, apiHostOptions: { host: 'https://www.alphavantage.co/', key: 'API_KEY', timeSeriesFunction: 'TIME_SERIES_INTRADAY', interval: '5min' }, graphqlURL: 'GRAPHQL_URL'};const getConfig = (key) = { return config[key];};module.exports = getConfig;
Ingrese estas opciones de la cadena de la base de datos que se generó cuando creamos la base de datos de Postgres en Heroku.
Consiste en opciones relacionadas con la API , apiHostOptions
como host
, y .key
timeSeriesFunction
interval
Obtendrá el graphqlURL
campo en la pestaña GRAPHIQL en la consola Hasura.
La getConfig
función se utiliza para devolver el valor solicitado del objeto de configuración. Ya hemos usado esto en index.js
el server
directorio.
Es hora de ejecutar el servidor y completar algunos datos en la base de datos. Agregué un script como package.json
:
"scripts": { "start": "nodemon index.js"}
Ejecute npm start
en el terminal y los puntos de datos de la matriz de símbolos index.js
deben completarse en las tablas.
Refactorización de la consulta sin formato en el script de NodeJs para la mutación GraphQL
Ahora que el motor Hasura está configurado, veamos qué tan fácil puede ser indicar una mutación sobre la stock_data
mesa.
La función insertStocksData
en queries.js
utiliza una consulta sin formato:
const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)';
Refactoricemos esta consulta y usemos la mutación impulsada por el motor Hasura. Aquí está el refactorizado queries.js
en el directorio del servidor:
const { createApolloFetch } = require('apollo-fetch');const getConfig = require('./config');const GRAPHQL_URL = getConfig('graphqlURL');const fetch = createApolloFetch({ uri: GRAPHQL_URL,});const insertStocksData = async (payload) = { const insertStockMutation = await fetch({ query: `mutation insertStockData($objects: [stock_data_insert_input!]!) { insert_stock_data (objects: $objects) { returning { id } } }`, variables: { objects: payload, }, }); console.log('insertStockMutation', insertStockMutation);};module.exports = { insertStocksData}
Tenga en cuenta: tenemos que agregar graphqlURL
el config.js
archivo.
El apollo-fetch
módulo devuelve una función de recuperación que se puede utilizar para consultar/mutar la fecha en el punto final GraphQL. Bastante fácil, ¿verdad?
El único cambio que debemos hacer index.js
es devolver el objeto de acciones en el formato requerido por la insertStocksData
función. Consulte index2.js
y queries2.js
para obtener el código completo con este enfoque.
Ahora que hemos completado el lado de los datos del proyecto, pasemos al front-end y construyamos algunos componentes interesantes.
Nota : ¡ No tenemos que mantener las opciones de configuración de la base de datos con este enfoque!
Front-end usando React y Apollo Client
El proyecto front-end está en el mismo repositorio y se crea utilizando el create-react-app
paquete. El trabajador de servicio generado con este paquete admite el almacenamiento en caché de activos, pero no permite agregar más personalizaciones al archivo del trabajador de servicio. Ya hay algunos problemas abiertos para agregar soporte para opciones de trabajadores de servicios personalizados. Hay formas de solucionar este problema y agregar soporte para un trabajador de servicio personalizado.
Comencemos mirando la estructura del proyecto front-end:
¡Por favor consulte el src
directorio! No se preocupe por los archivos relacionados con el trabajador del servicio por ahora. Aprenderemos más sobre estos archivos más adelante en esta sección. El resto de la estructura del proyecto parece simple. La components
carpeta tendrá los componentes (Cargador, Gráfico); la services
carpeta contiene algunas de las funciones/servicios auxiliares utilizados para transformar objetos en la estructura requerida; styles
como sugiere el nombre, contiene los archivos sass utilizados para diseñar el proyecto; views
es el directorio principal y contiene los componentes de la capa de vista.
Necesitaríamos solo dos componentes de vista para este proyecto: la lista de símbolos y la serie temporal de símbolos. Construiremos la serie temporal utilizando el componente Gráfico de la biblioteca Highcharts. ¡Comencemos a agregar código en estos archivos para construir las piezas en el front-end!
Instalación de dependencias
Aquí está la lista de dependencias que necesitaremos:
apollo-boost
Apollo boost es una forma sin configuración de comenzar a usar Apollo Client. Viene incluido con las opciones de configuración predeterminadas.reactstrap
y Los componentes se crean utilizando estos dos paquetes.bootstrap
graphql
y es una dependencia requerida para usar y se usa para admitir el tipo de datos que se usa en el esquema GraphQL.graphql-type-json
graphql
apollo-boost
graphql-type-json
json
-
highcharts
y estos dos paquetes se utilizarán para crear el gráfico:highcharts-react-official
-
node-sass
Esto se agrega para admitir archivos sass para estilo. -
uuid
Este paquete se utiliza para generar valores aleatorios fuertes.
Todas estas dependencias tendrán sentido una vez que comencemos a usarlas en el proyecto. ¡Pasemos al siguiente paso!
Configurando el cliente Apollo
Crea un apolloClient.js
dentro de la src
carpeta como:
import ApolloClient from 'apollo-boost';const apolloClient = new ApolloClient({ uri: 'HASURA_CONSOLE_URL'});export default apolloClient;
El código anterior crea una instancia de ApolloClient y tiene en cuenta uri
las opciones de configuración. Es uri
la URL de su consola Hasura. Obtendrá este uri
campo en la GRAPHIQL
pestaña de la sección GraphQL Endpoint .
¡El código anterior parece simple pero se encarga de la parte principal del proyecto! Conecta el esquema GraphQL creado en Hasura con el proyecto actual.
También tenemos que pasar este objeto de cliente Apollo ApolloProvider
y envolver el componente raíz en su interior ApolloProvider
. Esto permitirá que todos los componentes anidados dentro del componente principal utilicen client
consultas de prop y activen en este objeto de cliente.
Modifiquemos el index.js
archivo como:
const Wrapper = () = {/* some service worker logic - ignore for now */ const [insertSubscription] = useMutation(subscriptionMutation); useEffect(() = { serviceWorker.register(insertSubscription); }, []) /* ignore the above snippet */ return App /;}ReactDOM.render( ApolloProvider client={apolloClient} Wrapper / /ApolloProvider, document.getElementById('root'));
Ignore el insertSubscription
código relacionado. Lo entenderemos en detalle más adelante. El resto del código debería ser fácil de entender. La render
función toma el componente raíz y el elementId como parámetros. El aviso client
(instancia de ApolloClient) se pasa como accesorio a ApolloProvider
. Puedes consultar el index.js
expediente completo aquí .
Configurar el trabajador de servicio personalizado
Un trabajador de servicio es un archivo JavaScript que tiene la capacidad de interceptar solicitudes de red. Se utiliza para consultar el caché y verificar si el activo solicitado ya está presente en el caché en lugar de viajar al servidor. Los trabajadores de servicios también se utilizan para enviar notificaciones push web a los dispositivos suscritos.
Tenemos que enviar notificaciones push web sobre las actualizaciones del precio de las acciones a los usuarios suscritos. ¡Sentamos las bases y construyamos este archivo de trabajador de servicio!
El insertSubscription
recorte relacionado en el index.js
archivo hace el trabajo de registrar el trabajador del servicio y colocar el objeto de suscripción en la base de datos usando subscriptionMutation
.
Consulte queries.js para conocer todas las consultas y mutaciones que se utilizan en el proyecto.
serviceWorker.register(insertSubscription);
invoca la register
función escrita en el serviceWorker.js
archivo. Aquí lo tienes:
export const register = (insertSubscription) = { if ('serviceWorker' in navigator) { const swUrl = `${process.env.PUBLIC_URL}/serviceWorker.js` navigator.serviceWorker.register(swUrl) .then(() = { console.log('Service Worker registered'); return navigator.serviceWorker.ready; }) .then((serviceWorkerRegistration) = { getSubscription(serviceWorkerRegistration, insertSubscription); Notification.requestPermission(); }) }}
La función anterior primero verifica si serviceWorker
es compatible con el navegador y luego registra el archivo del trabajador del servicio alojado en la URL swUrl
. ¡Revisaremos este archivo en un momento!
La getSubscription
función hace el trabajo de obtener el objeto de suscripción utilizando el subscribe
método del pushManager
objeto. Luego, este objeto de suscripción se almacena en la user_subscription
tabla con un ID de usuario. Tenga en cuenta que el ID de usuario se genera mediante la uuid
función. Veamos la getSubscription
función:
const getSubscription = (serviceWorkerRegistration, insertSubscription) = { serviceWorkerRegistration.pushManager.getSubscription() .then ((subscription) = { const userId = uuidv4(); if (!subscription) { const applicationServerKey = urlB64ToUint8Array('APPLICATION_SERVER_KEY') serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }).then (subscription = { insertSubscription({ variables: { userId, subscription } }); localStorage.setItem('serviceWorkerRegistration', JSON.stringify({ userId, subscription })); }) } })}
¡ Puedes consultar serviceWorker.js
el archivo para ver el código completo!
Notification.requestPermission()
invocó esta ventana emergente que solicita al usuario permiso para enviar notificaciones. Una vez que el usuario hace clic en Permitir, el servicio push genera un objeto de suscripción. Estamos almacenando ese objeto en localStorage como:
El campo endpoint
en el objeto anterior se usa para identificar el dispositivo y el servidor usa este punto final para enviar notificaciones push web al usuario.
Hemos realizado el trabajo de inicializar y registrar el trabajador del servicio. ¡También tenemos el objeto de suscripción del usuario! Esto funciona bien gracias al serviceWo
Deja un comentario