การสร้างแอพแจ้งราคาหุ้นโดยใช้ React, Apollo GraphQL และ Hasura

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างรวดเร็ว ↬ ในบทความนี้ เราจะเรียนรู้วิธีสร้างแอปพลิเคชันตามเหตุการณ์ และส่งการแจ้งเตือนทางเว็บเมื่อมีการทริกเกอร์เหตุการณ์ใดเหตุการณ์หนึ่ง เราจะตั้งค่าตารางฐานข้อมูล เหตุการณ์ และทริกเกอร์ตามกำหนดเวลาบนเอ็นจิ้น Hasura GraphQL และเชื่อมโยงตำแหน่งข้อมูล GraphQL กับแอปพลิเคชันส่วนหน้าเพื่อบันทึกการตั้งค่าราคาหุ้นของผู้ใช้

แนวคิดในการรับการแจ้งเตือนเมื่อเหตุการณ์ที่คุณเลือกได้เกิดขึ้นนั้นได้รับความนิยมเมื่อเทียบกับการยึดติดกับกระแสข้อมูลอย่างต่อเนื่องเพื่อค้นหาเหตุการณ์นั้น ๆ ด้วยตัวคุณเอง ผู้คนมักต้องการรับอีเมล/ข้อความที่เกี่ยวข้องเมื่อมีเหตุการณ์ที่ต้องการเกิดขึ้น แทนที่จะต้องรอให้เหตุการณ์นั้นเกิดขึ้นบนหน้าจอ ศัพท์เฉพาะตามเหตุการณ์ก็เป็นเรื่องธรรมดาในโลกของซอฟต์แวร์เช่นกัน

จะดีแค่ไหนถ้าคุณได้รับข้อมูลอัปเดตราคาหุ้นที่คุณชื่นชอบบนโทรศัพท์ของคุณ?

ในบทความนี้ เราจะสร้างแอปพลิเคชัน Stocks Price Notifier โดยใช้ React, Apollo GraphQL และ Hasura GraphQL engine เราจะเริ่มโปรเจ็กต์จากโค้ดสำเร็จรูป create-react-app และจะสร้างทุกสิ่งทุกอย่าง เราจะเรียนรู้วิธีตั้งค่าตารางฐานข้อมูล และกิจกรรมบนคอนโซล Hasura นอกจากนี้เรายังจะได้เรียนรู้วิธีเชื่อมโยงเหตุการณ์ของ Hasura เพื่อรับการอัปเดตราคาหุ้นโดยใช้การแจ้งเตือนทางเว็บ

ต่อไปนี้คือภาพรวมคร่าวๆ ของสิ่งที่เราจะสร้าง:

ภาพรวมของแอปพลิเคชั่นแจ้งราคาหุ้น
แอปพลิเคชั่นแจ้งราคาหุ้น

เริ่มกันเลย!

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

ภาพรวมของโครงการนี้เกี่ยวกับอะไร

ข้อมูลหุ้น (รวมถึงตัวชี้วัดเช่น high , low , open , close , volume ) จะถูกเก็บไว้ในฐานข้อมูล Postgres ที่ได้รับการสนับสนุนจาก Hasura ผู้ใช้จะสามารถสมัครรับข้อมูลหุ้นเฉพาะตามมูลค่าบางอย่าง หรือเขาสามารถเลือกรับการแจ้งเตือนทุกชั่วโมง ผู้ใช้จะได้รับการแจ้งเตือนทางเว็บเมื่อปฏิบัติตามเกณฑ์การสมัครของเขา

ดูเหมือนว่าจะมีหลายสิ่งหลายอย่าง และแน่นอนว่าจะมีคำถามปลายเปิดเกี่ยวกับวิธีที่เราจะสร้างชิ้นส่วนเหล่านี้ออกมา

นี่คือแผนการที่เราจะบรรลุโครงการนี้ในสี่ขั้นตอน:

  1. การดึงข้อมูลหุ้นโดยใช้สคริปต์ NodeJs
    เราจะเริ่มต้นด้วยการดึงข้อมูลหุ้นโดยใช้สคริปต์ NodeJs ง่ายๆ จากหนึ่งในผู้ให้บริการหุ้น API — Alpha Vantage สคริปต์นี้จะดึงข้อมูลสำหรับหุ้นตัวใดตัวหนึ่งในช่วงเวลา 5 นาที การตอบสนองของ API ได้แก่ สูง ต่ำ เปิด ปิด และ ปริมาณ ข้อมูลนี้จะถูกแทรกลงในฐานข้อมูล Postgres ที่รวมเข้ากับส่วนหลังของ Hasura
  2. การตั้งค่าเครื่องยนต์ Hasura GraphQL
    จากนั้นเราจะตั้งค่าบางตารางในฐานข้อมูล Postgres เพื่อบันทึกจุดข้อมูล Hasura จะสร้างสคีมา คิวรี และการกลายพันธุ์ของ GraphQL สำหรับตารางเหล่านี้โดยอัตโนมัติ
  3. Front-end โดยใช้ React และ Apollo Client
    ขั้นตอนต่อไปคือการผสานรวมเลเยอร์ GraphQL โดยใช้ไคลเอนต์ Apollo และ Apollo Provider (ตำแหน่งข้อมูล GraphQL ที่จัดทำโดย Hasura) จุดข้อมูลจะแสดงเป็นแผนภูมิที่ส่วนหน้า นอกจากนี้ เราจะสร้างตัวเลือกการสมัครรับข้อมูล และจะเปิดใช้การกลายพันธุ์ที่สอดคล้องกันบนเลเยอร์ GraphQL
  4. การตั้งค่าทริกเกอร์เหตุการณ์/ตามกำหนดเวลา
    Hasura เป็นเครื่องมือที่ยอดเยี่ยมสำหรับทริกเกอร์ เราจะเพิ่มเหตุการณ์และทริกเกอร์ตามกำหนดเวลาในตารางข้อมูลหุ้น ทริกเกอร์เหล่านี้จะถูกตั้งค่าหากผู้ใช้สนใจรับการแจ้งเตือนเมื่อราคาหุ้นถึงค่าที่กำหนด (ทริกเกอร์เหตุการณ์) ผู้ใช้ยังสามารถเลือกที่จะรับการแจ้งเตือนของหุ้นเฉพาะทุก ๆ ชั่วโมง (ทริกเกอร์ตามกำหนดเวลา)

