Protobuf atau protokol komunikasi front-end JSON terstruktur?

gambar

Dalam proyek baru di tim kami, kami memilih kerangka kerja frontend VUE untuk produk baru, backend ditulis dalam PHP, dan telah bekerja dengan sukses selama 17 tahun sekarang.

Ketika kode mulai tumbuh, saya harus berpikir tentang menyederhanakan pertukaran data dengan server, yang akan saya bicarakan.

Tentang backend


Proyek ini cukup besar, dan fungsinya sangat membingungkan, oleh karena itu, kode yang ditulis dalam DDD memiliki struktur data tertentu, mereka kompleks dan produktif untuk beberapa universalitas dalam proyek secara keseluruhan.

Tentang frontend


4 bulan pengembangan bagian depan, kami menggunakan JSON sebagai respons dari server, diunggah ke State Vuex dalam format yang nyaman bagi kami. Tetapi untuk kembali ke server, kami harus mengonversi dalam arah yang berlawanan, sehingga server dapat membaca dan memetakan objek DTO-nya (mungkin tampak aneh, tetapi seharusnya begitu :))

Masalah


Tampaknya tidak ada apa-apa, mereka bekerja dengan apa yang ada, negara tumbuh menjadi benda besar. Mereka mulai memecah menjadi modul yang lebih kecil, masing-masing memiliki negara sendiri, mutasi, dll ... API mulai berubah setelah tugas baru dari manajer, dan itu menjadi semakin sulit untuk mengelola semua ini, kemudian dipetakan salah, kemudian bidang berubah ...

Dan di sini kita berada mulai berpikir tentang struktur data universal di server dan depan untuk menghilangkan kesalahan dalam parsing, pemetaan, dll.

Setelah beberapa pencarian, kami menemukan dua opsi:

  1. buffer protokol
  2. Pembuatan server JS DTO sisi-server untuk front, dengan pemrosesan JSON lebih lanjut dalam DTO ini.

Setelah mencoba pena, sudah biasa menggunakan Protobuf dari google.

Dan itulah kenapa:

  1. Sudah ada fungsional yang mengkompilasi struktur yang dijelaskan untuk banyak platform, termasuk untuk PHP dan untuk JS.
  2. Ada generator dokumentasi untuk struktur yang dibuat .proto
  3. Anda dapat dengan mudah mengacaukan beberapa versi untuk struktur.
  4. Pencarian objek difasilitasi saat refactoring dalam PHP dan JS.
  5. Dan chip lain seperti gRPC, dll. Jika perlu.

Berhenti bicara, mari kita lihat bagaimana tampilannya


Saya tidak akan menjelaskan tampilannya di sisi PHP, semuanya hampir sama di sana, objeknya sama.

Saya akan menunjukkan kepada Anda contoh klien JS sederhana dan server mini di Node.js.

Pertama, kami menggambarkan struktur data yang kami butuhkan. Doca .

produk.proto

syntax = "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;
}

harga

syntax = "proto3";
package api;

message Price {
    float value = 1;
    uint32 tax = 2;
}

service.proto

syntax = "proto3";

package api;

import "product.proto";

service ApiService {
    rpc getById (Product.Id) returns (Product);
}

Saya akan menjelaskan sedikit tentang layanan ini, mengapa itu diperlukan, jika bahkan tidak digunakan. Layanan ini dijelaskan hanya demi dokumentasi dalam kasus kami, apa yang diterimanya dan apa yang diberikannya, sehingga kami dapat mengganti objek yang diperlukan. Ini hanya diperlukan untuk gRPC.

Selanjutnya, generator kode diunduh berdasarkan struktur.

Dan perintah generasi berjalan di bawah 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

Lebih detail di dok .

Setelah generasi, 3 file JS muncul, di mana semuanya direduksi menjadi objek, dengan fungsionalitas serialisasi ke buffer dan deserialization dari buffer.

price_pb.js
product_pb.js
service_pb.js

Selanjutnya kita menggambarkan kode JS.

import { Product } from '../proto/product_pb';

//         Product.Id
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();

//     Product,   ,    , 
//      .
const data = Product.deserializeBinary(result);
console.log(data.toObject());

Pada prinsipnya, klien siap.

Kami Ekspres di server

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) {
  //     Product.Id,   
  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);

  //       Product
  res.send(Buffer.from(product.serializeBinary()));
});

app.listen(3008, function () {
  console.log('Example app listening on port 3008!');
});

Apa yang kita miliki secara total


  1. Satu titik kebenaran dalam bentuk objek yang dihasilkan berdasarkan pada struktur yang digambarkan satu kali untuk banyak platform.
  2. Tidak ada kebingungan, ada dokumentasi yang jelas baik dalam bentuk HTML yang dihasilkan secara otomatis dan hanya melihat file .proto.
  3. Di mana-mana, pekerjaan sedang dilakukan dengan entitas tertentu, tanpa modifikasi mereka, dll. (Dan kita semua tahu bahwa frontend suka muntah :))
  4. Operasi protokol ini sangat nyaman dipertukarkan melalui soket web.

Tentu saja ada minus kecil, ini adalah kecepatan serialisasi dan deserialisasi, di sini adalah contohnya.

Saya mengambil lorem ipsum selama 10 paragraf, ternyata data 5.5kb, dengan mempertimbangkan objek yang diisi Harga, Produk. Dan saya mengendarai data di Protobuf dan JSON (semua sama, hanya mengisi skema JSON, bukan objek 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

Terima kasih atas perhatiannya :)

All Articles