منذ بضع سنوات ، حاولت تحديد الإطارات والمنتجات والخدمات التي تسمح للتكنولوجيون بالتركيز على توسيع قيمة ممتلكاتهم الفكرية. وهذه تبدو مجرد رحلة رائعة لي ، مليئة بالفرص المنفصلة للتعلم.
أنا مهندس قد تساءل مؤخرًا إن كان هناك حالة يمكنني فيها العثور على فائدة ثانوية لمفهوم موجود من قبل الذي تحدثت عنه من قبل. بمعنى أي هل يمكنني تحديد فائدة أخرى ذات نivel تأثير مماثل للحل الأصلي الذي تم تعرفه مسبقًا؟
في هذا المقال أردت أن أتجول أعمق في GraphQL لمعرفة ما يمكنني إيجاده.
في مقالي “عندما يحاول الوقت أن يؤمن بالروستا” تحدثت عن كيف يوجد أحد الحالات العالمية الحقيقية التي يتم إختيار GraphQL بدلًا عن خدمة RESTful. وتوجهنا من خلاله كيفية بناء وتوزيع API GraphQL بواسطة Apollo Server.
في هذه المقالة التابعة لهذا المقال أخطط لتعزيز معرفتي بGraphQL عن طريق الاشتراكات للحصول على البيانات الحالية. سنقوم أيضًا ببناء خدمة WebSocket لتستخدم الاشتراكات.
تلخيص: حالة استخدام Customer 360
كان مقالي السابق يركز حول حالة Customer 360 حيث يمتلك المستهلكين لمخزني خيالي تلك المجموعات التالية من البيانات:
- معلومات الزبون
- معلومات العنوان
- طرق الاتصال
- صفات الكروت.
إن النقطة الأكبر التي يحظى بها باستخدام GraphQL هي أن طلب واحد لـGraphQL يستطيع جلب جميع البيانات الضرورية لرمز العميل (الهوية الفريدة).
type Query {
addresses: [Address]
address(customer_token: String): Address
contacts: [Contact]
contact(customer_token: String): Contact
customers: [Customer]
customer(token: String): Customer
credits: [Credit]
credit(customer_token: String): Credit
}
إذا كان يتم استخدام الأسلوب RESTful لاسترجاع عرض العميل الواحد (360)، ستتطلب طلبات متعددة وردودها ليتم ترصيصها معا. يعطينا GraphQL حلولًا تعمل بشكل أفضل.
أهداف ترفيه الطبقة
لا يمكن للشخص أن يرفع مستوىه في أي جانب من الحياة دون أن يحقق الأهداف الجديدة. بالنسبة لأهدافي هنا، هذا يعني:
- فهم وتنفيذ مقدمة الإشتراكات
subscriptions
داخل GraphQL - استخدام تنفيذ WebSocket لاستهلاك إشتراك GraphQL
فكرة استخدام الإشتراكات عوضاً عن الاستعلامات والتغييرات في GraphQL هي الطريقة المفضلة عندما يتم الوفاء بالشروط التالية:
- تغييرات صغيرة وتدريجية في كائنات كبيرة
- تحديثات بسرعة منخفضة وفي الوقت الحقيقي (مثل تطبيق المحادثة)
هذا مهم لأن تنفيذ الإشتراكات داخل GraphQL ليس مسألة سهلة. ليس فقط سيتوجب على الخادم الأساسي تحديثه، ولكن سيتوجب على التطبيق الاستهلاكي بعض التصميم التجديدي أيضًا.
ولحسن الحظ، الحالة التي نسعى فيها مع مثالنا للعميل 360 تتماشى جيدًا مع الإشتراكات. وسنقوم بتنفيذ طريقة WebSocket لاستغلال تلك الإشتراكات.
كما في الماضي، سأستمر في استخدام Apollo في المستقبل.
رفع الطبقة مع الإشتراكات Creds
أولاً، يحتاج للتثبيت للمكتبات المطلوبة لدعم الاشتراكات مع مخزني الGraphQL Apollo:
npm install ws
npm install graphql-ws @graphql-tools/schema
npm install graphql-subscriptions
بعد تثبيت المكتبات المطلوبة، تركزت على تحديث index.ts
من مجموعتي الأصلية لتوسيع معيار typedefs
بما يلي:
type Subscription {
creditUpdated: Credit
}
أيضًا أنشأت معيارًا لتوطين معاملة جديدة لPubSub
وأنشأت مشاهدة تجريدية التي سنستخدمها في المستقبل:
const pubsub = new PubSub();
pubsub.publish('CREDIT_BALANCE_UPDATED', {
creditUpdated: {
}
});
قمت بتنظيف المعالجين الحاليين وأضفت معيارًا جديدًا Subscription
لهذه الحالة الجديدة:
const resolvers = {
Query: {
addresses: () => addresses,
address: (parent, args) => {
const customer_token = args.customer_token;
return addresses.find(address => address.customer_token === customer_token);
},
contacts: () => contacts,
contact: (parent, args) => {
const customer_token = args.customer_token;
return contacts.find(contact => contact.customer_token === customer_token);
},
customers: () => customers,
customer: (parent, args) => {
const token = args.token;
return customers.find(customer => customer.token === token);
},
credits: () => credits,
credit: (parent, args) => {
const customer_token = args.customer_token;
return credits.find(credit => credit.customer_token === customer_token);
}
},
Subscription: {
creditUpdated: {
subscribe: () => pubsub.asyncIterator(['CREDIT_BALANCE_UPDATED']),
}
}
};
ومن ثم قمت بتعديل تكوين المخزن وقدمت تصميم الاشتراك:
const app = express();
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql'
});
const schema = makeExecutableSchema({ typeDefs, resolvers });
const serverCleanup = useServer({ schema }, wsServer);
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
serverCleanup.dispose();
}
};
}
}
],
});
await server.start();
app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
context: async () => ({ pubsub })
}));
const PORT = Number.parseInt(process.env.PORT) || 4000;
httpServer.listen(PORT, () => {
console.log(`Server is now running on http://localhost:${PORT}/graphql`);
console.log(`Subscription is now running on ws://localhost:${PORT}/graphql`);
});
لمحاكاة تحديثات متقدمة من قبل المستخدمين، أنشأت التالي وظيفة تزيد من رصيد الائتمان بـ $50 كل خمس ثواني بينما يعمل الخدمة. وبمجرد أن يصل رصيد الائتمان (أو يتجاوزه) حد الائتمان المئوي لـ $10,000، سأعيد تعيين رصيده إلى $2,500 مجددًا، محاكاة مدفوع رصيد الائتمان.
function incrementCreditBalance() {
if (credits[0].balance >= credits[0].credit_limit) {
credits[0].balance = 0.00;
console.log(`Credit balance reset to ${credits[0].balance}`);
} else {
credits[0].balance += 50.00;
console.log(`Credit balance updated to ${credits[0].balance}`);
}
pubsub.publish('CREDIT_BALANCE_UPDATED', { creditUpdated: credits[0] });
setTimeout(incrementCreditBalance, 5000);
}
incrementCreditBalance();
index.ts
الكامل يمكن العثور عليه هنا.
التنفيذ في Heroku
بعد أن تأسيس الخدمة، وقد حان وقت تنفيذ الخدمة حتى يتم التفاعل معها. ولأن الHeroku عمل بشكل جيد من قبل (ويسهل علي الاستخدام بواسطتي)، دعونا نبقى بهذا المنهج.
للبدء، كان علي القيام بالأوامر التالية لقارب الHeroku:
$ heroku login
$ heroku create jvc-graphql-server-sub
Creating jvc-graphql-server-sub... done
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-sub.git
الأمر أيضًا قام بتضمين المخزن المستخدم من قبل Heroku كمستخدم بعيد:
$ git remote
heroku
origin
وكما ذكرت في مقالتي السابقة، يتم تعطيل Apollo Server للApollo Explorer في البيئات الإنتاجية. ولكي نحافظ على Apollo Explorer متاح لأغراضنا، كان عليّ تعيين متغير البيئة NODE_ENV
إلى development. أنا قمت بذلك باستخدام أمر التحكم السطحي التالي:
$ heroku config:set NODE_ENV=development
Setting NODE_ENV and restarting jvc-graphql-server-sub... done, v3
NODE_ENV: development
كنت جاهزًا لتنفيذ برنامجي إلى Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku
نظرة سريعة إلى لوحة المدير لHeroku أظهرت لي أن يعمل Apollo Server بدون أي مشاكل:
في قسم Settings، وجدت الرابط العنواني لتطبيق Heroku لهذه النسخة الخاصة من الخدمة:
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/
- توجه: سيتوقف هذا الرابط عن الخدمة بمجرد نشر هذا المقال.
حتى الآن، يمكنني أن أإضافة graphql
إلى هذا الرابط لتشغيل Apollo Server Studio. هذا سمح لي برؤية الاشتراكات تعمل كما يتوجب.
لاحظ ردود الاشتراكات في الجانب الأيمن من الشاشة.
تعزيز مهارات الWebSocket
يمكننا استخدام الدعم للWebSocket وقدرات Heroku لخلق تطبيق يستخدم الاشتراك الذي أنشأناه.
في حالتي، أنشأت index.js مع المحتويات التالية. بشكل بسيط، هذا يخلق عميل WebSocket ويقوم أيضًا بإنشاء خدمة HTTP تمثيلية التي يمكنني استخدامها لتحقيق التحقق بأن العميل يجري.
import { createClient } from "graphql-ws";
import { WebSocket } from "ws";
import http from "http";
// توليد خدمة HTTP تمثيلية للاتصال ب$PORT Heroku
const PORT = process.env.PORT || 3000;
http.createServer((req, res) => res.end('Server is running')).listen(PORT, () => {
console.log(`HTTP server running on port ${PORT}`);
});
const host_url = process.env.GRAPHQL_SUBSCRIPTION_HOST || 'ws://localhost:4000/graphql';
const client = createClient({
url: host_url,
webSocketImpl: WebSocket
});
const query = `subscription {
creditUpdated {
token
customer_token
credit_limit
balance
credit_score
}
}`;
function handleCreditUpdated(data) {
console.log('Received credit update:', data);
}
// إشتراك في الاشتراك creditUpdated
client.subscribe(
{
query,
},
{
next: (data) => handleCreditUpdated(data.data.creditUpdated),
error: (err) => console.error('Subscription error:', err),
complete: () => console.log('Subscription complete'),
}
);
يمكنك العثور على ملف الindex.js
بالكامل هنا.
يمكننا تنفيذ تطبيق Node.js بسيط هذا في Heroku أيضًا، ونحن نتأكد من 设置GRAPHQL_SUBSCRIPTION_HOST
المتغير البيئي للURL التي استخدمناه من قبل للتطبيق في Heroku.
أيضًا أنشأت ملف الProcfile
التالي لتخبر Heroku كيف يمكن بدء تطبيقي:
web: node src/index.js
من ثم، أنشأت تطبيق جديد في Heroku:
$ heroku create jvc-websocket-example
Creating jvc-websocket-example... done
https://jvc-websocket-example-62824c0b1df4.herokuapp.com/ | https://git.heroku.com/jvc-websocket-example.git
ومن ثم، أنا قمت ب设置GRAPHQL_SUBSCRIPTION_HOST
المتغير البيئي للإشارة إلى محطة الخدمة التي تشتغل بالآن
$ heroku --app jvc-websocket-example \
config:set \
GRAPHQL_SUBSCRIPTION_HOST=ws://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/graphql
في هذه المرحلة نحن جاهزون للتنفيذ من خلال Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku
بمجرد بدء مستخدم الWebSocket، يمكننا رؤية حالته في لوحة المدير لHeroku:
من خلال مشاهدة السجلات داخل لوحة المدير لHeroku للمجال jvc-websocket-example
يمكننا رؤية تحديثات متعددة لخصائص balance
للخدمة jvc-graphql-server-sub
. في عرضي، أستطعت حتى تمكين ملاحظة الحالة التي أن الرصيد قد يختفي إلى صفر، محاكاة أن تم عملية الدفع.
في الطرفال، يمكننا متابعة تلك السجلات مع أوامر التحكم الشخصي (CLI) بأمر heroku logs.
2024-08-28T12:14:48.463846+00:00 app[web.1]: Received credit update: {
2024-08-28T12:14:48.463874+00:00 app[web.1]: token: 'credit-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]: customer_token: 'customer-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]: credit_limit: 10000,
2024-08-28T12:14:48.463875+00:00 app[web.1]: balance: 9950,
2024-08-28T12:14:48.463876+00:00 app[web.1]: credit_score: 750
2024-08-28T12:14:48.463876+00:00 app[web.1]: }
ليس هذا مجرد خدمة GraphQL مع تطبيقة أشعة الاشتراك، ولكن لدينا أيضًا مستخدم WebSocket الذي يستهلك تلك التحديثات.
ختام
قد تتذكر قوانين المهمة الشخصية الخاصة بي، التي أعتقد أنها تنطبق على أي مهني في التكنلوجيا الإلكترونية:
“أركز وقتك في تقديم الميزات / الوظائف التي تمد قيمة ممتلكاتك الفكرية. تسخر من الأساسات والمنتجات والخدمات لكل شيء آخر.”
— J. Vester
في هذه الغوص عميق في مشاهد الاشتراكات في GraphQL، قمنا بتنفيذ استخدام تحديثات من مخزن Apollo يعمل على Heroku باستخدام خدمة أخرى أيضًا تعمل على Heroku — تطبيق Node.js يستخدم الWebSockets. ومن خلال استخدام الاشتراكات الخفيفة من البلدان، تمت تجنب إرسال الأسئلة للبيانات التي لا تتغير وببساطة تسجل لتحصل على تحديثات رصيد الائتمان كما يحدث.
في المقدمة، ذكرت بالبحث عن مبدأ قيمة إضافي في موضوع كتبت عنه من قبل. الاشتراكات في GraphQL مثال جيد لما كنت أفكر فيه لأنها تسمح للمستهلكين بالحصول على التحديثات فوراً بدون حاجة إلى إرسال الأسئلة ضد المصادر البياناتية. هذا سيثير المستهلكين من بيانات Customer 360 للغاية، ويعلمون أنهم يمكنهم الحصول على تحديثات حية كما يحدث.
Heroku مثال آخر يستمر في الاتاحة لمبادرتي بواسطة تعليمات الاستخدام الخاصة بي وبأمور عامة من الأوامر الGit. هذا ليس فقط يعطيني وسيلة سهلة لعرض حالة استخدام الاشتراكات ولكن يمكنني أيضًا تطبيق مستهلك يستخدم الWebSockets.
إذا كان مهتم بمصادر البرمجيات لهذه المقالة، انظر إلى أعمالي في GitLab:
أشعر بالconfident عندما أقول أنني تمكنت بنجاح من رفع مستوى مهاراتي في GraphQL مع هذا الجهود. كان هذه الرحلة جديدة وتحدية لي — وأيضًا ممتعة جدًا!
أخطط للغوص في التحقق المستقبلي من المرة القادمة، والذي آمل أن يفرص لي أخر فرصة لرفع مستوى GraphQL مع Apollo Server. أبقى معنا!
أتمنى لكم يومًا رائعًا حقًا!
Source:
https://dzone.com/articles/leveling-up-my-graphql-skills-real-time-subscriptions