เมื่อแผนพร้อมแล้ว ไปลงมือทำกันเลย!

นี่คือที่เก็บ GitHub สำหรับโครงการนี้ หากคุณหลงทางในโค้ดด้านล่างนี้ ให้อ้างอิงกับที่เก็บนี้และกลับมาเร่งความเร็ว!

การดึงข้อมูลหุ้นโดยใช้สคริปต์ NodeJs

มันไม่ได้ซับซ้อนอย่างที่คิด! เราจะต้องเขียนฟังก์ชันที่ดึงข้อมูลโดยใช้จุดปลาย Alpha Vantage และการเรียกการดึงข้อมูลนี้ควรเริ่มทำงานในช่วงเวลา 5 นาที (คุณเดาถูกแล้ว เราจะต้องเรียกใช้ฟังก์ชันนี้ใน setInterval )

หากคุณยังคงสงสัยว่า Alpha Vantage คืออะไรและต้องการแค่เอาสิ่งนี้ออกจากหัวของคุณก่อนที่จะเข้าสู่ส่วนการเข้ารหัส นี่คือ:

Alpha Vantage Inc. เป็นผู้ให้บริการชั้นนำของ API ฟรีสำหรับข้อมูลเรียลไทม์และข้อมูลย้อนหลังของหุ้น ฟอเร็กซ์ (FX) และดิจิทัล/สกุลเงินดิจิทัล

เราจะใช้จุดปลายนี้เพื่อรับตัวชี้วัดที่จำเป็นของหุ้นตัวใดตัวหนึ่ง API นี้ต้องการคีย์ API เป็นหนึ่งในพารามิเตอร์ คุณสามารถรับคีย์ API ฟรีได้จากที่นี่ ตอนนี้เราพร้อมแล้วที่จะเข้าสู่ส่วนที่น่าสนใจ — มาเริ่มเขียนโค้ดกันเลย!

การติดตั้งการพึ่งพา

สร้างไดเร็กทอรี stocks-app และสร้างไดเร็กทอรี server ภายใน เริ่มต้นเป็นโปรเจ็กต์โหนดโดยใช้ npm init จากนั้นติดตั้งการพึ่งพาเหล่านี้:

 npm i isomorphic-fetch pg nodemon --save

นี่เป็นเพียงการพึ่งพาสามรายการเท่านั้นที่เราจำเป็นต้องเขียนสคริปต์นี้เพื่อดึงราคาหุ้นและเก็บไว้ในฐานข้อมูล Postgres

นี่คือคำอธิบายสั้น ๆ ของการขึ้นต่อกันเหล่านี้:

  • isomorphic-fetch
    มันทำให้ง่ายต่อการใช้การ fetch isomorphically (ในรูปแบบเดียวกัน) ทั้งบนไคลเอนต์และเซิร์ฟเวอร์
  • pg
    มันเป็นไคลเอนต์ PostgreSQL ที่ไม่บล็อกสำหรับ NodeJs
  • nodemon
    โดยจะรีสตาร์ทเซิร์ฟเวอร์โดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงไฟล์ในไดเร็กทอรี

ตั้งค่าคอนฟิก

เพิ่มไฟล์ config.js ที่ระดับรูท เพิ่มข้อมูลโค้ดด้านล่างในไฟล์นั้นในตอนนี้:

 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 เราจะกลับมาแก้ไขสิ่งนี้ในขณะที่เราตั้งค่าส่วนเครื่องยนต์ Hasura!

การเริ่มต้นพูลการเชื่อมต่อ Postgres สำหรับการสืบค้นฐานข้อมูล

กลุ่มการ connection pool เป็นคำศัพท์ทั่วไปในวิทยาการคอมพิวเตอร์และคุณมักจะได้ยินคำนี้ในขณะที่จัดการกับฐานข้อมูล

ขณะทำการสืบค้นข้อมูลในฐานข้อมูล คุณจะต้องสร้างการเชื่อมต่อกับฐานข้อมูลก่อน การเชื่อมต่อนี้ใช้ข้อมูลประจำตัวของฐานข้อมูลและช่วยให้คุณสามารถสอบถามตารางใดก็ได้ในฐานข้อมูล

หมายเหตุ : การสร้างการเชื่อมต่อฐานข้อมูลนั้นมีค่าใช้จ่ายสูงและสิ้นเปลืองทรัพยากรจำนวนมาก พูลการเชื่อมต่อแคชการเชื่อมต่อฐานข้อมูลและนำกลับมาใช้ใหม่ในการสืบค้นที่สำเร็จ หากมีการใช้การเชื่อมต่อที่เปิดอยู่ทั้งหมด การเชื่อมต่อใหม่จะถูกสร้างและเพิ่มลงในพูล

ตอนนี้เป็นที่ชัดเจนว่าพูลการเชื่อมต่อคืออะไรและใช้สำหรับอะไร มาเริ่มด้วยการสร้างอินสแตนซ์ของพูลการเชื่อมต่อ pg สำหรับแอปพลิเคชันนี้:

เพิ่มไฟล์ pool.js ที่ระดับรูทและสร้างอินสแตนซ์พูลเป็น:

 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;

โค้ดด้านบนนี้สร้างอินสแตนซ์ของ Pool ด้วยตัวเลือกการกำหนดค่าตามที่กำหนดไว้ในไฟล์ปรับแต่ง เรายังดำเนินการไฟล์กำหนดค่าให้เสร็จสิ้น แต่จะไม่มีการเปลี่ยนแปลงใดๆ ที่เกี่ยวข้องกับตัวเลือกการกำหนดค่า

ตอนนี้เราได้เริ่มต้นและพร้อมที่จะเริ่มการเรียก API ไปยังจุดปลาย Alpha Vantage

ไปที่บิตที่น่าสนใจกันเถอะ!

กำลังดึงข้อมูลหุ้น

ในส่วนนี้ เราจะดึงข้อมูลหุ้นจากจุดปลาย Alpha Vantage นี่คือไฟล์ index.js :

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

สำหรับวัตถุประสงค์ของโครงการนี้ เราจะค้นหาราคาสำหรับหุ้นเหล่านี้เท่านั้น — NFLX (Netflix), MSFT (Microsoft), AMZN (Amazon), W (Wayfair), FB (Facebook)

