نوع الفوضى

كما توضح الممارسة ، فإن جزءًا كبيرًا من المشكلات لا ينشأ بسبب الحلول نفسها ، ولكن بسبب كيفية حدوث التواصل بين مكونات النظام. إذا كانت هناك فوضى في الاتصال بين مكونات النظام ، فعندئذ لا تحاول كتابة المكونات الفردية جيدًا ، فسيفشل النظام ككل.


الحذر داخل الدراجة.


مشكلة أو بيان المشكلة


منذ بعض الوقت ، تم العمل على مشروع لشركة تقدم للجمهور بعض المسرات مثل CRM وأنظمة ERM ومشتقاتها. علاوة على ذلك ، أصدرت الشركة منتجًا شاملاً نوعًا ما من برنامج لتسجيل النقد إلى مركز الاتصال مع إمكانية استئجار مشغلين بمبلغ يصل إلى 200 شخص.


أنا نفسي عملت على تطبيق الواجهة الأمامية لمركز الاتصال.


من السهل تخيل أن المعلومات من جميع مكونات النظام تتدفق إلى تطبيق المشغل. وإذا أخذنا في الاعتبار حقيقة أنه ليس مشغلًا واحدًا ، ولكن أيضًا مدير ومسؤول ، فيمكنك تخيل مقدار الاتصالات والمعلومات التي يجب على التطبيق "استيعابها" وربطها ببعضها البعض.


عندما تم إطلاق المشروع بالفعل وعمل حتى بشكل مستقر تمامًا ، نشأت مشكلة شفافية النظام في كل نموه.


هذه هي النقطة. هناك العديد من المكونات وتعمل جميعها مع مصادر البيانات الخاصة بها. ولكن تمت كتابة جميع هذه المكونات تقريبًا كمنتجات قائمة بذاتها. هذا ، ليس كعنصر من عناصر النظام العام ، ولكن كقرارات منفصلة للبيع. نتيجة لذلك ، لا توجد واجهة برمجة تطبيقات (نظام) واحدة ولا توجد معايير اتصال مشتركة بينهما.


ساوضح. يرسل بعض المكون JSON ، "شخص ما" يرسل خطوطًا ذات مفتاح: القيمة في الداخل ، "شخص ما" يرسل ثنائيًا بشكل عام ويفعل ما تريد معه. ولكن ، كان على التطبيق النهائي لمركز الاتصال الحصول على كل شيء ومعالجته بطريقة ما. حسنًا والأهم من ذلك ، أنه لا يوجد رابط في النظام يمكنه إدراك أن تنسيق / بنية البيانات قد تغير. إذا أرسل أحد المكونات JSON أمس ، واليوم قرر إرسال ثنائي - لن يرى أحد هذا. سيبدأ تعطل التطبيق النهائي فقط كما هو متوقع.


سرعان ما أصبح من الواضح (بالنسبة لأولئك من حولي ، لا بالنسبة لي ، منذ أن تحدثت عن المشكلة في مرحلة التصميم) أن عدم وجود "لغة اتصال موحدة" بين المكونات يؤدي إلى مشاكل خطيرة.


أبسط الحالات هي عندما يطلب العميل تغيير مجموعة بيانات. يشطبون المهمة على الشاب الذي "يحتفظ" بعنصر العمل مع قواعد بيانات البضائع / الخدمات ، على سبيل المثال. يقوم بعمله ، وينفذ مجموعة بيانات جديدة ، وبالنسبة له ، الأحمق ، كل شيء يعمل. ولكن ، في اليوم التالي للتحديث ... أوه ... يبدأ التطبيق في مركز الاتصال فجأة في العمل وليس كما يتوقعون منه.


ربما كنت بالفعل خمنت. لم يغير بطلنا مجموعة البيانات فحسب ، ولكن أيضًا بنية البيانات التي يرسلها مكونه إلى النظام. نتيجةً لذلك ، لم يعد تطبيق مركز الاتصال قادراً على العمل مع هذا المكون ، وهناك تبعيات أخرى تطير على طول السلسلة.


لقد بدأوا في التفكير فيما نريد ، في الواقع ، الخروج منه. نتيجة لذلك ، قمنا بصياغة المتطلبات التالية لحل ممكن:


أولاً وقبل كل شيء: يجب أن يتم "تمييز" أي تغيير في بنية البيانات على الفور في النظام. إذا قام شخص ما بإجراء تغييرات في مكان ما وكانت هذه التغييرات غير متوافقة مع ما يتوقعه النظام ، فيجب أن يحدث خطأ في مرحلة اختبار المكون ، والذي تم تغييره.


