Protobuf
เว็บไซต์ทางการ: Protocol Buffers | Google Developers
บทนำ
บทช่วยสอนทางการ: Protocol Buffer Basics: Go | Protocol Buffers | Google Developers
Protocol Buffers เป็นกลไกการซีเรียลไลเซชันข้อมูลที่มีโครงสร้างแบบ可扩展 ที่ไม่ขึ้นกับภาษาและไม่ขึ้นกับโปรโตคอล ซึ่งเปิดแหล่งโดยกูเกิลในปี 2008 ใช้ในการแพ็กเก็ตและอันแพ็กเก็ตได้รวดเร็ว มักใช้ในด้านการสื่อสาร RPC สามารถกำหนดวิธีการจัดโครงสร้างข้อมูล จากนั้นสามารถใช้ซอร์สโค้ดที่สร้างขึ้นเป็นพิเศษเพื่อเขียนและอ่านข้อมูลที่มีโครงสร้างจากสตรีมข้อมูลต่างๆ ได้อย่างง่ายดาย และใช้กับภาษาต่างๆ เกี่ยวกับ Protocol Buffers ต่อไปนี้จะเรียกว่า protobuf
protobuf ค่อนข้างเป็นที่นิยม โดยเฉพาะในส่วนของ go gRPC ใช้เป็นกลไกการซีเรียลไลเซชันสำหรับการส่งโปรโตคอล
ไวยากรณ์
ก่อนอื่นจากตัวอย่างหนึ่งเพื่อดูว่าไฟล์ protobuf มีลักษณะอย่างไร โดยรวมแล้วไวยากรณ์ของมันง่ายมาก ใช้เวลาสิบกว่านาทีก็สามารถใช้งานได้ ด้านล่างนี้เป็นตัวอย่างของไฟล์ชื่อ search.proto นามสกุลไฟล์ของ protobuf คือ .proto
syntax = "proto3";
message SearchRequest {
string query = 1;
string number = 2;
}
message SearchResult {
string data = 1;
}
service SearchService {
rpc Search(SearchRequest) returns(SearchResult);
}- บรรทัดแรก
syntax = "proto3";หมายถึงใช้ไวยากรณ์proto3โดยค่าเริ่มต้นใช้ไวยากรณ์proto3 - การประกาศ
messageคล้ายกับโครงสร้าง เป็นโครงสร้างพื้นฐานของproto SearchRequestกำหนดสามฟิลด์ แต่ละฟิลด์มีชื่อและประเภทserviceกำหนดบริการหนึ่งบริการ หนึ่งบริการประกอบด้วยหนึ่งหรือมากกว่าหนึ่ง rpc interface- rpc interface ต้องมีพารามิเตอร์และค่าส่งคืนหนึ่งและเพียงหนึ่งเท่านั้น ประเภทของมันต้องเป็น
messageไม่สามารถเป็นประเภทพื้นฐาน
อีกสิ่งที่ควรสังเกตคือ ทุกบรรทัดในไฟล์ proto ต้องมีเครื่องหมายเซมิโคลอนต่อท้าย
คำอธิบาย
สไตล์คำอธิบายเหมือนกับ go ทุกประการ
syntax = "proto3";
/* คำอธิบาย
* คำอธิบาย */
message SearchRequest {
string query = 1; //คำอธิบาย
string number = 2;
}ประเภท
ตัวปรับประเภทสามารถปรากฏได้เฉพาะใน message เท่านั้น ไม่สามารถปรากฏเดี่ยวๆ
ประเภทพื้นฐาน
| proto Type | Go Type |
|---|---|
| double | float64 |
| float | float32 |
| int32 | int32 |
| int64 | int64 |
| uint32 | uint32 |
| uint64 | uint64 |
| sint32 | int32 |
| sint64 | int64 |
| fixed32 | uint32 |
| fixed64 | uint64 |
| sfixed32 | int32 |
| sfixed64 | int64 |
| bool | bool |
| string | string |
| bytes | []byte |
อาร์เรย์
เพิ่มตัวปรับ repeated หน้าประเภทพื้นฐานเพื่อแสดงว่าเป็นประเภทอาร์เรย์ ตรงกับสไลซ์ใน go
message Company {
repeated string employee = 1;
}map
การกำหนดประเภท map ใน protobuf มีรูปแบบดังนี้
map<key_type, value_type> map_field = N;key_type ต้องเป็นตัวเลขหรือสตริง value_type ไม่มีข้อจำกัดประเภท ดูตัวอย่าง
message Person {
map<string, int64> cards = 1;
}ฟิลด์
จริงๆ แล้ว proto ไม่ใช่ประเภทคีย์-ค่าแบบดั้งเดิม ในการประกาศไฟล์ proto จะไม่ปรากฏข้อมูลเฉพาะ หลังจากแต่ละฟิลด์ = ตามด้วยควรเป็นหมายเลขเฉพาะใน message ปัจจุบัน หมายเลขเหล่านี้ใช้สำหรับระบุและกำหนดฟิลด์เหล่านี้ในข้อความไบนารี หมายเลขเริ่มต้นจาก 1 หมายเลข 1-15 จะใช้ 1 ไบต์ หมายเลข 16-2047 จะใช้สองไบต์ ดังนั้นพยายามให้ฟิลด์ที่ปรากฏบ่อยมีหมายเลข 1-15 เพื่อประหยัดพื้นที่ และควรเว้นที่ว่างบางส่วนสำหรับฟิลด์ที่อาจปรากฏบ่อยในอนาคต
ฟิลด์ใน message ควรเป็นไปตามกฎต่อไปนี้
singular: โดยค่าเริ่มต้นเป็นฟิลด์ประเภทนี้ ในmessageที่มีโครงสร้างดี มีและเพียง 0 หรือ 1 ฟิลด์นี้เท่านั้น นั่นคือไม่สามารถมีฟิลด์เดียวกันซ้ำได้ การประกาศดังนี้จะรายงานข้อผิดพลาดprotobufsyntax = "proto3"; message SearchRequest { string query = 1; string number = 2; string number = 3;//ฟิลด์ซ้ำ }optional: คล้ายกับsingularเพียงแต่สามารถตรวจสอบค่าฟิลด์ว่าถูกตั้งค่าหรือไม่ อาจมีสองสถานการณ์ต่อไปนี้set: จะถูกซีเรียลไลซ์unset: จะไม่ถูกซีเรียลไลซ์
repeated: ฟิลด์ประเภทนี้สามารถปรากฏ 0 ครั้งหรือมากกว่า จะเก็บค่าซ้ำตามลำดับ (พูดง่ายๆ ก็คืออาร์เรย์ สามารถอนุญาตให้ค่าประเภทเดียวกันปรากฏหลายครั้งและเก็บตามลำดับที่ปรากฏ ก็คือดัชนี)map: ฟิลด์ประเภทคีย์-ค่า วิธีการประกาศดังนี้protobufmap<string,int32> config = 3;
ฟิลด์ที่สงวนไว้
คำสำคัญ reserve สามารถประกาศฟิลด์ที่สงวนไว้ หลังจากประกาศหมายเลขฟิลด์ที่สงวนไว้แล้ว จะไม่สามารถใช้เป็นหมายเลขฟิลด์และชื่อของฟิลด์อื่นได้อีก และจะเกิดข้อผิดพลาดเมื่อคอมไพล์ คำตอบทางการจากกูเกิลคือ: หากไฟล์ proto ลบหมายเลขบางตัวในเวอร์ชันใหม่ ในอนาคตผู้ใช้รายอื่นอาจใช้หมายเลขที่ถูกลบเหล่านี้ซ้ำ แต่ถ้าเปลี่ยนกลับเป็นหมายเลขเวอร์ชันเก่าจะทำให้หมายเลขที่ฟิลด์ตรงกันไม่สอดคล้องกันและเกิดข้อผิดพลาด ฟิลด์ที่สงวนไว้สามารถทำหน้าที่เตือนในช่วงคอมไพล์ เตือนว่าคุณไม่สามารถใช้ฟิลด์ที่สงวนไว้นี้ได้ มิฉะนั้นการคอมไพล์จะไม่ผ่าน
syntax = "proto3";
message SearchRequest {
string query = 1;
string number = 2;
map<string, int32> config = 3;
repeated string a = 4;
reserved "a"; //ประกาศฟิลด์ที่มีชื่อเฉพาะเป็นฟิลด์ที่สงวนไว้
reserved 1 to 2; //ประกาศลำดับหมายเลขเป็นฟิลด์ที่สงวนไว้
reserved 3,4; //ประกาศ
}ดังนั้นไฟล์นี้จะไม่ผ่านการคอมไพล์
ฟิลด์ที่เลิกใช้
หากฟิลด์ถูกเลิกใช้ สามารถเขียนดังนี้
message Body {
string name = 1 [deprecated = true];
}การนับ
สามารถประกาศค่าคงที่การนับและใช้เป็นประเภทฟิลด์ได้ ควรสังเกตว่าองค์ประกอบแรกของการนับต้องเป็นศูนย์ เพราะค่าเริ่มต้นของการนับคือองค์ประกอบแรก
syntax = "proto3";
enum Type {
GET = 0;
POST = 1;
PUT = 2;
DELETE = 3;
}
message SearchRequest {
string query = 1;
string number = 2;
map<string, int32> config = 3;
repeated string a = 4;
Type type = 5;
}เมื่อมีการนับที่มีค่าเดียวกันในการนับ สามารถใช้นามสกุลการนับ
syntax = "proto3";
enum Type {
option allow_alias = true; //ต้องเปิดการตั้งค่าที่อนุญาตให้ใช้นามสกุล
GET = 0;
GET_ALIAS = 0; //นามสกุลของฟิลด์ GET
POST = 1;
PUT = 2;
DELETE = 3;
}
message SearchRequest {
string query = 1;
string number = 2;
map<string, int32> config = 3;
repeated string a = 4;
Type type = 5;
}ข้อความซ้อนกัน
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}message สามารถประกาศ message ซ้อนกันได้ เหมือนกับโครงสร้างซ้อนกัน
Package
คุณสามารถเพิ่มตัวปรับแพ็กเกจเสริมให้กับไฟล์ protobuf เพื่อป้องกันการชนกันของชื่อระหว่างประเภทข้อความโปรโตคอล
package foo.bar;
message Open { ... }จากนั้นคุณสามารถใช้ชื่อแพ็กเกจเมื่อประกาศฟิลด์ในประเภทข้อความ:
message Foo {
...
foo.bar.Open open = 1;
...
}Import
การนำเข้าสามารถให้ไฟล์ protobuf หลายไฟล์แชร์คำจำกัดความ ไวยากรณ์มีดังนี้ เมื่อไม่สามารถละเลยนามสกุลไฟล์ได้
import "a/b/c.proto";เมื่อการนำเข้าทั้งหมดใช้พาธสัมพัทธ์ พาธสัมพัทธ์นี้ไม่ใช่พาธสัมพัทธ์ระหว่างไฟล์นำเข้าและไฟล์ที่นำเข้า แต่ขึ้นอยู่กับพาธการสแกนที่ระบุเมื่อตัวคอมไพเลอร์ protoc สร้างโค้ด สมมติว่ามีโครงสร้างไฟล์ดังนี้
pb_learn
│ common.proto
│
├─monster
│ monster.proto
│
└─player
health.proto
player.protoหากเราต้องการสร้างโค้ดส่วนของไดเรกทอรี player เท่านั้น และระบุเฉพาะไดเรกทอรี player เมื่อสแกนพาธ การนำเข้าระหว่าง health.proto กับ player.proto สามารถเขียนชื่อไฟล์เดียวได้ เช่น player.proto นำเข้า health.proto
import "health.proto";หากในเวลานี้ player.proto นำเข้าไฟล์ในไดเรกทอรี common.proto หรือ monster จะล้มเหลวในการคอมไพล์ ดังนั้นการเขียนแบบนี้จึงผิดอย่างสมบูรณ์ เพราะตัวคอมไพเลอร์ไม่สามารถหาไฟล์เหล่านี้ได้
import "../common.proto"; // การเขียนที่ผิดTIP
พูดเสริมว่า สัญลักษณ์ .., . เหล่านี้ไม่อนุญาตให้ปรากฏในพาธการนำเข้า
สมมติว่าระบุ pb_learn เป็นพาธการสแกนเมื่อคอมไพล์ จากนั้นสามารถนำเข้าไฟล์ในไดเรกทอรีอื่นผ่านพาธสัมพัทธ์ พาธการนำเข้าจริงคือที่อยู่สัมบูรณ์ของไฟล์สัมพัทธ์กับ pb_learn ดูตัวอย่างการนำเข้าไฟล์อื่นของ player.proto ด้านล่าง
import "common.proto";
imrpot "monster/monster.proto";
import "player/health.proto";แม้ว่า health.proto จะอยู่ในไดเรกทอรีเดียวกัน ตอนนี้ก็ต้องใช้พาธสัมพัทธ์ ดังนั้นในโครงการหนึ่ง โดยทั่วไปเราจะสร้างโฟลเดอร์แยกต่างหากเพื่อเก็บไฟล์ protobuf ทั้งหมด และระบุเป็นพาธการสแกนเมื่อคอมไพล์ และการดำเนินการนำเข้าทั้งหมดในไดเรกทอรีนี้ก็ขึ้นอยู่กับพาธสัมพัทธ์
TIP
หากคุณใช้ตัวแก้ไข goland สำหรับไดเรกทอรี protobuf ที่คุณสร้างขึ้นเอง โดยค่าเริ่มต้นจะไม่สามารถวิเคราะห์ได้ ซึ่งจะทำให้เกิดสถานการณ์สีแดง หากต้องการให้ goland ระบุได้ก็ต้องตั้งค่าพาธการสแกนด้วยตนเอง หลักการเหมือนกับที่กล่าวมาข้างต้น วิธีการตั้งค่ามีดังนี้ เปิดการตั้งค่าดังนี้
File | Settings | Languages & Frameworks | Protocol Buffersเพิ่มพาธการสแกนด้วยตนเองใน Import Paths พาธการสแกนนี้ควรเหมือนกับพาธที่คุณระบุเมื่อคอมไพล์