อ้างถึงไฟล์นี้สำหรับตัวเลือกการกำหนดค่า ฟังก์ชัน getStocksData ไม่ได้ทำอะไรมาก! โดยจะวนผ่านสัญลักษณ์เหล่านี้และค้นหาจุดปลาย Alpha Vantage ${host}query/?function=${timeSeriesFunction}&symbol=${symbol}&interval=${interval}&apikey=${key} เพื่อรับเมตริกสำหรับหุ้นเหล่านี้

ฟังก์ชัน insertStocksData จะวางจุดข้อมูลเหล่านี้ไว้ในฐานข้อมูล Postgres นี่คือฟังก์ชัน insertStocksData :

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

นี่ไง! เราได้ดึงจุดข้อมูลของสต็อคจาก Alpha Vantage API และได้เขียนฟังก์ชันเพื่อใส่ข้อมูลเหล่านี้ในฐานข้อมูล Postgres ในตาราง stock_data มีชิ้นส่วนที่ขาดหายไปเพียงชิ้นเดียวที่จะทำให้งานนี้สำเร็จ! เราต้องเติมค่าที่ถูกต้องในไฟล์ปรับแต่ง เราจะได้ค่าเหล่านี้หลังจากตั้งค่าเครื่องยนต์ Hasura ไปที่นั้นทันที!

โปรดอ้างอิงไดเร็กทอรี server สำหรับรหัสที่สมบูรณ์ในการดึงจุดข้อมูลจากจุดปลาย Alpha Vantage และเติมข้อมูลนั้นลงในฐานข้อมูล Hasura Postgres

หากวิธีการในการตั้งค่าการเชื่อมต่อ ตัวเลือกการกำหนดค่า และการแทรกข้อมูลโดยใช้การสืบค้นข้อมูลดิบดูยากสักหน่อย โปรดอย่ากังวล เราจะเรียนรู้วิธีการทำทั้งหมดนี้ด้วยวิธีง่ายๆ ด้วยการกลายพันธุ์ของ GraphQL เมื่อตั้งค่าเอ็นจิ้น Hasura แล้ว!

การตั้งค่า Hasura GraphQL Engine

มันง่ายมากในการตั้งค่าเอ็นจิ้น Hasura และเริ่มต้นใช้งานด้วยสคีมา GraphQL การสืบค้น การกลายพันธุ์ การสมัครสมาชิก ทริกเกอร์เหตุการณ์ และอีกมากมาย!

คลิกที่ ลอง Hasura และป้อนชื่อโครงการ:

การสร้างโครงการ Hasura
การสร้างโครงการ Hasura (ตัวอย่างขนาดใหญ่)

ฉันใช้ฐานข้อมูล Postgres ที่โฮสต์บน Heroku สร้างฐานข้อมูลบน Heroku และเชื่อมโยงกับโครงการนี้ จากนั้นคุณควรพร้อมที่จะสัมผัสกับพลังของคอนโซล Hasura ที่มีข้อความค้นหามากมาย

โปรดคัดลอก Postgres DB URL ที่คุณจะได้รับหลังจากสร้างโครงการ เราจะต้องใส่สิ่งนี้ลงในไฟล์ปรับแต่ง

คลิกที่ Launch Console และคุณจะถูกเปลี่ยนเส้นทางไปยังมุมมองนี้:

Hasura Console
คอนโซล Hasura (ตัวอย่างขนาดใหญ่)

มาเริ่มสร้างตารางที่เราต้องการสำหรับโปรเจ็กต์นี้กัน

การสร้างสคีมาตารางบนฐานข้อมูล Postgres

กรุณาไปที่แท็บข้อมูลและคลิกที่เพิ่มตาราง! มาเริ่มสร้างตารางกัน:

ตาราง symbol

ตารางนี้จะใช้สำหรับเก็บข้อมูลของสัญลักษณ์ สำหรับตอนนี้ ฉันเก็บสองฟิลด์ไว้ที่นี่ — id และ company id ฟิลด์เป็นคีย์หลักและ company เป็นประเภท varchar มาเพิ่มสัญลักษณ์บางอย่างในตารางนี้:

ตารางสัญลักษณ์
ตาราง symbol (ตัวอย่างขนาดใหญ่)

ตาราง stock_data

ตาราง stock_data เก็บ id symbol time และตัวชี้วัด เช่น high low open close volume สคริปต์ NodeJs ที่เราเขียนไว้ก่อนหน้านี้ในส่วนนี้จะถูกนำมาใช้เพื่อเติมข้อมูลในตารางนี้โดยเฉพาะ

นี่คือลักษณะของตาราง:

ตาราง stock_data
ตาราง stock_data (ตัวอย่างขนาดใหญ่)

ประณีต! ไปที่ตารางอื่นในสคีมาฐานข้อมูลกันเถอะ!

user_subscription table

ตาราง user_subscription เก็บออบเจ็กต์การบอกรับเป็นสมาชิกเทียบกับ ID ผู้ใช้ ออบเจ็กต์การสมัครสมาชิกนี้ใช้สำหรับส่งการแจ้งเตือนทางเว็บไปยังผู้ใช้ เราจะเรียนรู้ในภายหลังในบทความเกี่ยวกับวิธีสร้างออบเจ็กต์การสมัครรับข้อมูลนี้

มีสองฟิลด์ในตารางนี้ — id เป็นคีย์หลักของประเภท uuid และฟิลด์การสมัครสมาชิกเป็นประเภท jsonb

ตาราง events

นี่เป็นส่วนสำคัญและใช้สำหรับจัดเก็บตัวเลือกกิจกรรมการแจ้งเตือน เมื่อผู้ใช้เลือกรับการอัปเดตราคาหุ้นตัวใดตัวหนึ่ง เราจะจัดเก็บข้อมูลเหตุการณ์นั้นไว้ในตารางนี้ ตารางนี้มีคอลัมน์เหล่านี้:

  • id : เป็นคีย์หลักที่มีคุณสมบัติเพิ่มอัตโนมัติ
  • symbol : เป็นช่องข้อความ
  • user_id : เป็นประเภท uuid
  • trigger_type : ใช้สำหรับจัดเก็บประเภททริกเกอร์เหตุการณ์ — time/event
  • trigger_value : ใช้สำหรับเก็บค่าทริกเกอร์ ตัวอย่างเช่น หากผู้ใช้เลือกใช้ทริกเกอร์เหตุการณ์ตามราคา — เขาต้องการอัปเดตหากราคาของหุ้นถึง 1,000 แล้ว trigger_value จะเป็น 1,000 และ trigger_type จะเป็น event