والثاني . يجب التحقق من أنواع البيانات ليس فقط أثناء التجميع ، ولكن أيضًا في وقت التشغيل.


الثالث . نظرًا لأن هناك عددًا كبيرًا من الأشخاص ذوي مستويات المهارة المختلفة تمامًا يعملون على المكونات ، يجب أن تكون لغة الوصف أبسط.


الرابع . أيا كان الحل ، يجب أن يكون مناسبًا قدر الإمكان للعمل به. إذا كان ذلك ممكنًا ، ينبغي على IDE إبراز أكبر قدر ممكن.


كان الفكر الأول لتنفيذ protobuf. بسيطة وقابلة للقراءة وسهلة. كتابة البيانات صارمة. يبدو أن ما أمر الطبيب. ولكن للأسف ، لم يكن كل بناء جملة protobuf بسيطًا. بالإضافة إلى ذلك ، يتطلب البروتوكول المترجم مكتبة إضافية ، ولكن لم يكن Javascript مدعومًا بواسطة protobuf وكان نتيجة عمل المجتمع. بشكل عام ، رفضوا.


ثم جاءت الفكرة لوصف البروتوكول في JSON. حسنا ، كم هو أسهل؟


حسنا ، ثم استقال. وفي هذا الصدد ، كان يمكن إكمال هذا المنشور ، لأنه بعد رحيلي لم يبدأ أي شخص آخر في التعامل مع المشكلة عن كثب.


ومع ذلك ، بالنظر إلى اثنين من المشاريع الشخصية التي ارتفعت فيها مسألة الاتصال بين المكونات مرة أخرى إلى أقصى إمكاناتها ، قررت أن أبدأ في تطبيق الفكرة بمفردي. ما سيتم مناقشته أدناه.


لذلك ، أقدم انتباهكم إلى مشروع ceres ، والذي يتضمن:


  • مولد البروتوكول
  • مزود
  • العميل
  • تنفيذ النقل

البروتوكول


كانت المهمة لجعله بحيث:


  • كان من السهل ضبط بنية الرسائل في النظام.
  • كان من السهل تحديد نوع البيانات لجميع حقول الرسائل.
  • كان من الممكن تحديد الكيانات المساعدة والرجوع إليها.
  • وبطبيعة الحال ، بحيث يتم تمييز كل هذا من قبل IDE

أعتقد أنه بطريقة طبيعية تمامًا ، وليس جافا سكريبت خالصًا ، ولكن تم اختيار Typescript كلغة يتم تحويل البروتوكول إليها. أي أن كل ما يفعله منشئ البروتوكول هو تحويل JSON إلى Typescript.


لوصف الرسائل المتوفرة في النظام ، ما عليك سوى معرفة ماهية JSON. مع ذلك ، أنا متأكد من أن لا أحد لديه أي مشاكل.


بدلاً من Hello World ، أقدم مثالًا لا يقل عن الاختراق - الدردشة.


{ "Events": { "NewMessage": { "message": "ChatMessage" }, "UsersListUpdated": { "users": "Array<User>" } }, "Requests": { "GetUsers": {}, "AddUser": { "user": "User" } }, "Responses": { "UsersList": { "users": "Array<User>" }, "AddUserResult": { "error?": "asciiString" } }, "ChatMessage": { "nickname": "asciiString", "message": "utf8String", "created": "datetime" }, "User": { "nickname": "asciiString" }, "version": "0.0.1" } 

كل شيء بسيط للغاية. لدينا بعض الأحداث NewMessage و UsersListUpdated؛ بالإضافة إلى اثنين من طلبات UsersList و AddUserResult. هناك كيانان آخران: ChatMessage و User.


كما ترون ، الوصف شفاف تمامًا ومفهوم. قليلا عن القواعد.


  • سيصبح كائن في JSON فئة في البروتوكول الذي تم إنشاؤه
  • قيمة الخاصية هي تعريف لنوع البيانات أو مرجع لفئة (كيان)
  • الكائنات المتداخلة من وجهة نظر البروتوكول الذي تم إنشاؤه ستصبح فئات "متداخلة" ، أي أن الكائنات المتداخلة سوف ترث جميع خصائص والديهم.

الآن كل ما عليك فعله هو إنشاء بروتوكول لبدء استخدامه.


 npm install ceres.protocol -g ceres.protocol -s chat.protocol.json -o chat.protocol.ts -r 