Any
ประเภท Any อนุญาตให้คุณใช้ข้อความเป็นประเภทฝังตัวโดยไม่ต้องมีคำจำกัดความ proto ของพวกมัน เราสามารถนำเข้าประเภทที่กูเกิลกำหนดไว้โดยตรง มันมีมาให้ ไม่ต้องเขียนด้วยตนเอง
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}กูเกิลยังกำหนดประเภทอื่นๆ ไว้มากมาย ไปที่ protobuf/ptypes at master · golang/protobuf (github.com) เพื่อดูเพิ่มเติม มีหลักๆ รวมถึง
- การห่อหุ้มประเภทพื้นฐาน
- ประเภทเวลา
- ประเภท Duration
เกี่ยวกับคำจำกัดความ protobuf ของพวกมันควรอยู่ในไดเรกทอรี inlucde ของตัวคอมไพเลอร์ protoc
OneOf
คำอธิบายที่เอกสารทางการให้ไว้ซับซ้อนเกินไป พูดภาษามนุษย์其实就是แสดงว่าฟิลด์จะมีประเภทที่เป็นไปได้หลายประเภทเมื่อส่งข้อมูล แต่สุดท้ายจะมีเพียงประเภทเดียวที่ถูกใช้ ภายในไม่อนุญาตให้มีฟิลด์ที่ปรับด้วย repeated นี่ก็เหมือนกับ union ในภาษา c
message Stock {
// Stock-specific data
}
message Currency {
// Currency-specific data
}
message ChangeNotification {
int32 id = 1;
oneof instrument {
Stock stock = 2;
Currency currency = 3;
}
}Service
คำสำคัญ service สามารถกำหนดบริการ RPC หนึ่งบริการ RPC หนึ่งบริการประกอบด้วย rpc interface หลายอัน interface แบ่งเป็น one-way interface และ stream interface
message Body {
string name = 1;
}
service ExampleService {
rpc DoSomething(Body) returns(Body);
}และ stream interface ยังแบ่งเป็น one-way stream และ two-way stream มักใช้คำสำคัญ stream เพื่อปรับ ดูตัวอย่างด้านล่าง
message Body {
string name = 1;
}
service ExampleService {
// ไคลเอนต์สตรีม
rpc DoSomething(stream Body) returns(Body);
// เซิร์ฟเวอร์สตรีม
rpc DoSomething1(Body) returns(stream Body);
// ทวิทิศทางสตรีม
rpc DoSomething2(stream Body) returns(stream Body);
}สิ่งที่เรียกว่าสตรีมคือการส่งข้อมูลซึ่งกันและกันในระยะยาวในการเชื่อมต่อหนึ่งครั้ง ไม่เหมือน one-way interface ที่ถามตอบง่ายๆ
Empty
empty จริงๆ แล้วเป็น message ว่าง ตรงกับโครงสร้างว่างใน go มันใช้ปรับฟิลด์น้อยมาก ส่วนใหญ่ใช้แสดงว่า rpc interface บางอย่างไม่มีพารามิเตอร์หรือไม่มีค่าส่งคืน
syntax = "proto3";
import "google/protobuf/empty.proto";
service EmptyService {
rpc Do(google.protobuf.Empty) returns(google.protobuf.Empty);
}Option
option มักใช้ควบคุมพฤติกรรมบางอย่างของ protobuf เช่น ควบคุมแพ็กเกจที่สร้างซอร์สโค้ดภาษา go สามารถประกาศดังนี้
option go_package = "github/jack/sample/pb_learn;pb_learn"ด้านหน้าเซมิโคลอนคือพาธการนำเข้าของไฟล์ซอร์สอื่นหลังจากสร้างโค้ด ด้านหลังเซมิโคลอนคือชื่อแพ็กเกจของไฟล์ที่สร้าง
มันสามารถทำการปรับ优化ได้ มีค่าที่ใช้ได้หลายค่าต่อไปนี้ ไม่สามารถประกาศซ้ำได้
SPEEDระดับการปรับ优化สูงสุด ปริมาตรโค้ดที่สร้างใหญ่ที่สุด โดยค่าเริ่มต้นเป็นนี้CODE_SIZEจะลดปริมาตรโค้ดที่สร้าง แต่จะพึ่งพาการสะท้อนเพื่อซีเรียลไลเซชันLIFE_RUNEIMTEปริมาตรโค้ดเล็กที่สุด แต่จะขาดคุณสมบัติบางอย่าง
ด้านล่างนี้เป็นตัวอย่างการใช้
option optimize_for = SPEED;นอกจากนี้ option ยังสามารถเพิ่มข้อมูลเมตาบางอย่างให้กับ message และ enum ได้ ใช้การสะท้อนสามารถรับข้อมูลเหล่านี้ ซึ่งมีประโยชน์เป็นพิเศษเมื่อทำการตรวจสอบพารามิเตอร์
การคอมไพล์
การคอมไพล์ก็คือการสร้างโค้ด ด้านบนเพียงกำหนดไฟล์ protobuf เมื่อใช้งานจริงต้องแปลงเป็นซอร์สโค้ดภาษาใดภาษาหนึ่งจึงจะสามารถใช้งานได้ เราทำสิ่งนี้ผ่านตัวคอมไพเลอร์ protoc มันรองรับหลายภาษา