นี่คือตารางทั้งหมดที่เราต้องการสำหรับโครงการนี้ เรายังต้องตั้งค่าความสัมพันธ์ระหว่างตารางเหล่านี้เพื่อให้มีกระแสข้อมูลและการเชื่อมต่อที่ราบรื่น มาทำกัน!

การสร้างความสัมพันธ์ระหว่างตาราง

ตาราง events ใช้สำหรับส่งการแจ้งเตือนทางเว็บโดยอิงตามค่าของเหตุการณ์ ดังนั้นจึงเหมาะสมที่จะเชื่อมต่อตารางนี้กับตาราง user_subscription เพื่อให้สามารถส่งการแจ้งเตือนแบบพุชในการสมัครสมาชิกที่จัดเก็บไว้ในตารางนี้

 events.user_id → user_subscription.id

ตาราง stock_data เกี่ยวข้องกับตารางสัญลักษณ์ดังนี้:

 stock_data.symbol → symbol.id

เรายังต้องสร้างความสัมพันธ์บางอย่างในตาราง symbol ดังนี้:

 stock_data.symbol → symbol.id events.symbol → symbol.id

ตอนนี้เราได้สร้างตารางที่จำเป็นและสร้างความสัมพันธ์ระหว่างพวกเขาแล้ว! มาสลับไปที่แท็บ GRAPHIQL บนคอนโซลเพื่อดูความมหัศจรรย์กันเถอะ!

Hasura ได้ตั้งค่าการสืบค้น GraphQL ตามตารางเหล่านี้แล้ว:

การสืบค้น/การกลายพันธุ์ของ GraphQL บนคอนโซล Hasura
การสืบค้น/การกลายพันธุ์ของ GraphQL บนคอนโซล Hasura (ตัวอย่างขนาดใหญ่)

การค้นหาในตารางเหล่านี้ทำได้ง่ายมาก และคุณยังสามารถใช้ตัวกรอง/คุณสมบัติเหล่านี้ ( order_by , limit , offset , distinct_on , where ) เพื่อรับข้อมูลที่ต้องการได้

ทั้งหมดนี้ดูดี แต่เรายังไม่ได้เชื่อมต่อโค้ดฝั่งเซิร์ฟเวอร์ของเรากับคอนโซล Hasura มาทำให้เสร็จสักหน่อย!

การเชื่อมต่อสคริปต์ NodeJs กับฐานข้อมูล Postgres

โปรดใส่ตัวเลือกที่จำเป็นในไฟล์ config.js ในไดเร็กทอรี server ดังนี้:

 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;

โปรดใส่ตัวเลือกเหล่านี้จากสตริงฐานข้อมูลที่สร้างขึ้นเมื่อเราสร้างฐานข้อมูล Postgres บน Heroku

apiHostOptions ประกอบด้วยตัวเลือกที่เกี่ยวข้องกับ API เช่น host , key , timeSeriesFunction และ interval

คุณจะได้รับฟิลด์ graphqlURL ในแท็บ GRAPHIQL บนคอนโซล Hasura

ฟังก์ชัน getConfig ใช้สำหรับคืนค่าที่ร้องขอจากอ็อบเจ็กต์ config เราใช้สิ่งนี้ใน index.js ในไดเร็กทอรี server

ถึงเวลาเรียกใช้เซิร์ฟเวอร์และเติมข้อมูลบางส่วนในฐานข้อมูล ฉันได้เพิ่มหนึ่งสคริปต์ใน package.json เป็น:

 "scripts": { "start": "nodemon index.js" }

รัน npm start บนเทอร์มินัลและจุดข้อมูลของอาร์เรย์สัญลักษณ์ใน index.js ควรถูกเติมลงในตาราง

การปรับโครงสร้างการสืบค้นข้อมูลดิบในสคริปต์ NodeJs เป็นการกลายพันธุ์ของ GraphQL

เมื่อตั้งค่าเอ็นจิ้น Hasura แล้ว เรามาดูกันว่าการเรียกการกลายพันธุ์บนตาราง stock_data นั้นง่ายเพียงใด

ฟังก์ชัน insertStocksData ใน queries.js ใช้การสืบค้นข้อมูลดิบ:

 const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)';

มาทำการ refactor เคียวรีนี้และใช้การกลายพันธุ์ที่ขับเคลื่อนโดยเอ็นจิ้น Hasura นี่คือการรีแฟค 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 }

โปรดทราบ: เราต้องเพิ่ม graphqlURL ในไฟล์ config.js

โมดูล apollo-fetch ส่งคืนฟังก์ชันดึงข้อมูลที่สามารถใช้ในการสืบค้น/เปลี่ยนแปลงวันที่บนจุดปลาย GraphQL ง่ายพอใช่มั้ย?

การเปลี่ยนแปลงอย่างเดียวที่เราต้องทำใน index.js คือการส่งคืนอ็อบเจ็กต์ stocks ในรูปแบบตามที่ฟังก์ชัน insertStocksData ต้องการ โปรดตรวจสอบ index2.js และ queries2.js สำหรับโค้ดที่สมบูรณ์ของแนวทางนี้

ตอนนี้เราได้ทำดาต้าไซด์ของโปรเจ็กต์เสร็จแล้ว มาต่อกันที่ front-end bit และสร้างส่วนประกอบที่น่าสนใจ!

หมายเหตุ : เราไม่ต้องรักษาตัวเลือกการกำหนดค่าฐานข้อมูลด้วยวิธีนี้!

Front-end โดยใช้ React และ Apollo Client

โปรเจ็กต์ส่วนหน้าอยู่ในที่เก็บเดียวกันและสร้างขึ้นโดยใช้แพ็คเกจ create-react-app เจ้าหน้าที่บริการที่สร้างโดยใช้แพ็คเกจนี้รองรับการแคชสินทรัพย์ แต่ไม่อนุญาตให้เพิ่มการปรับแต่งเพิ่มเติมในไฟล์พนักงานบริการ มีปัญหาเปิดอยู่แล้วเพื่อเพิ่มการสนับสนุนสำหรับตัวเลือกพนักงานบริการที่กำหนดเอง มีวิธีแก้ไขปัญหานี้และเพิ่มการสนับสนุนสำหรับผู้ปฏิบัติงานบริการแบบกำหนดเอง

