在我们团队的新项目中,我们为新产品选择了前端框架VUE,后端是用PHP编写的,并且已经成功运行了17年。当代码开始增长时,我不得不考虑简化与服务器的数据交换,这将在后面讨论。关于后端
该项目足够大且功能非常混乱,因此,用DDD编写的代码具有某些数据结构,对于整个项目中的某些通用性而言,它们既复杂又庞大。关于前端
4个月 在前端开发中,我们使用JSON作为服务器的响应,我们以方便我们的格式映射到State Vuex。但是,要返回服务器,我们需要朝相反的方向进行转换,以便服务器可以读取和映射其DTO对象(这看起来很奇怪,但应该如此:)问题
似乎什么都没有,他们与之共事,国家成长为大物体。他们开始分解成甚至更小的模块,每个模块都有自己的状态,突变等。...在经理提出新任务之后,API开始发生变化,管理所有这些模块变得越来越困难,然后将其映射错误,然后更改字段...在这里,我们开始考虑服务器和前端的通用数据结构以消除解析,映射等错误。经过一番搜索,我们得出了两种选择:- 协议缓冲区
- 服务器端JS DTO会自动生成前端,并在这些DTO中进行进一步的JSON处理。
试过笔后,习惯使用Google的Protobuf。这就是为什么:- 已经有一个功能可以为许多平台(包括PHP和JS)编译所描述的结构。
- 有一个用于生成结构的文档生成器.proto
- 您可以轻松地为结构添加一些版本控制。
- 在PHP和JS中重构时,都有助于对象的搜索。
- 如有必要,还可以使用其他芯片,例如gRPC等。
别说话了,让我们看看一切
我不会描述它在PHP方面的外观,那里的内容几乎相同,对象也相同。我将向您展示Node.js上的简单客户端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才需要。接下来,根据结构下载代码生成器。生成命令在JS下运行。./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文件的形式。
- 到处都在与特定实体进行工作,而无需对其进行修改等(并且我们都知道前端喜欢gag :))
- 可通过Web套接字交换此协议的非常方便的操作。
当然有一个小的减号,这是序列化和反序列化的速度,这里是一个示例。我用lorem ipsum进行了10段,考虑到填充的对象Price,Product,结果得出5.5kb的数据。我将数据驱动在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
谢谢大家的关注:)