في المشروع الجديد في فريقنا ، اخترنا إطار الواجهة الأمامية VUE للمنتج الجديد ، والواجهة الخلفية مكتوبة بلغة PHP ، وهي تعمل بنجاح منذ 17 عامًا.عندما بدأ الكود في النمو ، كان علي أن أفكر في تبسيط تبادل البيانات مع الخادم ، والذي سأتحدث عنه.حول الخلفية
المشروع كبير بما فيه الكفاية ، والوظائف مشوشة للغاية ، لذلك ، كان الرمز المكتوب في DDD بهياكل بيانات معينة ، كانت معقدة وضخمة لبعض العالمية في المشروع ككل.حول الواجهة
4 اشهر تطوير الجبهة ، استخدمنا JSON كرد من الخادم ، قمنا بتعيينه في State Vuex بتنسيق مناسب لنا. ولكن من أجل العودة إلى الخادم ، كنا بحاجة إلى التحويل في الاتجاه المعاكس ، حتى يتمكن الخادم من قراءة كائنات DTO الخاصة به وتعيينها (قد يبدو الأمر غريبًا ، ولكن يجب أن يكون كذلك :))مشاكل
يبدو أنه لا شيء ، عملوا مع ما ، نمت الدولة إلى أشياء كبيرة. بدأوا في الانقسام إلى وحدات أصغر ، كل منها له حالاته الخاصة ، طفراته ، وما إلى ذلك ... بدأت واجهة برمجة التطبيقات في التغيير بعد المهام الجديدة من المديرين ، وأصبح الأمر أكثر صعوبة في إدارة كل هذا ، ثم تم تعيينه بشكل خاطئ ، ثم تغيرت الحقول ...وهنا نحن بدأ التفكير في هياكل البيانات العالمية على الخادم والواجهة للقضاء على الأخطاء في التحليل ، والتعيينات ، وما إلى ذلك.بعد إجراء بعض البحث ، توصلنا إلى خيارين:- المخازن المؤقتة للبروتوكول
- إنشاء JS DTO التلقائي من جانب الخادم للجهة الأمامية ، مع معالجة JSON إضافية في DTOs هذه.
بعد تجربة القلم ، كان من المعتاد استخدام Protobuf من google.ولهذا السبب:- هناك بالفعل وظيفة تجمع الهياكل الموصوفة للعديد من المنصات ، بما في ذلك PHP و JS.
- يوجد مولد توثيق للهياكل التي تم إنشاؤها. pro
- يمكنك بسهولة ربط بعض الإصدارات للهياكل.
- يتم تسهيل البحث عن الكائنات عند إعادة البناء في كل من PHP و JS.
- والرقائق الأخرى مثل gRPC ، وما إلى ذلك إذا لزم الأمر.
توقف عن الحديث ، دعنا نرى كيف يبدو كل شيء
لن أصف كيف يبدو من ناحية PHP ، فكل شيء متشابه هناك ، والأشياء متشابهة.سأعرض لك مثالاً على عميل JS بسيط وخادم صغير على Node.js.أولاً ، نحن نصف هياكل البيانات التي نحتاجها. دوكا .product.protosyntax = "proto3";
package api;
import "price.proto";
message Product {
message Id {
uint32 value = 1;
}
Id id = 1;
string name = 2;
string text = 3;
string url = 4;
Price price = 5;
}
price.protosyntax = "proto3";
package api;
message Price {
float value = 1;
uint32 tax = 2;
}
service.protosyntax = "proto3";
package api;
import "product.proto";
service ApiService {
rpc getById (Product.Id) returns (Product);
}
سأشرح قليلاً عن الخدمة ، ولماذا هناك حاجة إليها ، حتى لو لم يتم استخدامها. يتم وصف الخدمة فقط من أجل التوثيق في حالتنا ، وما تقبله وما تقدمه ، حتى نتمكن من استبدال الأشياء الضرورية. هو مطلوب فقط لـ gRPC.بعد ذلك ، يتم تنزيل منشئ التعليمات البرمجية بناءً على الهياكل.ويعمل أمر التوليد تحت شبيبة../protoc --proto_path=/Users/user/dev/habr_protobuf/public/proto --js_out=import_style=commonjs,binary:/Users/user/dev/habr_protobuf/src/proto/ /Users/user/dev/habr_protobuf/public/proto/*.proto
مزيد من التفاصيل في قفص الاتهام .بعد الجيل ، تظهر 3 ملفات JS ، حيث يتم تقليل كل شيء إلى كائنات ، مع وظيفة التسلسل إلى المخزن المؤقت وإلغاء التسلسل من المخزن المؤقت.price_pb.jsproduct_pb.jsservice_pb.jsبعد ذلك ، سنصف شفرة JS.import { Product } from '../proto/product_pb';
const instance = new Product.Id().setValue(12345);
let message = instance.serializeBinary();
let response = await fetch('http://localhost:3008/api/getById', {
method: 'POST',
body: message
});
let result = await response.arrayBuffer();
const data = Product.deserializeBinary(result);
console.log(data.toObject());
من حيث المبدأ ، العميل جاهز.نعبر عن الخادمconst express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
const Product = require('./src/proto/product_pb').Product;
const Price = require('./src/proto/price_pb').Price;
app.use (function(req, res, next) {
let data = [];
req.on('data', function(chunk) {
data.push(chunk);
});
req.on('end', function() {
if (data.length <= 0 ) return next();
data = Buffer.concat(data);
console.log('Received buffer', data);
req.raw = data;
next();
})
});
app.post('/api/getById', function (req, res) {
const prId = Product.Id.deserializeBinary(req.raw);
const id = prId.toObject().value;
const product = new Product();
product.setId(new Product.Id().setValue(id));
product.setName('Sony PSP');
product.setUrl('http://mysite.ru/product/psp/');
const price = new Price();
price.setValue(35500.00);
price.setTax(20);
product.setPrice(price);
res.send(Buffer.from(product.serializeBinary()));
});
app.listen(3008, function () {
console.log('Example app listening on port 3008!');
});
ماذا لدينا في المجموع
- نقطة واحدة من الحقيقة في شكل كائنات ولدت على أساس الهياكل التي تم وصفها مرة واحدة للعديد من المنصات.
- لا يوجد ارتباك ، فهناك وثائق واضحة في شكل HTML تم إنشاؤه تلقائيًا وعرض ملفات .proto فقط.
- في كل مكان ، يتم العمل مع كيانات محددة ، دون تعديلات ، وما إلى ذلك (ونعلم جميعًا أن الواجهة الأمامية تحب الكمامات :))
- يتم تبادل تشغيل هذا البروتوكول بشكل مريح للغاية عبر مقابس الويب.
هناك بالطبع ناقص صغير ، هذه هي سرعة التسلسل وإلغاء التسلسل ، هنا مثال.لقد أخذت lorem ipsum لـ 10 فقرات ، وتبين أن 5.5 كيلو بايت من البيانات ، مع الأخذ في الاعتبار السعر المملوء ، المنتج. وقمت بقيادة البيانات على Protobuf و JSON (كل نفس ، تم ملؤها للتو في مخططات JSON ، بدلاً من كائنات Protobuf)
Protobuf parsing
client
2.804999ms
1.8150000ms
0.744999ms
server
1.993ms
0.495ms
0.412ms
JSON
client
0.654999ms
0.770000ms
0.819999ms
server
0.441ms
0.307ms
0.242ms
شكرا لكم جميعا على اهتمامكم :)