เริ่มต้นด้วยการดูโครงสร้างสำหรับโครงการส่วนหน้า:

ไดเรกทอรีโครงการ
ไดเรกทอรีโครงการ (ตัวอย่างขนาดใหญ่)

โปรดตรวจสอบไดเรกทอรี src ! ไม่ต้องกังวลกับไฟล์ที่เกี่ยวข้องกับพนักงานบริการในตอนนี้ เราจะเรียนรู้เพิ่มเติมเกี่ยวกับไฟล์เหล่านี้ในภายหลังในหัวข้อนี้ โครงสร้างโครงการที่เหลือดูเรียบง่าย โฟลเดอร์ components จะมีส่วนประกอบ (ตัวโหลด, แผนภูมิ); โฟลเดอร์ services มีฟังก์ชันช่วยเหลือ/บริการที่ใช้สำหรับเปลี่ยนวัตถุในโครงสร้างที่ต้องการ styles ตามชื่อมีไฟล์ sass ที่ใช้สำหรับจัดสไตล์โปรเจ็กต์ views เป็นไดเร็กทอรีหลักและมีส่วนประกอบของเลเยอร์การดู

เราต้องการองค์ประกอบมุมมองเพียงสองอย่างสำหรับโปรเจ็กต์นี้ — The Symbol List และ Symbol Timeseries เราจะสร้างอนุกรมเวลาโดยใช้องค์ประกอบแผนภูมิจากไลบรารี highcharts มาเริ่มเพิ่มโค้ดในไฟล์เหล่านี้เพื่อสร้างส่วนต่างๆ ในส่วนหน้ากันเถอะ!

การติดตั้งการพึ่งพา

นี่คือรายการการพึ่งพาที่เราต้องการ:

  • apollo-boost
    Apollo boost เป็นวิธีที่ไม่มีการกำหนดค่าในการเริ่มใช้งาน Apollo Client มาพร้อมกับตัวเลือกการกำหนดค่าเริ่มต้น
  • reactstrap และ bootstrap
    ส่วนประกอบถูกสร้างขึ้นโดยใช้สองแพ็คเกจนี้
  • graphql และ graphql-type-json
    graphql เป็นการพึ่งพาที่จำเป็นสำหรับการใช้ apollo-boost และ graphql-type-json ใช้สำหรับรองรับประเภทข้อมูล json ที่ใช้ในสคีมา GraphQL
  • highcharts และ highcharts-react-official
    และสองแพ็คเกจนี้จะใช้สำหรับสร้างแผนภูมิ:

  • node-sass
    เพิ่มเข้ามาเพื่อรองรับไฟล์ sass สำหรับใส่สไตล์

  • uuid
    แพ็คเกจนี้ใช้สำหรับสร้างค่าสุ่มที่แข็งแกร่ง

การพึ่งพาเหล่านี้ทั้งหมดจะสมเหตุสมผลเมื่อเราเริ่มใช้งานในโครงการ มาเริ่มกันเลยดีกว่า!

การตั้งค่าไคลเอนต์ Apollo

สร้าง apolloClient.js ภายในโฟลเดอร์ src เป็น:

 import ApolloClient from 'apollo-boost'; const apolloClient = new ApolloClient({ uri: '<HASURA_CONSOLE_URL>' }); export default apolloClient;

โค้ดด้านบนสร้างอินสแตนซ์ ApolloClient และใช้ uri ในตัวเลือกการกำหนดค่า uri คือ URL ของคอนโซล Hasura ของคุณ คุณจะได้รับฟิลด์ uri นี้บนแท็บ GRAPHIQL ในส่วน GraphQL Endpoint

รหัสด้านบนดูเรียบง่าย แต่ดูแลส่วนหลักของโครงการ! มันเชื่อมต่อสคีมา GraphQL ที่สร้างบน Hasura กับโครงการปัจจุบัน

นอกจากนี้เรายังต้องส่งอ็อบเจ็กต์ไคลเอ็นต์ apollo นี้ไปยัง ApolloProvider และห่อส่วนประกอบรูทภายใน ApolloProvider สิ่งนี้จะเปิดใช้งานส่วนประกอบที่ซ้อนกันทั้งหมดภายในองค์ประกอบหลักเพื่อใช้ client prop และ fire เคียวรีบนวัตถุไคลเอนต์นี้

มาแก้ไขไฟล์ index.js เป็น:

 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') );

โปรดละเว้นรหัสที่เกี่ยวข้องกับการ insertSubscription เราจะเข้าใจในรายละเอียดในภายหลัง รหัสที่เหลือควรใช้งานได้ง่าย ฟังก์ชันการ render ใช้องค์ประกอบรูทและ elementId เป็นพารามิเตอร์ client ประกาศ (อินสแตนซ์ ApolloClient) กำลังถูกส่งผ่านไปยัง ApolloProvider คุณสามารถตรวจสอบไฟล์ index.js ฉบับสมบูรณ์ได้ที่นี่

การตั้งค่าเจ้าหน้าที่บริการแบบกำหนดเอง

พนักงานบริการคือไฟล์ JavaScript ที่มีความสามารถในการสกัดกั้นคำขอของเครือข่าย ใช้สำหรับการสืบค้นแคชเพื่อตรวจสอบว่าเนื้อหาที่ร้องขอมีอยู่แล้วในแคชหรือไม่ แทนที่จะทำการขี่ไปยังเซิร์ฟเวอร์ พนักงานบริการยังใช้สำหรับส่งการแจ้งเตือนทางเว็บไปยังอุปกรณ์ที่สมัครรับข้อมูล

เราต้องส่งการแจ้งเตือนทางเว็บสำหรับการอัปเดตราคาหุ้นไปยังผู้ใช้ที่สมัครรับข้อมูล มาตั้งค่าพื้นฐานและสร้างไฟล์พนักงานบริการนี้กันเถอะ!

ข้อมูลที่เกี่ยวข้องกับการ insertSubscription ในไฟล์ index.js ทำหน้าที่ลงทะเบียนพนักงานบริการ และวางออบเจกต์การบอกรับ subscriptionMutation ในฐานข้อมูลโดยใช้ subscribeMutation