نتيجة لذلك ، حصلنا على بروتوكول تم إنشاؤه بواسطة Typescript. نحن نتواصل ونستخدم:


الصورة

لذلك ، فإن البروتوكول يعطي بالفعل شيئا للمطور:


  • يسلط IDE الضوء على ما لدينا في البروتوكول. يبرز IDE أيضًا جميع الخصائص المتوقعة.
  • Typescript ، والذي سيخبرنا بالتأكيد إذا كان هناك خطأ في أنواع البيانات. بالطبع ، يتم ذلك في مرحلة التطوير ، لكن البروتوكول نفسه سوف يقوم بالفعل بفحص أنواع البيانات وقت التشغيل وإلقاء استثناء إذا تم اكتشاف حدوث انتهاك
  • بشكل عام ، يمكنك نسيان التحقق من الصحة. سوف البروتوكول القيام بجميع الشيكات اللازمة.
  • لا يتطلب البروتوكول الذي تم إنشاؤه أي مكتبات إضافية. كل ما يحتاجه للعمل ، يحتوي بالفعل. وأنها مريحة جدا.

نعم ، قد يفاجئك حجم البروتوكول الذي تم إنشاؤه ، على أقل تقدير. ولكن ، لا تنسَ التصغير ، الذي يفسر ملف البروتوكول الذي تم إنشاؤه بشكل جيد.

الآن يمكننا "حزمة" الرسالة وإرسالها


 import * as Protocol from '../../protocol/protocol.chat'; const message: Protocol.ChatMessage = new Protocol.ChatMessage({ nickname: 'noname', message: 'Hello World!', created: new Date() }); const packet: Uint8Array = message.stringify(); // Send packet somewhere 

من المهم إجراء الحجز هنا ، فالحزمة ستكون عبارة عن مجموعة من البايتات ، وهي جيدة وصحيحة للغاية من وجهة نظر تحميل حركة المرور ، حيث إن إرسال "تكاليف" JSON نفسها ، بالطبع ، أغلى. ومع ذلك ، يحتوي البروتوكول على ميزة واحدة - في وضع التصحيح ، سيتم إنشاء JSON قابل للقراءة بحيث يمكن للمطور "النظر" في حركة المرور ومعرفة ما يحدث.


يتم ذلك مباشرة في وقت التشغيل.


 import * as Protocol from '../../protocol/protocol.chat'; const message: Protocol.ChatMessage = new Protocol.ChatMessage({ nickname: 'noname', message: 'Hello World!', created: new Date() }); // Switch to debug mode Protocol.Protocol.state.debug(true); // Now packet will be present as JSON string const packet: string = message.stringify(); // Send packet somewhere 

