React, Apollo GraphQL ve Hasura Kullanarak Hisse Senedi Fiyat Bildirimi Uygulaması Oluşturma
Yayınlanan: 2022-03-10Seçtiğiniz olayı gerçekleştiğinde haberdar olma konsepti, belirli olayları kendiniz bulmak için sürekli veri akışına yapıştırılmaya kıyasla popüler hale gelmiştir. İnsanlar, o olayın olmasını beklemek için ekrana takılıp kalmak yerine, tercih ettikleri olay gerçekleştiğinde ilgili e-postaları/mesajları almayı tercih ederler. Olay tabanlı terminoloji yazılım dünyasında da oldukça yaygındır.
En sevdiğiniz hisse senedinin fiyat güncellemelerini telefonunuzda alabilseydiniz, bu ne kadar harika olurdu?
Bu yazımızda React, Apollo GraphQL ve Hasura GraphQL motorunu kullanarak bir Stocks Price Notifier uygulaması oluşturacağız. Projeye bir create-react-app
ortak kodundan başlayacağız ve her şeyi temelden oluşturacağız. Hasura konsolunda veritabanı tablolarını ve olaylarını nasıl kuracağımızı öğreneceğiz. Ayrıca, web-push bildirimlerini kullanarak hisse senedi fiyat güncellemelerini almak için Hasura'nın olaylarını nasıl bağlayacağımızı da öğreneceğiz.
İşte ne inşa edeceğimize hızlı bir bakış:
Haydi gidelim!
Bu Projenin Ne Hakkında Olduğuna Genel Bir Bakış
Hisse senedi verileri ( yüksek , düşük , açık , kapalı , hacim gibi metrikler dahil) Hasura destekli bir Postgres veritabanında saklanacaktır. Kullanıcı, belirli bir değere dayalı olarak belirli bir hisse senedine abone olabilir veya her saat başı bildirim almayı tercih edebilir. Kullanıcı, abonelik kriterleri karşılandığında bir web-push bildirimi alacaktır.
Bu çok fazla şeye benziyor ve açıkçası bu parçaları nasıl oluşturacağımıza dair bazı açık sorular olacak.
İşte bu projeyi dört adımda nasıl gerçekleştireceğimize dair bir plan:
- NodeJs betiği kullanarak hisse senedi verilerini getirme
Hisse senedi API'si sağlayıcılarından biri olan Alpha Vantage'dan basit bir NodeJs betiği kullanarak hisse senedi verilerini getirerek başlayacağız. Bu komut dosyası, belirli bir hisse senedi için verileri 5 dakikalık aralıklarla getirecektir. API'nin yanıtı yüksek , düşük , açık , kapat ve hacim içerir . Bu veriler daha sonra Hasura arka ucu ile entegre olan Postgres veritabanına eklenecektir. - Hasura Graphql motorunu ayarlama
Daha sonra veri noktalarını kaydetmek için Postgres veritabanında bazı tablolar ayarlayacağız. Hasura, bu tablolar için GraphQL şemalarını, sorgularını ve mutasyonlarını otomatik olarak oluşturur. - React ve Apollo Client kullanan ön uç
Sonraki adım, Apollo istemcisini ve Apollo Sağlayıcısını (Hasura tarafından sağlanan GraphQL uç noktası) kullanarak GraphQL katmanını entegre etmektir. Veri noktaları ön uçta çizelgeler olarak gösterilecektir. Ayrıca abonelik seçeneklerini oluşturacağız ve GraphQL katmanında karşılık gelen mutasyonları tetikleyeceğiz. - Olay / Planlanmış Tetikleyiciler Ayarlama
Hasura, tetikleyiciler etrafında mükemmel bir araç sağlar. Hisse senedi veri tablosuna etkinlik ve planlanmış tetikleyiciler ekleyeceğiz. Bu tetikleyiciler, kullanıcı, hisse senedi fiyatları belirli bir değere ulaştığında (olay tetikleyici) bir bildirim almak istiyorsa ayarlanacaktır. Kullanıcı ayrıca her saat belirli bir hisse senedi bildirimi almayı da seçebilir (planlanmış tetikleyici).
Artık plan hazır olduğuna göre harekete geçelim!
İşte bu proje için GitHub deposu. Aşağıdaki kodun herhangi bir yerinde kaybolursanız, bu depoya bakın ve hızınıza geri dönün!
NodeJs Komut Dosyası Kullanarak Hisse Senedi Verilerini Alma
Bu göründüğü kadar karmaşık değil! Alpha Vantage Endpoint kullanarak verileri içeren bir işlev yazmanız gerekecek ve bu getirme çağrısı 5 dakika aralığında ateşlenmeli (doğru tahmin ettiniz, bu fonksiyon çağrısını setInterval
).
Hala Alpha Vantage'ın ne olduğunu merak ediyorsanız ve kodlama kısmına geçmeden önce bunu kafanızdan atmak istiyorsanız, işte burada:
Alpha Vantage Inc. hisse senetleri, forex (FX) ve dijital/kripto para birimleri hakkında gerçek zamanlı ve geçmiş veriler için lider bir ücretsiz API sağlayıcısıdır.
Belirli bir hisse senedinin gerekli ölçümlerini almak için bu uç noktayı kullanıyor olacağız. Bu API, parametrelerden biri olarak bir API anahtarı bekler. Ücretsiz API anahtarınızı buradan alabilirsiniz. Şimdi ilginç kısma geçmekte fayda var - hadi biraz kod yazmaya başlayalım!
Bağımlılıkları Yükleme
Bir stocks-app
dizini oluşturun ve bunun içinde bir server
dizini oluşturun. Bunu npm init
kullanarak bir düğüm projesi olarak başlatın ve ardından şu bağımlılıkları kurun:
npm i isomorphic-fetch pg nodemon --save
Bunlar, hisse senedi fiyatlarını almak ve bunları Postgres veritabanında saklamak için bu komut dosyasını yazmamız gereken üç bağımlılık.
İşte bu bağımlılıkların kısa bir açıklaması:
-
isomorphic-fetch
Hem istemcide hem de sunucuda eşbiçimli (aynı biçimde)fetch
kullanmayı kolaylaştırır. -
pg
NodeJ'ler için engelleyici olmayan bir PostgreSQL istemcisidir. -
nodemon
Dizindeki herhangi bir dosya değişikliğinde sunucuyu otomatik olarak yeniden başlatır.
Yapılandırmayı ayarlama
Kök düzeyinde bir config.js
dosyası ekleyin. Şimdilik aşağıdaki kod parçasını bu dosyaya ekleyin:
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;
user
, password
, host
, port
, database
, ssl
Postgres yapılandırmasıyla ilgilidir. Hasura motor bölümünü kurarken bunu düzenlemek için geri geleceğiz!
Veritabanını Sorgulamak İçin Postgres Bağlantı Havuzunu Başlatma
connection pool
, bilgisayar bilimlerinde yaygın olarak kullanılan bir terimdir ve bu terimi veritabanlarıyla uğraşırken sıklıkla duyarsınız.
Veritabanlarında veri sorgularken öncelikle veri tabanı ile bağlantı kurmanız gerekir. Bu bağlantı, veritabanı kimlik bilgilerini alır ve size veritabanındaki tablolardan herhangi birini sorgulamak için bir kanca verir.
Not : Veritabanı bağlantıları kurmak maliyetlidir ve ayrıca önemli kaynakları boşa harcar. Bir bağlantı havuzu, veritabanı bağlantılarını önbelleğe alır ve bunları sonraki sorgularda yeniden kullanır. Tüm açık bağlantılar kullanımdaysa, yeni bir bağlantı kurulur ve ardından havuza eklenir.
Artık bağlantı havuzunun ne olduğu ve ne için kullanıldığı açık olduğuna göre, bu uygulama için pg
bağlantı havuzunun bir örneğini oluşturarak başlayalım:
pool.js
dosyasını kök düzeyinde ekleyin ve şu şekilde bir havuz örneği oluşturun:
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;
Yukarıdaki kod satırları, yapılandırma dosyasında ayarlandığı gibi yapılandırma seçenekleriyle bir Pool
örneği oluşturur. Henüz yapılandırma dosyasını tamamlamadık, ancak yapılandırma seçenekleriyle ilgili herhangi bir değişiklik olmayacak.
Şimdi temelleri attık ve Alpha Vantage uç noktasına bazı API çağrıları yapmaya hazırız.
İlginç bir bite girelim!
Hisse Senedi Verilerini Alma
Bu bölümde, Alpha Vantage uç noktasından hisse senedi verilerini getireceğiz. İşte index.js
dosyası:
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); }); }); }) })()
Bu projenin amacı doğrultusunda, yalnızca bu hisse senetleri için fiyatları sorgulayacağız - NFLX (Netflix), MSFT (Microsoft), AMZN (Amazon), W (Wayfair), FB (Facebook).
Yapılandırma seçenekleri için bu dosyaya bakın. IIFE getStocksData
işlevi pek bir şey yapmıyor! Bu sembollerle döngüler ve alfa vantage son noktayı sorgular ${host}query/?function=${timeSeriesFunction}&symbol=${symbol}&interval=${interval}&apikey=${key}
Bu stoklar için metrikleri almak için.
insertStocksData
işlevi, bu veri noktalarını Postgres veritabanına yerleştirir. İşte insertStocksData
işlevi:
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); }); };
Budur! Alpha Vantage API'sinden hissenin veri noktalarını aldık ve bunları stock_data
tablosundaki Postgres veritabanına koyacak bir fonksiyon yazdık. Tüm bu işleri yapmak için sadece bir eksik parça var! Yapılandırma dosyasında doğru değerleri doldurmamız gerekiyor. Hasura motorunu kurduktan sonra bu değerleri alacağız. Hadi doğru gidelim!
Alpha Vantage uç noktasından veri noktaları alma ve bunu Hasura Postgres veri tabanına yerleştirme ile ilgili kodun tamamı için lütfen server
dizinine bakın.
Bu bağlantı kurma, yapılandırma seçenekleri ve ham sorguyu kullanarak veri ekleme yaklaşımı biraz zor görünüyorsa, lütfen bunun için endişelenmeyin! Hasura motoru kurulduktan sonra tüm bunları GraphQL mutasyonuyla kolay yoldan nasıl yapacağımızı öğreneceğiz!
Hasura GraphQL Motorunu Ayarlama
Hasura motorunu kurmak ve GraphQL şemaları, sorguları, mutasyonları, abonelikleri, olay tetikleyicileri ve çok daha fazlasıyla çalışmaya başlamak gerçekten çok basit!
Hasura'yı Dene'ye tıklayın ve proje adını girin:
Heroku'da barındırılan Postgres veritabanını kullanıyorum. Heroku'da bir veritabanı oluşturun ve onu bu projeye bağlayın. Ardından, sorgu açısından zengin Hasura konsolunun gücünü deneyimlemeye hazır olmalısınız.
Lütfen projeyi oluşturduktan sonra alacağınız postgres db URL'sini kopyalayın. Bunu yapılandırma dosyasına koymamız gerekecek.
Konsolu Başlat'a tıklayın ve bu görünüme yönlendirileceksiniz:
Bu proje için ihtiyacımız olan tablo şemasını oluşturmaya başlayalım.
Postgres Veritabanında Tablo Şeması Oluşturma
Lütfen veri sekmesine gidin ve tablo ekle'ye tıklayın! Tablolardan bazılarını oluşturmaya başlayalım:
symbol
tablosu
Bu tablo, sembollerin bilgilerini saklamak için kullanılacaktır. Şimdilik, burada iki alan tuttum - id
ve company
. Alan id
birincil anahtardır ve company
türünde varchar
. Bu tablodaki bazı sembolleri ekleyelim:
stock_data
masa
stock_data
tablosu id
, symbol
, time
ve high
, low
, open
, close
, volume
gibi metrikleri saklar . Bu bölümde daha önce yazdığımız NodeJs betiği, bu belirli tabloyu doldurmak için kullanılacaktır.
Tablonun nasıl göründüğü aşağıda açıklanmıştır:
Düzenli! Gelelim veritabanı şemasındaki diğer tabloya!
user_subscription
tablosu
user_subscription
tablosu, abonelik nesnesini kullanıcı kimliğine karşı saklar. Bu abonelik nesnesi, kullanıcılara web-push bildirimleri göndermek için kullanılır. Bu abonelik nesnesinin nasıl üretileceğini makalede daha sonra öğreneceğiz.
Bu tabloda iki alan vardır - id
uuid
türünde birincil anahtardır ve abonelik alanı jsonb
.
events
tablosu
Bu önemlidir ve bildirim olay seçeneklerini saklamak için kullanılır. Bir kullanıcı belirli bir hisse senedinin fiyat güncellemelerini tercih ettiğinde, bu olay bilgilerini bu tabloda saklarız. Bu tablo şu sütunları içerir:
-
id
: Otomatik Artırma özelliğine sahip bir birincil anahtardır. -
symbol
: bir metin alanıdır. -
user_id
:uuid
. -
trigger_type
: olay tetikleme tipini depolamak için kullanılır -time/event
. -
trigger_value
: tetikleyici değerini depolamak için kullanılır. Örneğin, bir kullanıcı fiyata dayalı olay tetikleyiciyi seçtiyse — hisse senedinin fiyatı 1000'e ulaştığında güncellemeler istiyorsa,trigger_value
1000 olur vetrigger_type
event
olur.
Bunlar, bu proje için ihtiyacımız olan tüm tablolar. Ayrıca sorunsuz bir veri akışı ve bağlantılara sahip olmak için bu tablolar arasında ilişkiler kurmamız gerekiyor. Hadi bunu yapalım!
Tablolar arasında ilişki kurma
events
tablosu, olay değerine göre web-push bildirimleri göndermek için kullanılır. Böylece, bu tabloyu, bu tabloda depolanan aboneliklere push bildirimleri gönderebilmek için bu tabloyu user_subscription
bağlamak mantıklıdır.
events.user_id → user_subscription.id
stock_data
tablosu, semboller tablosu ile ilgilidir:
stock_data.symbol → symbol.id
Ayrıca symbol
tablosunda aşağıdaki gibi bazı ilişkiler kurmamız gerekiyor:
stock_data.symbol → symbol.id events.symbol → symbol.id
Şimdi gerekli masaları oluşturduk ve ayrıca onların arasındaki ilişkileri kurduk! Sihri görmek için konsoldaki GRAPHIQL
sekmesine geçelim!
Hasura, şu tablolara dayalı olarak GraphQL sorgularını zaten kurmuştur:
Bu tablolarda sorgulamak açıkça basittir ve istenen verileri elde etmek için bu filtrelerin / özelliklerden ( distinct_on
, limit
, offset
, order_by
, where
) uygulamasını da uygulayabilirsiniz.
Bunların hepsi iyi görünüyor ama hala sunucu tarafı kodumuzu Hasura konsoluna bağlamadık. Hadi o kısmı tamamlayalım!
NodeJs Komut Dosyasını Postgres Veritabanına Bağlama
Lütfen gerekli seçenekleri server
dizinindeki config.js
dosyasına şu şekilde yerleştirin:
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;
Lütfen bu seçenekleri Heroku'da Postgres veritabanını oluşturduğumuzda oluşturulan veritabanı dizesinden koyun.
apiHostOptions
, host
, key
, timeSeriesFunction
ve interval
gibi API ile ilgili seçeneklerden oluşur.
graphqlURL
alanını Hasura konsolundaki GRAPHIQL sekmesinde alırsınız.
getConfig
işlevi, istenen değeri CONFIG nesnesinden iade etmek için kullanılır. Bunu zaten server
dizinindeki index.js
kullandık.
Sunucuyu çalıştırmanın ve veritabanındaki bazı verileri doldurmanın zamanı geldi. package.json
şu şekilde bir komut dosyası ekledim:
"scripts": { "start": "nodemon index.js" }
Terminalde npm start
ve index.js
içindeki semboller dizisinin veri noktaları tablolarda doldurulmalıdır.
NodeJs Komut Dosyasındaki Ham Sorguyu GraphQL Mutasyonuna Yeniden Düzenleme
Şimdi Hasura motoru kurulduğuna göre, stock_data
tablosunda bir mutasyon çağırmanın ne kadar kolay olabileceğini görelim.
queries.js
içindeki insertStocksData
işlevi ham bir sorgu kullanır:
const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)';
Hadi bu sorguyu değiştirelim ve Hasura Motoru tarafından desteklenen mutasyonu kullanalım. İşte sunucu dizininde refactored queries.js
:
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 }
Lütfen Dikkat: graphqlURL
dosyasına config.js
eklemeliyiz.
apollo-fetch
modülü, GraphQL uç noktasındaki tarihi sorgulamak/değiştirmek için kullanılabilecek bir getirme işlevi döndürür. Yeterince kolay, değil mi?
insertStocksData
index.js
gerektirdiği şekilde biçiminde iade etmektir. Lütfen bu yaklaşımla birlikte kodun tamamı için index2.js
ve queries2.js
kontrol edin.
Şimdi projenin veri tarafını gerçekleştirdik, hadi ön uç biti üzerine hareket edelim ve bazı ilginç bileşenler oluşturalım!
Not : Bu yaklaşımla veritabanı yapılandırma seçeneklerini saklamak zorunda değiliz!
React ve Apollo İstemcisini Kullanan Ön Uç
Ön uç projesi aynı depodadır ve create-react-app
paketi kullanılarak oluşturulur. Bu paket kullanılarak üretilen servis işçisi, önbellekleme varlıklarını destekler ancak daha fazla özelleştirmenin servis çalışanı dosyasına eklenmesine izin vermez. Özel servis çalışanı seçeneklerine destek eklemek için zaten açık konular var. Bu problemden kurtulmanın yolları var ve özel bir servis çalışanı için destek ekleyin.
Ön uç projesinin yapısına bakarak başlayalım:
Lütfen src
dizinini kontrol edin! Servis çalışanı ile ilgili dosyalar için şimdilik endişelenmeyin. Bu bölümde daha sonra bu dosyalar hakkında daha fazla bilgi edineceğiz. Proje yapısının geri kalanı basit görünüyor. components
klasörü bileşenleri içerecektir (Yükleyici, Grafik); services
klasörü, gerekli yapıdaki nesneleri dönüştürmek için kullanılan bazı yardımcı işlevleri/hizmetleri içerir; adından da anlaşılacağı gibi styles
, projeyi şekillendirmek için kullanılan sass dosyalarını içerir; views
ana dizindir ve görünüm katmanı bileşenlerini içerir.
Bu proje için sadece iki görünüm bileşenine ihtiyacımız var - Sembol Listesi ve Sembol Zaman Serisi. Highcharts kitaplığındaki Chart bileşenini kullanarak zaman serisini oluşturacağız. Ön uçtaki parçaları oluşturmak için bu dosyalara kod eklemeye başlayalım!
Bağımlılıkları Yükleme
İşte ihtiyacımız olan bağımlılıkların listesi:
-
apollo-boost
Apollo boost, Apollo İstemcisini kullanmaya başlamanın sıfır yapılandırmalı bir yoludur. Varsayılan yapılandırma seçenekleri ile birlikte gelir. -
reactstrap
vebootstrap
Bileşenler bu iki paket kullanılarak oluşturulur. -
graphql
vegraphql-type-json
graphql
, WRAPHQL şemasında kullanılanjson
veri türünü desteklemek içinapollo-boost
vegraphql-type-json
kullanmak için gerekli bir bağımlılıktır. highcharts
vehighcharts-react-official
Ve bu iki paket, grafiği oluşturmak için kullanılacaktır:node-sass
Bu, stil için sass dosyalarını desteklemek için eklenmiştir.uuid
Bu paket, güçlü rastgele değerler üretmek için kullanılır.
Tüm bu bağımlılıklar, onları projede kullanmaya başladığımızda anlam kazanacaktır. Bir sonraki kısma geçelim!
Apollo İstemcisini Ayarlama
src
klasörünün içindeki bir apolloClient.js
oluşturun:
import ApolloClient from 'apollo-boost'; const apolloClient = new ApolloClient({ uri: '<HASURA_CONSOLE_URL>' }); export default apolloClient;
Yukarıdaki kod, ApolloClient'i başlatır ve yapılandırma seçeneklerinde uri
alır. uri
, Hasura konsolunuzun URL'sidir. Bu URI alanını uri
Endpoint bölümündeki GRAPHIQL
sekmesinde alacaksınız.
Yukarıdaki kod basit görünüyor ama projenin ana kısmına bakıyor! HASURE üzerine inşa edilen GRAPHQL şemasını mevcut proje ile bağlar.
Ayrıca bu apollo istemci nesnesini ApolloProvider
ve kök bileşeni ApolloProvider
içine sarmalıyız. Bu, ana bileşenin içindeki tüm iç içe bileşenlerin bu istemci nesnesinde client
desteği ve yangın sorgularını kullanmasını sağlayacaktır.
index.js
dosyasını şu şekilde değiştirelim:
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') );
Lütfen insertSubscription
ile ilgili kodu yok sayınız. Bunu daha sonra ayrıntılı olarak anlayacağız. Kodun geri kalanı dolaşmak için basit olmalıdır. render
işlevi, kök bileşenini ve ElementID'i parametre olarak alır. Bildirim client
(ApolloClient örneği), bir destek olarak ApolloProvider
. index.js
dosyasının tamamını buradan kontrol edebilirsiniz.
Özel Hizmet Çalışanını Ayarlama
Bir servis işçisi, ağ isteklerini engelleme yeteneğine sahip bir javascript dosyasıdır. Önbelleğin sorunu sorgulamak için, istenen varlığın sunucuya bir yolculuk yapmak yerine önbelleğe daha önce mevcut olup olmadığını kontrol etmek için kullanılır. Hizmet çalışanları, abone olunan cihazlara web-push bildirimleri göndermek için de kullanılır.
Hisse senedi fiyat güncellemeleri için abone olan kullanıcılara web-push bildirimleri göndermeliyiz. Zemini ayarlayalım ve bu servis çalışanı dosyasını oluşturalım!
insertSubscription
dosyasında tutturulmuş ekli iskelet, servis işçisini kaydetme ve abonelik nesnesini subscriptionMutation
index.js
kullanarak veritabanına koyma çalışmasını yapıyor.
Lütfen projede kullanılan tüm sorgular ve mutasyonlar için querlies.js bakın.
serviceWorker.register(insertSubscription);
serviceWorker.js
dosyasında yazılan register
işlevini çağırır. İşte burada:
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(); }) } }
Yukarıdaki işlev, önce servisçinin tarayıcı tarafından desteklenip desteklenmediğini kontrol eder ve ardından URL serviceWorker
barındırılan servis çalışanı dosyasını swUrl
. Bu dosyayı birazdan kontrol edeceğiz!
getSubscription
işlevi, Abonelik Nesnesini subscribe
pushManager
nesnesindeki Abone Oluşturma'yı kullanma çalışmasını yapar. Bu abonelik nesnesi daha sonra user_subscription
tablosunda bir kullanıcı kimliğine karşı saklanır. Lütfen uuid
işlevi kullanılarak oluşturulduğunu unutmayın. getSubscription
fonksiyonunu kontrol edelim:
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 })); }) } }) }
Kodun tamamı için serviceWorker.js
dosyasını kontrol edebilirsiniz!
Notification.requestPermission()
, kullanıcıdan bildirim göndermek için izin isteyen bu açılır pencereyi çağırdı. Kullanıcı izin verdikten sonra, bir abonelik nesnesi itme hizmeti tarafından oluşturulur. Bu nesneyi yerel gün içinde saklıyoruz:
Yukarıdaki nesnedeki alan endpoint
, cihazı tanımlamak için kullanılır ve sunucu, kullanıcı için Web Push bildirimleri göndermek için bu son noktayı kullanır.
Servis çalışanını başlatma ve kaydetme işini yaptık. Ayrıca kullanıcının abonelik nesnesine sahibiz! Bu, public
klasörde bulunan serviceWorker.js
dosyası nedeniyle bu kadar iyi çalışıyor. Şimdi işyerinde işleri hazırlamak için hazırlayalım!
Bu biraz zor bir konu ama doğru anlayalım! Daha önce belirtildiği gibi, create-react-app
yardımcı programı hizmet çalışanı için varsayılan olarak özelleştirmeleri desteklemez. workbox-build
modülünü kullanarak müşteri hizmetleri çalışanı uygulamasını gerçekleştirebiliriz.
Ayrıca, önbelleğe alma dosyalarının varsayılan davranışının sağlam olduğundan emin olmalıyız. Servis çalışanının projede inşa edildiği kısmı değiştireceğiz. Ve workbox-build tam olarak bunu başarmaya yardımcı olur! Düzgün şeyler! Bunu basit tutalım ve özel hizmet çalışanının çalışması için yapmamız gereken her şeyi listeleyelim:
-
workboxBuild
kullanarak varlıkların ön önbelleğe alınmasını gerçekleştirin. - Varlıkları önbelleğe almak için bir hizmet çalışanı şablonu oluşturun.
- Özel yapılandırma seçenekleri sağlamak için
sw-precache-config.js
dosyası oluşturun. - derleme hizmeti çalışanı komut dosyasını
package.json
içindeki derleme adımına ekleyin.
Bütün bunlar kafa karıştırıcı geliyorsa endişelenmeyin! Makale, bu noktaların her birinin arkasındaki anlambilimi açıklamaya odaklanmıyor. Şimdilik uygulama bölümüne odaklanmalıyız! Özel bir hizmet çalışanı yapmak için tüm çalışmaları yapmanın ardındaki mantığı başka bir makalede ele almaya çalışacağım.
src
dizininde sw-build.js
ve sw-custom.js
olmak üzere iki dosya oluşturalım. Lütfen bu dosyalara bağlantılara bakın ve kodu projenize ekleyin.
Şimdi sw-precache-config.js
dosyasını root seviyesinde oluşturalım ve o dosyaya aşağıdaki kodu ekleyelim:
module.exports = { staticFileGlobs: [ 'build/static/css/**.css', 'build/static/js/**.js', 'build/index.html' ], swFilePath: './build/serviceWorker.js', stripPrefix: 'build/', handleFetch: false, runtimeCaching: [{ urlPattern: /this\\.is\\.a\\.regex/, handler: 'networkFirst' }] }
Özel hizmet çalışanı dosyasını oluşturmaya yer açmak için package.json
dosyasını da değiştirelim:
Bu ifadeleri scripts
bölümüne ekleyin:
"build-sw": "node ./src/sw-build.js", "clean-cra-sw": "rm -f build/precache-manifest.*.js && rm -f build/service-worker.js",
Ve build
komut dosyasını şu şekilde değiştirin:
"build": "react-scripts build && npm run build-sw && npm run clean-cra-sw",
Kurulum sonunda tamamlandı! Şimdi public
klasörün içine özel bir hizmet çalışanı dosyası eklememiz gerekiyor:
function showNotification (event) { const eventData = event.data.json(); const { title, body } = eventData self.registration.showNotification(title, { body }); } self.addEventListener('push', (event) => { event.waitUntil(showNotification(event)); })
Sunucu tarafından gönderilen itme bildirimlerini dinlemek için bir push
dinleyicisi ekledik. showNotification
işlevi, Web Push bildirimlerini kullanıcıya görüntülemek için kullanılır.
Budur! Web push bildirimlerini işlemek için özel bir hizmet çalışanı ayarlamanın tüm zor işini bitirdik. Kullanıcı arayüzlerini oluşturduğumuzda bu bildirimleri eylemde göreceğiz!
Ana kod parçalarını inşa etmek için yaklaşıyoruz. Şimdi ilk görünümle başlayalım!
Sembol Listesi Görünümü
Önceki bölümde kullanılan App
bileşeni şuna benzer:
import React from 'react'; import SymbolList from './views/symbolList'; const App = () => { return <SymbolList />; }; export default App;
SymbolList
görünümünü döndüren basit bir bileşendir ve SymbolList
, sergilenen tüm ağır kaldırma sembollerini düzgünce bağlı bir kullanıcı arayüzüne yapar.
views
klasörünün içindeki symbolList.js
bakalım:
Lütfen buradaki dosyaya bakın!
Bileşen, renderSymbols
işlevinin sonuçlarını döndürür. Ve, bu veriler, Kullanım Kancası kullanarak veritabanından useQuery
:
const { loading, error, data } = useQuery(symbolsQuery, {variables: { userId }});
symbolsQuery
şu şekilde tanımlanır:
export const symbolsQuery = gql` query getSymbols($userId: uuid) { symbol { id company symbol_events(where: {user_id: {_eq: $userId}}) { id symbol trigger_type trigger_value user_id } stock_symbol_aggregate { aggregate { max { high volume } min { low volume } } } } } `;
Kullanıcı userId
alır ve bu belirli kullanıcının abone olan olaylarını, bildirim simgesinin doğru durumunu (başlıkla birlikte görüntülenen Bell simgesi) görüntülemek için alınır. Sorgu ayrıca stoğun maksimum ve minimum değerlerini de getirir. Yukarıdaki sorguda aggregate
kullanımına dikkat edin. Hasura'nın Toplama sorguları, count
, sum
, avg
, max
, min
vb. toplu değerleri getirmek için arka planda çalışır.
Yukarıdaki GraphQL çağrısından alınan yanıta göre, ön uçta görüntülenen kartların listesi:
Kart HTML yapısı böyle bir şey görünüyor:
<div key={id}> <div className="card-container"> <Card> <CardBody> <CardTitle className="card-title"> <span className="company-name">{company} </span> <Badge color="dark" pill>{id}</Badge> <div className={classNames({'bell': true, 'disabled': isSubscribed})} id={`subscribePopover-${id}`}> <FontAwesomeIcon icon={faBell} title="Subscribe" /> </div> </CardTitle> <div className="metrics"> <div className="metrics-row"> <span className="metrics-row--label">High:</span> <span className="metrics-row--value">{max.high}</span> <span className="metrics-row--label">{' '}(Volume: </span> <span className="metrics-row--value">{max.volume}</span>) </div> <div className="metrics-row"> <span className="metrics-row--label">Low: </span> <span className="metrics-row--value">{min.low}</span> <span className="metrics-row--label">{' '}(Volume: </span> <span className="metrics-row--value">{min.volume}</span>) </div> </div> <Button className="timeseries-btn" outline onClick={() => toggleTimeseries(id)}>Timeseries</Button>{' '} </CardBody> </Card> <Popover className="popover-custom" placement="bottom" target={`subscribePopover-${id}`} isOpen={isSubscribePopoverOpen === id} toggle={() => setSubscribeValues(id, symbolTriggerData)} > <PopoverHeader> Notification Options <span className="popover-close"> <FontAwesomeIcon icon={faTimes} onClick={() => handlePopoverToggle(null)} /> </span> </PopoverHeader> {renderSubscribeOptions(id, isSubscribed, symbolTriggerData)} </Popover> </div> <Collapse isOpen={expandedStockId === id}> { isOpen(id) ? <StockTimeseries symbol={id}/> : null } </Collapse> </div>
Bu kartları işlemek için ReactStrap'in Card
bileşenini kullanıyoruz. Popover
bileşeni, abonelik tabanlı seçenekleri görüntülemek için kullanılır:
Kullanıcı belirli bir stok için bell
simgesini tıkladığında, her saate haber verilecek şekilde veya stokun fiyatının girilen değere ulaştığında öğütülebilir. Bunu Olaylar/Zaman Tetikleyicileri bölümünde çalışırken göreceğiz.
Not : Bir sonraki bölümde StockTimeseries
bileşenine geleceğiz!
Lütfen stoklar listesi bileşeniyle ilgili tüm kod için symbolList.js
bakın.
Hisse Senedi Zaman Serisi Görünümü
StockTimeseries
bileşeni stocksDataQuery
sorgusunu kullanır:
export const stocksDataQuery = gql` query getStocksData($symbol: String) { stock_data(order_by: {time: desc}, where: {symbol: {_eq: $symbol}}, limit: 25) { high low open close volume time } } `;
Yukarıdaki sorgu, seçilen hisse senedinin son 25 veri noktasını getirir. Örneğin, Facebook hisse senedi açık metriği için grafik:
Bu, bazı grafik seçeneklerini [ HighchartsReact
] bileşenine geçirdiğimiz basit bir bileşendir. İşte grafik seçenekleri:
const chartOptions = { title: { text: `${symbol} Timeseries` }, subtitle: { text: 'Intraday (5min) open, high, low, close prices & volume' }, yAxis: { title: { text: '#' } }, xAxis: { title: { text: 'Time' }, categories: getDataPoints('time') }, legend: { layout: 'vertical', align: 'right', verticalAlign: 'middle' }, series: [ { name: 'high', data: getDataPoints('high') }, { name: 'low', data: getDataPoints('low') }, { name: 'open', data: getDataPoints('open') }, { name: 'close', data: getDataPoints('close') }, { name: 'volume', data: getDataPoints('volume') } ] }
X ekseni zamanı gösterir ve Y ekseni o zamandaki metrik değeri gösterir. getDataPoints
işlevi, seri her biri için bir dizi nokta oluşturmak için kullanılır.
const getDataPoints = (type) => { const values = []; data.stock_data.map((dataPoint) => { let value = dataPoint[type]; if (type === 'time') { value = new Date(dataPoint['time']).toLocaleString('en-US'); } values.push(value); }); return values; }
Basit! Grafik bileşeni bu şekilde oluşturulur! Hisse senedi zaman serilerindeki tam kod için lütfen Chart.js ve stockTimeseries.js
dosyalarına bakın.
Artık projenin veri ve kullanıcı arayüzleri kısmıyla hazır olmalısınız. Şimdi ilginç kısma geçelim - kullanıcının girdisine göre olay/zaman tetikleyicilerini ayarlama.
Olay/Zamanlanmış Tetikleyicileri Ayarlama
Bu bölümde, Hasura Konsolu'ndaki tetikleyicileri nasıl oluşturacağını ve seçilen kullanıcılara web push bildirimlerinin nasıl gönderileceğini öğreneceğiz. Başlayalım!
Hasura Konsolunda Olayları Tetikliyor
insert
tablosunda bir event trigger stock_value
oluşturalım ve tetikleme işlemi olarak stock_data
. Web kancası, stock_data
tablosunda her ekleme olduğunda çalışır.
Web kancası URL'si için bir aksaklık projesi oluşturacağız. Kolay anlaşılır hale getirmek için web kancaları hakkında biraz bilgi vereyim:
Web kancaları, belirli bir olayın meydana gelmesiyle ilgili olarak bir uygulamadan diğerine veri göndermek için kullanılır. Bir olay tetiklendiğinde, yük olarak olay verileriyle web kancası URL'sine bir HTTP POST çağrısı yapılır.
In this case, when there is an insert operation on the stock_data
table, an HTTP post call will be made to the configured webhook URL (post call in the glitch project).
Glitch Project For Sending Web-push Notifications
We've to get the webhook URL to put in the above event trigger interface. Go to glitch.com and create a new project. In this project, we'll set up an express listener and there will be an HTTP post listener. The HTTP POST payload will have all the details of the stock datapoint including open
, close
, high
, low
, volume
, time
. We'll have to fetch the list of users subscribed to this stock with the value equal to the close
metric.
These users will then be notified of the stock price via web-push notifications.
That's all we've to do to achieve the desired target of notifying users when the stock price reaches the expected value!
Let's break this down into smaller steps and implement them!
Installing Dependencies
We would need the following dependencies:
-
express
: is used for creating an express server. -
apollo-fetch
: is used for creating a fetch function for getting data from the GraphQL endpoint. -
web-push
: is used for sending web push notifications.
Please write this script in package.json
to run index.js
on npm start
command:
"scripts": { "start": "node index.js" }
Setting Up Express Server
Let's create an index.js
file as:
const express = require('express'); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.json()); const handleStockValueTrigger = (eventData, res) => { /* Code for handling this trigger */ } app.post('/', (req, res) => { const { body } = req const eventType = body.trigger.name const eventData = body.event switch (eventType) { case 'stock-value-trigger': return handleStockValueTrigger(eventData, res); } }); app.get('/', function (req, res) { res.send('Hello World - For Event Triggers, try a POST request?'); }); var server = app.listen(process.env.PORT, function () { console.log(`server listening on port ${process.env.PORT}`); });
In the above code, we've created post
and get
listeners on the route /
. get
is simple to get around! We're mainly interested in the post call. If the eventType
is stock-value-trigger
, we'll have to handle this trigger by notifying the subscribed users. Let's add that bit and complete this function!
Abone Olunan Kullanıcılar Alınıyor
const fetch = createApolloFetch({ uri: process.env.GRAPHQL_URL }); const getSubscribedUsers = (symbol, triggerValue) => { return fetch({ query: `query getSubscribedUsers($symbol: String, $triggerValue: numeric) { events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) { user_id user_subscription { subscription } } }`, variables: { symbol, triggerValue } }).then(response => response.data.events) } const handleStockValueTrigger = async (eventData, res) => { const symbol = eventData.data.new.symbol; const triggerValue = eventData.data.new.close; const subscribedUsers = await getSubscribedUsers(symbol, triggerValue); const webpushPayload = { title: `${symbol} - Stock Update`, body: `The price of this stock is ${triggerValue}` } subscribedUsers.map((data) => { sendWebpush(data.user_subscription.subscription, JSON.stringify(webpushPayload)); }) res.json(eventData.toString()); }
Yukarıdaki handleStockValueTrigger
işlevinde, önce abone olan kullanıcıları getSubscribedUsers
işlevini kullanarak getiriyoruz. Ardından, bu kullanıcıların her birine web push bildirimleri gönderiyoruz. sendWebpush
işlevi bildirim göndermek için kullanılır. Birazdan web-push uygulamasına bakacağız.
getSubscribedUsers
işlevi şu sorguyu kullanır:
query getSubscribedUsers($symbol: String, $triggerValue: numeric) { events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) { user_id user_subscription { subscription } } }
Bu sorgu, hisse senedi sembolünü ve değeri alır ve bu koşullarla eşleşen user-id
ve user_subscription
dahil olmak üzere kullanıcı ayrıntılarını getirir:
- yükte geçirilene eşit
symbol
. -
trigger_type
,event
eşittir. -
trigger_value
, bu işleve iletilen değerden büyük veya ona eşittir (bu durumdaclose
).
Kullanıcıların listesini aldığımızda, geriye kalan tek şey onlara web-push bildirimleri göndermek! Bunu hemen yapalım!
Abone Olunan Kullanıcılara Web-Push Bildirimleri Gönderme
Web-push bildirimleri göndermek için önce genel ve özel VAPID anahtarlarını almalıyız. Lütfen bu anahtarları .env
dosyasında saklayın ve bu ayrıntıları index.js
şu şekilde ayarlayın:
webPush.setVapidDetails( 'mailto:<YOUR_MAIL_ID>', process.env.PUBLIC_VAPID_KEY, process.env.PRIVATE_VAPID_KEY ); const sendWebpush = (subscription, webpushPayload) => { webPush.sendNotification(subscription, webpushPayload).catch(err => console.log('error while sending webpush', err)) }
sendNotification
işlevi, ilk parametre olarak sağlanan abonelik uç noktasında web push'u göndermek için kullanılır.
Abone olanlara web-push bildirimlerini başarılı bir şekilde göndermek için hepsi bu kadar. İşte index.js
tanımlanan kodun tamamı:
const express = require('express'); const bodyParser = require('body-parser'); const { createApolloFetch } = require('apollo-fetch'); const webPush = require('web-push'); webPush.setVapidDetails( 'mailto:<YOUR_MAIL_ID>', process.env.PUBLIC_VAPID_KEY, process.env.PRIVATE_VAPID_KEY ); const app = express(); app.use(bodyParser.json()); const fetch = createApolloFetch({ uri: process.env.GRAPHQL_URL }); const getSubscribedUsers = (symbol, triggerValue) => { return fetch({ query: `query getSubscribedUsers($symbol: String, $triggerValue: numeric) { events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) { user_id user_subscription { subscription } } }`, variables: { symbol, triggerValue } }).then(response => response.data.events) } const sendWebpush = (subscription, webpushPayload) => { webPush.sendNotification(subscription, webpushPayload).catch(err => console.log('error while sending webpush', err)) } const handleStockValueTrigger = async (eventData, res) => { const symbol = eventData.data.new.symbol; const triggerValue = eventData.data.new.close; const subscribedUsers = await getSubscribedUsers(symbol, triggerValue); const webpushPayload = { title: `${symbol} - Stock Update`, body: `The price of this stock is ${triggerValue}` } subscribedUsers.map((data) => { sendWebpush(data.user_subscription.subscription, JSON.stringify(webpushPayload)); }) res.json(eventData.toString()); } app.post('/', (req, res) => { const { body } = req const eventType = body.trigger.name const eventData = body.event switch (eventType) { case 'stock-value-trigger': return handleStockValueTrigger(eventData, res); } }); app.get('/', function (req, res) { res.send('Hello World - For Event Triggers, try a POST request?'); }); var server = app.listen(process.env.PORT, function () { console.log("server listening"); });
Bir değere sahip hisse senedine abone olarak ve bu değeri tabloya manuel olarak ekleyerek (test için) bu akışı test edelim!
2000
değeriyle AMZN
üye oldum ve ardından tabloya bu değerle bir veri noktası ekledim. Hisse senedi bildirim uygulaması, eklemeden hemen sonra beni şu şekilde bilgilendirdi:
Düzenli! Ayrıca olay çağırma günlüğünü buradan kontrol edebilirsiniz:
Web kancası işi beklendiği gibi yapıyor! Artık etkinlik tetikleyicileri için hazırız!
Zamanlanmış/Cron Tetikleyicileri
Cron olay tetikleyicisini kullanarak abone kullanıcılarına her saat başı bilgilendirmek için zamana dayalı bir tetikleyici elde edebiliriz:
Aynı web kancası URL'sini kullanabilir ve abone olan kullanıcıları, stock_price_time_based_trigger
olarak tetikleyici olay türüne göre işleyebiliriz. Uygulama, olay tabanlı tetikleyiciye benzer.
Çözüm
Bu yazımızda bir hisse senedi fiyat bildirici uygulaması oluşturduk. Alpha Vantage API'lerini kullanarak fiyatların nasıl alınacağını ve veri noktalarını Hasura destekli Postgres veritabanında nasıl depolayacağımızı öğrendik. Ayrıca Hasura GraphQL motorunun nasıl kurulacağını ve olay tabanlı ve zamanlanmış tetikleyicilerin nasıl oluşturulacağını da öğrendik. Abone olan kullanıcılara web-push bildirimleri göndermek için bir aksaklık projesi oluşturduk.