โปรดอ้างอิงเคียวรี.js สำหรับการสืบค้นและการกลายพันธุ์ทั้งหมดที่ใช้ในโปรเจ็กต์

serviceWorker.register(insertSubscription); เรียกใช้ฟังก์ชัน register ที่เขียนในไฟล์ serviceWorker.js นี่คือ:

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

ฟังก์ชันข้างต้นจะตรวจสอบว่าเบราว์เซอร์รองรับ serviceWorker ไม่ จากนั้นจึงลงทะเบียนไฟล์ service worker ที่โฮสต์บน URL swUrl เราจะตรวจสอบไฟล์นี้ในอีกสักครู่!

ฟังก์ชัน getSubscription ทำหน้าที่รับออบเจ็กต์การบอกรับสมาชิกโดยใช้วิธีการ subscribe บนอ็อบเจ็กต์ pushManager ออบเจ็กต์การสมัครสมาชิกนี้จะถูกเก็บไว้ในตาราง user_subscription เทียบกับ userId โปรดทราบว่า ID ผู้ใช้กำลังถูกสร้างขึ้นโดยใช้ฟังก์ชัน uuid มาดูฟังก์ชัน getSubscription กัน:

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

คุณสามารถตรวจสอบไฟล์ serviceWorker.js เพื่อดูรหัสที่สมบูรณ์ได้!

ป๊อปอัปการแจ้งเตือน
ป๊อปอัปการแจ้งเตือน (ตัวอย่างขนาดใหญ่)

Notification.requestPermission() เรียกป๊อปอัปนี้ซึ่งขออนุญาตจากผู้ใช้ในการส่งการแจ้งเตือน เมื่อผู้ใช้คลิกที่อนุญาต ออบเจ็กต์การสมัครสมาชิกจะถูกสร้างขึ้นโดยบริการพุช เรากำลังจัดเก็บวัตถุนั้นใน localStorage เป็น:

ออบเจ็กต์การสมัครสมาชิก Webpush
ออบเจ็กต์การสมัครสมาชิก Webpush (ตัวอย่างขนาดใหญ่)

endpoint ของฟิลด์ในวัตถุด้านบนใช้เพื่อระบุอุปกรณ์และเซิร์ฟเวอร์ใช้ปลายทางนี้เพื่อส่งการแจ้งเตือนแบบพุชของเว็บไปยังผู้ใช้

เราได้ดำเนินการเริ่มต้นและลงทะเบียนพนักงานบริการแล้ว เรายังมีออบเจ็กต์การสมัครสมาชิกของผู้ใช้! วิธีนี้ใช้ได้ผลดีเพราะมีไฟล์ serviceWorker.js อยู่ในโฟลเดอร์ public มาตั้งค่าพนักงานบริการเพื่อเตรียมของให้พร้อมกันเถอะ!

หัวข้อนี้ค่อนข้างยาก แต่มาทำให้ถูกต้องกันเถอะ! ตามที่กล่าวไว้ก่อนหน้านี้ ยูทิลิตี create-react-app ไม่สนับสนุนการปรับแต่งตามค่าเริ่มต้นสำหรับผู้ปฏิบัติงานบริการ เราสามารถใช้พนักงานบริการลูกค้าได้สำเร็จโดยใช้โมดูล workbox-build

นอกจากนี้เรายังต้องตรวจสอบให้แน่ใจว่าการทำงานเริ่มต้นของไฟล์ที่แคชล่วงหน้านั้นไม่เสียหาย เราจะแก้ไขส่วนที่พนักงานบริการสร้างขึ้นในโปรเจ็กต์ และ workbox-build ช่วยในการบรรลุสิ่งนั้น! ของเรียบร้อย! เรามาทำให้มันเรียบง่ายกันเถอะ และลงรายการทั้งหมดที่เราต้องทำเพื่อให้เจ้าหน้าที่บริการแบบกำหนดเองทำงาน:

  • จัดการการแคชล่วงหน้าของเนื้อหาโดยใช้ workboxBuild
  • สร้างเทมเพลตพนักงานบริการสำหรับการแคชสินทรัพย์
  • สร้างไฟล์ sw-precache-config.js เพื่อให้ตัวเลือกการกำหนดค่าแบบกำหนดเอง
  • เพิ่มสคริปต์ build service worker ในขั้นตอนการสร้างใน package.json

ไม่ต้องกังวลหากทั้งหมดนี้ฟังดูสับสน! บทความนี้ไม่ได้เน้นที่การอธิบายความหมายเบื้องหลังแต่ละประเด็นเหล่านี้ เราต้องมุ่งเน้นไปที่ส่วนการนำไปใช้ในตอนนี้! ฉันจะพยายามอธิบายเหตุผลที่อยู่เบื้องหลังการทำงานทั้งหมดเพื่อสร้างพนักงานบริการแบบกำหนดเองในบทความอื่น

มาสร้างไฟล์สองไฟล์ sw-build.js และ sw-custom.js ในไดเร็กทอรี src โปรดดูลิงก์ไปยังไฟล์เหล่านี้และเพิ่มรหัสในโครงการของคุณ

ตอนนี้มาสร้างไฟล์ sw-precache-config.js ที่ระดับรูทและเพิ่มรหัสต่อไปนี้ในไฟล์นั้น:

 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' }] }

เรามาแก้ไขไฟล์ package.json เพื่อให้มีที่ว่างสำหรับสร้างไฟล์เจ้าหน้าที่บริการแบบกำหนดเอง:

เพิ่มคำสั่งเหล่านี้ในส่วน scripts :

 "build-sw": "node ./src/sw-build.js", "clean-cra-sw": "rm -f build/precache-manifest.*.js && rm -f build/service-worker.js",

และแก้ไขสคริปต์การ build เป็น:

 "build": "react-scripts build && npm run build-sw && npm run clean-cra-sw",

ในที่สุดการตั้งค่าก็เสร็จสิ้น! ตอนนี้เราต้องเพิ่มไฟล์ผู้ปฏิบัติงานบริการแบบกำหนดเองในโฟลเดอร์ public :

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

เราเพิ่งเพิ่มตัวฟัง push ชเพื่อฟังการแจ้งเตือนแบบพุชที่ส่งมาจากเซิร์ฟเวอร์ ฟังก์ชัน showNotification ใช้สำหรับแสดงการแจ้งเตือนทางเว็บแก่ผู้ใช้