การติดตั้ง
การดาวน์โหลดตัวคอมไพเลอร์ไปที่ protocolbuffers/protobuf: Protocol Buffers - Google's data interchange format (github.com) เพื่อดาวน์โหลด Release เวอร์ชันล่าสุด โดยทั่วไปเป็นไฟล์บีบอัด
protoc-25.1-win64
│ readme.txt
│
├─bin
│ protoc.exe
│
└─include
└─google
└─protobuf
│ any.proto
│ api.proto
│ descriptor.proto
│ duration.proto
│ empty.proto
│ field_mask.proto
│ source_context.proto
│ struct.proto
│ timestamp.proto
│ type.proto
│ wrappers.proto
│
└─compiler
plugin.protoหลังจากดาวน์โหลดเสร็จต้องเพิ่มไดเรกทอรี bin ลงในตัวแปรสภาพแวดล้อม เพื่อให้สามารถใช้คำสั่ง protoc ได้ หลังจากเสร็จแล้วดูเวอร์ชัน หากสามารถเอาต์พุตได้ตามปกติแสดงว่าติดตั้งสำเร็จ
$ protoc --version
libprotoc 25.1ตัวคอมไพเลอร์ที่ดาวน์โหลดมาโดยค่าเริ่มต้นไม่รองรับภาษา go เพราะการสร้างโค้ดภาษา go เป็นไฟล์ที่รันได้แยกต่างหาก ภาษาอื่นรวมกันไว้ ดังนั้นติดตั้งปลั๊กอินภาษา go เพิ่มเติม ใช้สำหรับแปลคำจำกัดความ protocbuf เป็นซอร์สโค้ดภาษา go
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latestหากต้องการสร้างโค้ดบริการ gRPC ด้วย ติดตั้งปลั๊กอินดังนี้
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestหลังจากติดตั้งแล้วดูเวอร์ชัน
$ protoc-gen-go-grpc --version
protoc-gen-go-grpc 1.3.0
$ protoc-gen-go --version
protoc-gen-go.exe v1.31.0ปลั๊กอินเหล่านี้เป็นไฟล์ไบนารีแยกต่างหาก แต่สามารถเรียกใช้ได้ผ่าน protoc เท่านั้น ไม่สามารถรันเดี่ยวๆ
(this program should be run by protoc, not directly)นอกจากนี้ยังมีปลั๊กอินอื่นๆ อีกมากมาย เช่น ปลั๊กอินสร้างเอกสาร interface openapi เป็นต้น หากสนใจสามารถค้นหาด้วยตนเอง
การสร้าง
ยังคงใช้ตัวอย่างก่อนหน้า来讲 โครงสร้างมีดังนี้
pb_learn
│ common.proto
│
├─monster
│ monster.proto
│
└─player
health.proto
player.protoสำหรับการสร้างโค้ด ต้องระบุพารามิเตอร์สามตัวทั้งหมด
- พาธการสแกน บอกตัวคอมไพเลอร์ว่าค้นหาไฟล์
protobufจากที่ไหนและวิเคราะห์พาธการนำเข้าอย่างไร - พาธการสร้าง ไฟล์ที่คอมไพล์เสร็จแล้ววางไว้ที่ไหน
- ไฟล์เป้าหมาย ระบุไฟล์เป้าหมายใดบ้างที่จะถูกคอมไพล์
ก่อนเริ่มต้นต้องแน่ใจว่าตั้งค่า go_package ในไฟล์ protobuf ถูกต้อง ดูพารามิเตอร์ที่รองรับผ่าน protoc -h ที่ใช้บ่อยที่สุดคือ -I หรือ --proto_path สามารถใช้หลายครั้งเพื่อระบุพาธการสแกนหลายพาธ เช่น
$ protoc --proto_path=./pb_learn --proto_path=./third_partyเพียงระบุพาธการสแกนไม่เพียงพอ ต้องระบุพาธการสร้างและไฟล์ protobuf เป้าหมาย ที่นี่สร้างไฟล์ go ดังนั้นใช้พารามิเตอร์ --go_out รองรับโดยปลั๊กอิน protoc-gen-go ที่ดาวน์โหลดก่อนหน้า
$ cd pb_learn
$ protoc --proto_path=. --go_out=. common.proto
$ ls
common.pb.go common.proto monster/ player/พารามิเตอร์ --go_out คือระบุพาธการสร้าง . หมายถึงพาธปัจจุบัน common.proto คือระบุไฟล์ที่จะคอมไพล์ หากต้องการสร้างโค้ด grpc (ต้องมีปลั๊กอิน grpc ก่อน) สามารถเพิ่มพารามิเตอร์ --go-grpc_out (หากไฟล์ protobuf ไม่ได้กำหนด service จะไม่สร้างไฟล์ที่สอดคล้องกัน)
$ protoc --proto_path=. --go_out=. --go-grpc_out=. common.proto
$ ls
common.pb.go common.proto common_grpc.pb.go monster/ player/common.pb.go คือคำจำกัดความประเภท protobuf ที่สร้าง common_grpc.pb.go คือโค้ด gRPC ที่สร้าง มันขึ้นอยู่กับ前者 หากไม่มีคำจำกัดความของภาษาที่สอดคล้องกัน ก็ไม่สามารถสร้างโค้ด gRPC ได้
หากต้องการคอมไพล์ไฟล์ protobuf ทั้งหมดในไดเรกทอรีนี้ สามารถใช้ wildcard * เช่น
$ protoc --proto_path=. --go_out=.. common.proto --go-grpc_out=. ./*.protoหากต้องการรวมไฟล์ทั้งหมด สามารถใช้ wildcard ** เช่น ./**/*.proto
$ protoc --proto_path=. --go_out=.. common.proto --go-grpc_out=. ./**/*.protoแต่ วิธีนี้ใช้ได้เฉพาะกับ shell ที่รองรับ wildcard ประเภทนี้ เช่น ภายใต้ windows cmd หรือ powershell ไม่รองรับการเขียนแบบนี้
D> protoc --proto_path=. --go_out=.. common.proto --go-grpc_out=. ./**/*.proto
Invalid file name pattern or missing input file "./**/*.proto"โชคดีที่ gitbash รองรับคำสั่ง linux หลายคำสั่ง สามารถทำให้ windows รองรับไวยากรณ์นี้ได้ เพื่อหลีกเลี่ยงการเขียนคำสั่งซ้ำทุกครั้ง สามารถใส่ไว้ใน makefile
.PHONY: all
proto_gen:
protoc --proto_path=. \
--go_out=paths=source_relative:. \
--go-grpc_out=paths=source_relative:. \
./**/*.proto ./*.protoจะสังเกตว่ามี paths=source_relative:. เพิ่มขึ้นมา นี่คือการตั้งค่าโหมดพาธการสร้างไฟล์ มีตัวเลือกที่ใช้ได้ดังนี้
paths=importโดยค่าเริ่มต้นเป็นนี้ ไฟล์จะสร้างในไดเรกทอรีที่ระบุโดยimportมันสามารถเป็นพาธโมดูลได้ เช่น มีไฟล์protos/buzz.protoระบุpaths=example.com/project/protos/fizzสุดท้ายจะสร้างexample.com/project/protos/fizz/buzz.pb.gomodule=$PREFIXเมื่อสร้าง จะลบคำนำหน้าพาธออก ในตัวอย่างข้างต้น หากระบุคำนำหน้าexample.com/projectสุดท้ายจะสร้างprotos/fizz/buzz.pb.goโหมดนี้ส่วนใหญ่ใช้เพื่อสร้างในโมดูลโดยตรง (รู้สึกว่า好像ไม่มีความแตกต่าง)paths=source_relativeไฟล์ที่สร้างจะรักษาโครงสร้างสัมพัทธ์เดียวกับไฟล์protobufในไดเรกทอรีที่ระบุ
หลังเครื่องหมายเซมิโคลอน : คือพาธการสร้างที่ระบุ
| common.proto
| common.pb.go
│
├─monster
│ monster.pb.go
│ monster.proto
│
└─player
health.pb.go
health.proto
health_grpc.pb.go
player.pb.go
player.protoการสะท้อน
สามารถขยาย enum และ message ผ่าน options ก่อนนำเข้า "google/protobuf/descriptor.proto"
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
optional string string_name = 123456789;
}
enum Integer {
INT64 = 0[
(string_name) = "int_64"
];
}นี่เท่ากับการเพิ่มข้อมูลเมตาให้กับค่าการนับนี้ สำหรับ message ก็เช่นเดียวกัน ดังนี้
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = "Hello world!";
}นี่เท่ากับการมี reflection เกี่ยวกับ protobuf หลังจากสร้างโค้ดสามารถเข้าถึงผ่าน Descriptor ได้ ดังนี้
func main() {
message := pb_learn.MyMessage{}
message.ProtoReflect().Descriptor().Options().ProtoReflect().Range(func(descriptor protoreflect.FieldDescriptor, value protoreflect.Value) bool {
fmt.Println(descriptor.FullName(), ":", value)
return true
})
}เอาต์พุต
my_option:"Hello world!"วิธีนี้สามารถเปรียบเทียบกับการเพิ่ม tag ให้กับโครงสร้างใน go รู้สึกไม่ต่างกันมาก ตามวิธีนี้ยังสามารถ实现ฟังก์ชันการตรวจสอบพารามิเตอร์ได้ เพียงแค่เขียนกฎใน options ตรวจสอบผ่าน Descriptor