على الخادم (أو أي مستلم آخر) ، يمكننا بسهولة تفكيك الرسالة:


 import * as Protocol from '../../protocol/protocol.chat'; const smth = Protocol.parse(packet); if (smth instanceof Error) { // Oops. Something wrong with this packet. } if (Protocol.ChatMessage.instanceOf(smth) === true) { // This is chat message } 

يدعم البروتوكول جميع أنواع البيانات الرئيسية:


اكتبالقيموصفالحجم ، بايت
utf8StringUTF8 سلسلة مشفرةس
asciiStringسلسلة أسكيحرف واحد - 1 بايت
int8-128 إلى 1271
int16-32768 إلى 327672
int32-2147483648 إلى 21474836474
uint80 إلى 2551
uint160 إلى 655352
uint320 إلى 42949672954
float321.2x10 -38 إلى 3.4x10 384
float645.0x10 -324 إلى 1.8x10 3088
منطقية1

داخل البروتوكول ، تسمى أنواع البيانات هذه بدائية. ومع ذلك ، هناك ميزة أخرى في البروتوكول وهي أنه يسمح لك بإضافة أنواع البيانات الخاصة بك (تسمى "أنواع بيانات إضافية").


على سبيل المثال ، ربما لاحظت بالفعل أن ChatMessage يحتوي على حقل تم إنشاؤه بنوع بيانات التاريخ . على مستوى التطبيق - يتوافق هذا النوع مع التاريخ ، ويتم تخزينه (وإرساله) داخل البروتوكول على أنه uint32 .


إضافة نوعك إلى البروتوكول أمر بسيط للغاية. على سبيل المثال ، إذا أردنا الحصول على نوع بيانات البريد الإلكتروني ، فقل الرسالة التالية في البروتوكول:


 { "User": { "nickname": "asciiString", "address": "email" }, "version": "0.0.1" } 

كل ما عليك فعله هو كتابة تعريف لنوع البريد الإلكتروني.


 export const AdvancedTypes: { [key:string]: any} = { email: { // Binary type or primitive type binaryType : 'asciiString', // Initialization value. This value is used as default value init : '""', // Parse value. We should not do any extra decode operations with it parse : (value: string) => { return value; }, // Also we should not do any encoding operations with it serialize : (value: string) => { return value; }, // Typescript type tsType : 'string', // Validation function to valid value validate : (value: string) => { if (typeof value !== 'string'){ return false; } if (value.trim() === '') { // Initialization value is "''", so we allow use empty string. return true; } const validationRegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/gi; return validationRegExp.test(value); }, } }; 

هذا كل شيء. من خلال إنشاء البروتوكول ، نحصل على دعم لنوع بيانات البريد الإلكتروني الجديد. عندما نحاول إنشاء كيان بعنوان خاطئ ، نحصل على خطأ


 const user: Protocol.User = new Protocol.User({ nickname: 'Brad', email: 'not_valid_email' }); console.log(user); 

اوه ...


 Error: Cannot create class of "User" due error(s): - Property "email" has wrong value; validation was failed with value "not_valid_email". 

لذا ، فإن البروتوكول ببساطة لا يسمح بالبيانات "السيئة" في النظام.


يرجى ملاحظة أنه عند تحديد نوع بيانات جديد ، حددنا بعض الخصائص الرئيسية:


  • binaryType - إشارة إلى نوع البيانات البدائية التي يجب استخدامها لتخزين ، تشفير / فك شفرة البيانات. في هذه الحالة ، نشير إلى أن العنوان عبارة عن سلسلة ascii.
  • tsType هو مرجع لنوع Javascript ، أي كيفية تمثيل نوع البيانات في بيئة Javascript. في هذه الحالة نحن نتحدث عن سلسلة
  • تجدر الإشارة أيضًا إلى أننا بحاجة إلى تحديد نوع بيانات جديد فقط في وقت إنشاء البروتوكول. في الإخراج ، نحصل على بروتوكول تم إنشاؤه يحتوي بالفعل على نوع بيانات جديد.

يمكنك الاطلاع على معلومات مفصلة حول جميع ميزات البروتوكول هنا ceres.protocol .

المزود والعملاء


على العموم ، يمكن استخدام البروتوكول نفسه لتنظيم الاتصالات. ومع ذلك ، إذا كنا نتحدث عن المستعرض و nodejs ، فسيتم توفير الموفر والعميل.


العملاء


الخلق


لإنشاء عميل ، تحتاج إلى العميل والنقل.


تركيب


 # Install consumer (client) npm install ceres.consumer --save # Install transport npm install ceres.consumer.browser.ws --save 

الخلق


 import Transport, { ConnectionParameters } from 'ceres.consumer.browser.ws'; import Consumer from 'ceres.consumer'; // Create transport const transport:Transport = new Transport(new ConnectionParameters({ host: 'http://localhost', port: 3005, wsHost: 'ws://localhost', wsPort: 3005, })); // Create consumer const consumer: Consumer = new Consumer(transport); 

تم تصميم العميل ، وكذلك المزود ، خصيصًا للبروتوكول. وهذا هو ، وأنها سوف تعمل فقط مع البروتوكول (ceres.protocol).

أحداث


بعد إنشاء العميل ، يمكن للمطور الاشتراك في الأحداث


 import * as Protocol from '../../protocol/protocol.chat'; import Transport, { ConnectionParameters } from 'ceres.consumer.browser.ws'; import Consumer from 'ceres.consumer'; // Create transport const transport:Transport = new Transport(new ConnectionParameters({ host: 'http://localhost', port: 3005, wsHost: 'ws://localhost', wsPort: 3005, })); // Create consumer const consumer: Consumer = new Consumer(transport); // Subscribe to event consumer.subscribe(Protocol.Events.NewMessage, (message: Protocol.Events.NewMessage) => { console.log(`New message came: ${message.message}`); }).then(() => { console.log('Subscription to "NewMessage" is done'); }).catch((error: Error) => { console.log(`Fail to subscribe to "NewMessage" due error: ${error.message}`); }); 

يرجى ملاحظة أن العميل سيتصل بمعالج الأحداث فقط إذا كانت بيانات الرسالة صحيحة تمامًا. بمعنى آخر ، تطبيقنا محمي ضد البيانات غير الصحيحة وسيتم دائمًا استدعاء معالج أحداث NewMessage بمثيل Protocol.Events.NewMessage كوسيطة.


بطبيعة الحال ، يمكن للعميل إنشاء الأحداث.


 consumer.emit(new Protocol.Events.NewMessage({ message: 'This is new message' })).then(() => { console.log(`New message was sent`); }).catch((error: Error) => { console.log(`Fail to send message due error: ${error.message}`); }); 

لاحظ أننا لا نحدد أسماء الأحداث في أي مكان ، فنحن ببساطة نستخدم إما رابطًا للفئة من البروتوكول ، أو نمرر نسخة منه.


يمكننا أيضًا إرسال رسالة إلى مجموعة محدودة من المستلمين عن طريق تحديد كائن بسيط من النوع { [key: string]: string } كوسيطة ثانية. داخل سيريس ، هذا الكائن يسمى الاستعلام .


 consumer.emit( new Protocol.Events.NewMessage({ message: 'This is new message' }), { location: "UK" } ).then(() => { console.log(`New message was sent`); }).catch((error: Error) => { console.log(`Fail to send message due error: ${error.message}`); }); 

وبالتالي ، من خلال الإشارة إلى { location: "UK" } ، يمكننا التأكد من أن هذه الرسالة هي فقط العملاء الذين حددوا وضعهم كمملكة المتحدة.


لربط العميل نفسه مع استعلام معين ، تحتاج فقط إلى استدعاء طريقة المرجع :


 consumer.ref({ id: '12345678', location: 'UK' }).then(() => { console.log(`Client successfully bound with query`); }); 

بعد توصيل العميل بالاستعلام ، تتوفر لديه الفرصة لتلقي الرسائل "الشخصية" أو "الجماعية".


طلبات


يمكننا أيضا تقديم الطلبات


 consumer.request( new Protocol.Requests.GetUsers(), // Request Protocol.Responses.UsersList // Expected response ).then((response: Protocol.Responses.UsersList) => { console.log(`Available users: ${response.users}`); }).catch((error: Error) => { console.log(`Fail to get users list due error: ${error.message}`); }); 

تجدر الإشارة إلى أننا ، كحجة ثانية ، نحدد النتيجة المتوقعة ( Protocol.Responses.UsersList ) ، مما يعني أن طلبنا سوف يكتمل بنجاح فقط إذا كانت الاستجابة هي مثال من UserList ، في جميع الحالات الأخرى سوف "نقع" في قبض مرة أخرى ، هذا يضمن لنا من معالجة البيانات غير صحيحة.


يمكن للعميل نفسه أيضًا التحدث إلى أولئك الذين يمكنهم معالجة الطلبات. للقيام بذلك ، تحتاج فقط إلى "تعريف" نفسك على أنه "مسؤول" عن الطلب.


 function processRequestGetUsers(request: Protocol.Requests.GetUsers, callback: (error: Error | null, results : any ) => any) { // Get user list somehow const users: Protocol.User[] = []; // Prepare response const response = new Protocol.Responses.UsersList({ users: users }); // Send response callback(null, response); // Or send error // callback(new Error(`Something is wrong`)) }; consumer.listenRequest(Protocol.Requests.GetUsers, processRequestGetUsers, { location: "UK" }).then(() => { console.log(`Consumer starts listen request "GetUsers"`); }); 

ملاحظة ، اختياريًا ، كوسيطة ثالثة ، يمكننا تحديد كائن استعلام يمكن استخدامه لتحديد العميل. وبالتالي ، إذا أرسل شخص ما طلب استعلام ، قل ، { location: "RU" } ، فلن يتلقى عميلنا هذا الطلب ، لأن استعلامه { location: "UK" } .


يمكن أن يتضمن الاستعلام عددًا غير محدود من العقارات. على سبيل المثال ، يمكنك تحديد ما يلي


 { location: "UK", type: "managers" } 

ثم ، بالإضافة إلى مطابقة استعلام كاملة ، سنقوم أيضًا بمعالجة الاستعلامات التالية بنجاح:


 { location: "UK" } 

او


 { type: "managers" } 

مزود


الخلق


لإنشاء موفر (وكذلك لإنشاء عميل) ، فأنت بحاجة إلى الموفر والنقل.


تركيب


 # Install provider npm install ceres.provider --save # Install transport npm install ceres.provider.node.ws --save 

الخلق


 import Transport, { ConnectionParameters } from 'ceres.provider.node.ws'; import Provider from 'ceres.provider'; // Create transport const transport:Transport = new Transport(new ConnectionParameters({ port: 3005 })); // Create provider const provider: Provider = new Provider(transport); 

من لحظة إنشاء الموفر ، يمكنه قبول الاتصالات من العملاء.


أحداث


بالإضافة إلى العميل ، يمكن للمزود "الاستماع" إلى الرسائل وإنشاءها.


الاستماع


 // Subscribe to event provider.subscribe(Protocol.Events.NewMessage, (message: Protocol.Events.NewMessage) => { console.log(`New message came: ${message.message}`); }); 

توليد


 provider.emit(new Protocol.Events.NewMessage({ message: 'This message from provider' })); 

طلبات


بطبيعة الحال ، يمكن للمزود (ويجب عليه) "الاستماع" للطلبات


 function processRequestGetUsers(request: Protocol.Requests.GetUsers, clientID: string, callback: (error: Error | null, results : any ) => any) { console.log(`Request from client ${clientId} was gotten.`); // Get user list somehow const users: Protocol.User[] = []; // Prepare response const response = new Protocol.Responses.UsersList({ users: users }); // Send response callback(null, response); // Or send error // callback(new Error(`Something is wrong`)) }; provider.listenRequest(Protocol.Requests.GetUsers, processRequestGetUsers).then(() => { console.log(`Consumer starts listen request "GetUsers"`); }); 

هناك اختلاف واحد فقط عن العميل ، حيث يتلقى الموفر بالإضافة إلى نص الطلب عميلًا فريدًا ، يتم تعيينه تلقائيًا لجميع العملاء المتصلين.


مثال


في الحقيقة ، لا أريد حقًا أن أتحمل لك مقتطفات من الوثائق ، أنا متأكد من أنه سيكون من الأسهل والأكثر إثارة للاهتمام بالنسبة لك أن ترى مجرد رمز قصير.


يمكنك بسهولة تثبيت مثال الدردشة عن طريق تنزيل المصادر والقيام ببعض الإجراءات البسيطة


تثبيت العميل وإطلاق


 cd chat/client npm install npm start 

سيكون العميل متاحًا على الموقع http: // localhost: 3000 . افتح على الفور بضع علامات تبويب مع العميل لرؤية "التواصل".


تركيب وتشغيل الموفر (الخادم)


 cd chat/server npm install ts-node ./server.ts 

أنا متأكد من أنك على دراية بحزمة ts-node ، لكن إذا لم يكن كذلك ، فهي تتيح لك تشغيل ملفات TS. إذا كنت لا تريد التثبيت ، فقم فقط بترجمة الخادم ، ثم قم بتشغيل ملف JS.


 cd chat/server npm run build node ./build/server/server.js 

ماذا؟ مرة اخرى؟


توقع أسئلة حول ماذا بحق الجحيم لابتكار دراجة أخرى ، لأن هناك الكثير من الحلول التي تم إعدادها بالفعل ، من protobuf إلى joynr المتشددين من BMW ، لا يمكنني إلا أن أقول إنها كانت مثيرة للاهتمام بالنسبة لي. تم تنفيذ المشروع بالكامل بمبادرة شخصية فقط دون أي دعم ، في وقت فراغي من العمل.


هذا هو السبب في تعليقاتك ذات قيمة خاصة بالنسبة لي. في محاولة لتحفيزك بطريقة أو بأخرى ، يمكنني أن أعدكم أنه مقابل كل نجم على جيثب ، سأضرب الهامستر (والذي ، بعبارة ملطفة ، لا أحب). من أجل الشوكة ، آه ، سأخدش بوسيكو ... brrrr.


الهامستر ليس لي ، الهامستر الابن .


بالإضافة إلى ذلك ، في غضون أسبوعين ، سيذهب المشروع للاختبار لزملائي السابقين (الذي ذكرته في بداية المنشور والذين كانوا مهتمين بما كانت عليه نسخة ألفا). الهدف هو تصحيح الأخطاء وتشغيلها على مكونات متعددة. آمل حقًا أن تنجح.


الروابط والحزم


يستضيف المشروع مستودعين


  • مصادر ceres : ceres.provider ، ceres.consumer وجميع وسائل النقل المتاحة اليوم.
  • مصادر مولد بروتوكول ceres.protocol

NPM التالية الحزم المتاحة



جيد وخفيف.

Source: https://habr.com/ru/post/ar442380/


All Articles