นี่ไง! เราเสร็จสิ้นการทำงานหนักในการตั้งค่าพนักงานบริการแบบกำหนดเองเพื่อจัดการข้อความ Push ของเว็บแล้ว เราจะเห็นการแจ้งเตือนเหล่านี้ทำงานเมื่อเราสร้างอินเทอร์เฟซผู้ใช้!

เรากำลังเข้าใกล้การสร้างชิ้นส่วนของรหัสหลัก มาเริ่มกันที่วิวแรกกันเลย!

มุมมองรายการสัญลักษณ์

ส่วนประกอบ App ที่ใช้ในส่วนก่อนหน้ามีลักษณะดังนี้:

 import React from 'react'; import SymbolList from './views/symbolList'; const App = () => { return <SymbolList />; }; export default App;

เป็นองค์ประกอบอย่างง่ายที่ส่งคืนมุมมอง SymbolList และ SymbolList ทำหน้าที่แสดงสัญลักษณ์จำนวนมากในส่วนต่อประสานผู้ใช้ที่เชื่อมโยงอย่างเรียบร้อย

ลองดู symbolList.js ภายในโฟลเดอร์ views :

โปรดดูไฟล์ที่นี่!

คอมโพเนนต์ส่งคืนผลลัพธ์ของฟังก์ชัน renderSymbols และข้อมูลนี้กำลังดึงมาจากฐานข้อมูลโดยใช้ useQuery hook เป็น:

 const { loading, error, data } = useQuery(symbolsQuery, {variables: { userId }});

symbolsQuery ถูกกำหนดเป็น:

 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 } } } } } `;

ใช้ userId ผู้ใช้และดึงข้อมูลกิจกรรมที่สมัครรับข้อมูลของผู้ใช้รายนั้นเพื่อแสดงสถานะที่ถูกต้องของไอคอนการแจ้งเตือน (ไอคอนกระดิ่งที่แสดงพร้อมกับชื่อ) แบบสอบถามยังดึงค่าสูงสุดและต่ำสุดของหุ้น สังเกตการใช้การ aggregate ในแบบสอบถามข้างต้น ข้อความค้นหาการรวมของ Hasura ทำงานเบื้องหลังเพื่อดึงค่ารวมเช่น count , sum , avg , max , min ฯลฯ

จากการตอบสนองจากการเรียก GraphQL ด้านบน ต่อไปนี้คือรายการการ์ดที่แสดงที่ส่วนหน้า:

บัตรหุ้น
บัตรหุ้น (ตัวอย่างขนาดใหญ่)

โครงสร้าง HTML ของการ์ดมีลักษณะดังนี้:

 <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>

เรากำลังใช้องค์ประกอบ Card ของ ReactStrap เพื่อแสดงผลการ์ดเหล่านี้ คอมโพเนนต์ Popover ใช้สำหรับแสดงตัวเลือกตามการสมัครรับข้อมูล:

ตัวเลือกการแจ้งเตือน
ตัวเลือกการแจ้งเตือน (ตัวอย่างขนาดใหญ่)

เมื่อผู้ใช้คลิกที่ไอคอน bell สำหรับหุ้นตัวใดตัวหนึ่ง เขาสามารถเลือกรับการแจ้งเตือนทุกชั่วโมงหรือเมื่อราคาของหุ้นถึงค่าที่ป้อนไว้ เราจะเห็นการดำเนินการนี้ในส่วนทริกเกอร์เหตุการณ์/เวลา

หมายเหตุ : เราจะไปที่องค์ประกอบ StockTimeseries ในส่วนถัดไป!

โปรดดู symbolList.js สำหรับรหัสที่สมบูรณ์ที่เกี่ยวข้องกับองค์ประกอบรายการหุ้น

หุ้น Timeseries ดู

องค์ประกอบ StockTimeseries ใช้แบบสอบถาม stocksDataQuery :

 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 } } `;

ข้อความค้นหาด้านบนดึงข้อมูล 25 จุดข้อมูลล่าสุดของหุ้นที่เลือก ตัวอย่างเช่น นี่คือแผนภูมิสำหรับตัวชี้วัดการ เปิด หุ้นของ Facebook:

ไทม์ไลน์ราคาหุ้น
ไทม์ไลน์ราคาหุ้น (ตัวอย่างขนาดใหญ่)

นี่เป็นองค์ประกอบตรงไปตรงมาที่เราส่งผ่านตัวเลือกแผนภูมิบางตัวไปยังองค์ประกอบ [ HighchartsReact ] นี่คือตัวเลือกแผนภูมิ:

 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 แสดงเวลา และแกน Y แสดงค่าเมตริกในขณะนั้น ฟังก์ชัน getDataPoints ใช้สำหรับสร้างชุดของคะแนนสำหรับแต่ละชุดข้อมูล

 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; }

เรียบง่าย! นั่นคือวิธีสร้างองค์ประกอบแผนภูมิ! โปรดดูที่ไฟล์ Chart.js และ stockTimeseries.js สำหรับรหัสที่สมบูรณ์ของอนุกรมเวลาของหุ้น

ตอนนี้คุณควรจะพร้อมสำหรับข้อมูลและส่วนติดต่อผู้ใช้ของโปรเจ็กต์แล้ว ตอนนี้เรามาดูส่วนที่น่าสนใจกันดีกว่า — การตั้งค่าทริกเกอร์เหตุการณ์/เวลาตามข้อมูลที่ผู้ใช้ป้อน

การตั้งค่าทริกเกอร์เหตุการณ์/ตามกำหนดเวลา

ในส่วนนี้ เราจะเรียนรู้วิธีตั้งค่าทริกเกอร์บนคอนโซล Hasura และวิธีส่งการแจ้งเตือนทางเว็บไปยังผู้ใช้ที่เลือก มาเริ่มกันเลย!

ทริกเกอร์เหตุการณ์บนคอนโซล Hasura

มาสร้างทริกเกอร์เหตุการณ์ stock_value บนตาราง stock_data และ insert เป็นการดำเนินการทริกเกอร์ เว็บฮุคจะทำงานทุกครั้งที่มีการแทรกในตาราง stock_data

การตั้งค่าทริกเกอร์เหตุการณ์
การตั้งค่าทริกเกอร์เหตุการณ์ (ตัวอย่างขนาดใหญ่)

เรากำลังจะสร้างโครงการผิดพลาดสำหรับ URL ของเว็บฮุค ให้ฉันใส่ข้อมูลเล็กน้อยเกี่ยวกับเว็บฮุคเพื่อให้เข้าใจได้ง่าย:

Webhooks ใช้สำหรับส่งข้อมูลจากแอปพลิเคชันหนึ่งไปยังอีกแอปพลิเคชันหนึ่งเมื่อมีเหตุการณ์เฉพาะเกิดขึ้น เมื่อเหตุการณ์ถูกทริกเกอร์ การเรียก HTTP POST จะทำไปยัง URL ของเว็บฮุคโดยมีข้อมูลเหตุการณ์เป็นเพย์โหลด

ในกรณีนี้ เมื่อมีการแทรกในตาราง stock_data จะมีการเรียกการโพสต์ HTTP ไปยัง URL ของเว็บฮุคที่กำหนดค่าไว้ (การโพสต์การโทรในโครงการความผิดพลาด)

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!

กำลังดึงผู้ใช้ที่สมัครรับข้อมูล

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

ในฟังก์ชัน handleStockValueTrigger ด้านบน เราจะดึงข้อมูลผู้ใช้ที่สมัครรับข้อมูลโดยใช้ฟังก์ชัน getSubscribedUsers จากนั้น เราจะส่งการแจ้งเตือนทางเว็บไปยังผู้ใช้เหล่านี้แต่ละราย ฟังก์ชัน sendWebpush ใช้สำหรับส่งการแจ้งเตือน เราจะมาดูการใช้งานเว็บพุชในอีกสักครู่

ฟังก์ชัน getSubscribedUsers ใช้แบบสอบถาม:

 query getSubscribedUsers($symbol: String, $triggerValue: numeric) { events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) { user_id user_subscription { subscription } } }

แบบสอบถามนี้ใช้สัญลักษณ์หุ้นและค่าและดึงรายละเอียดผู้ใช้รวมทั้ง user-id และ user_subscription ที่ตรงกับเงื่อนไขเหล่านี้:

  • symbol เท่ากับที่ถูกส่งผ่านในส่วนข้อมูล
  • trigger_type เท่ากับ event
  • trigger_value มากกว่าหรือเท่ากับค่าที่ส่งไปยังฟังก์ชันนี้ ( close ในกรณีนี้)

เมื่อเราได้รับรายชื่อผู้ใช้แล้ว สิ่งเดียวที่เหลือคือการส่งการแจ้งเตือนทางเว็บไปยังพวกเขา! มาทำกันทันที!

ส่งการแจ้งเตือนทางเว็บไปยังผู้ใช้ที่สมัครรับข้อมูล

ก่อนอื่นเราต้องรับคีย์ VAPID แบบสาธารณะและส่วนตัวเพื่อส่งการแจ้งเตือนทางเว็บ โปรดเก็บคีย์เหล่านี้ในไฟล์ .env และตั้งค่ารายละเอียดเหล่านี้ใน index.js เป็น:

 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 ใช้สำหรับส่งการพุชทางเว็บบนปลายทางการสมัครรับข้อมูลที่ระบุเป็นพารามิเตอร์แรก

นั่นคือทั้งหมดที่จำเป็นในการส่งการแจ้งเตือนทางเว็บไปยังผู้ใช้ที่สมัครรับข้อมูลได้สำเร็จ นี่คือรหัสที่สมบูรณ์ที่กำหนดไว้ใน index.js :

 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"); });

มาทดสอบโฟลว์นี้โดยสมัครเป็นสมาชิกสต็อคที่มีค่าบางอย่างแล้วแทรกค่านั้นในตารางด้วยตนเอง (สำหรับการทดสอบ)!

ฉันสมัครรับข้อมูล AMZN โดยมีค่าเป็น 2000 จากนั้นจึงแทรกจุดข้อมูลลงในตารางด้วยค่านี้ นี่คือวิธีที่แอพแจ้งหุ้นแจ้งฉันทันทีหลังจากการแทรก:

การแทรกแถวในตาราง stock_data สำหรับการทดสอบ
การแทรกแถวในตาราง stock_data สำหรับการทดสอบ (ตัวอย่างขนาดใหญ่)

ประณีต! คุณยังสามารถตรวจสอบบันทึกการเรียกใช้กิจกรรมได้ที่นี่:

บันทึกเหตุการณ์
บันทึกเหตุการณ์. (ตัวอย่างขนาดใหญ่)

เว็บฮุคกำลังทำงานตามที่คาดไว้! เราพร้อมแล้วสำหรับทริกเกอร์เหตุการณ์ในขณะนี้!

ทริกเกอร์ตามกำหนดเวลา/Cron

เราสามารถบรรลุทริกเกอร์ตามเวลาเพื่อแจ้งผู้ใช้ที่สมัครรับข้อมูลทุกชั่วโมงโดยใช้ทริกเกอร์เหตุการณ์ Cron ดังนี้:

การตั้งค่าทริกเกอร์ Cron/Scheduled
การตั้งค่าทริกเกอร์ Cron/Scheduled (ตัวอย่างขนาดใหญ่)

เราสามารถใช้ URL ของเว็บฮุคเดียวกันและจัดการผู้ใช้ที่สมัครรับข้อมูลตามประเภทเหตุการณ์ทริกเกอร์เป็น stock_price_time_based_trigger การใช้งานคล้ายกับทริกเกอร์ตามเหตุการณ์

บทสรุป

ในบทความนี้ เราได้สร้างแอปพลิเคชั่นแจ้งราคาหุ้น เราได้เรียนรู้วิธีดึงราคาโดยใช้ Alpha Vantage API และจัดเก็บจุดข้อมูลในฐานข้อมูล Postgres ที่ได้รับการสนับสนุนจาก Hasura นอกจากนี้เรายังได้เรียนรู้วิธีตั้งค่าเอ็นจิ้น Hasura GraphQL และสร้างทริกเกอร์ตามเหตุการณ์และตามกำหนดเวลา เราสร้างโครงการผิดพลาดในการส่งการแจ้งเตือนทางเว็บไปยังผู้ใช้ที่สมัครรับข้อมูล