Protobuf Tooling
สิ่งที่จะสร้าง
หัวข้อที่มีชื่อว่า “สิ่งที่จะสร้าง”toolchain buf ที่ตั้งค่าไว้แล้วแต่ยังไม่มีอะไรป้อนเข้าไป: buf.yaml (proto workspace กับ dependency ภายนอกหนึ่งตัว) และ buf.gen.yaml (plugin ทั้งสี่ตัวที่จะเปลี่ยนไฟล์ .proto ให้เป็นโค้ด Go) สัญญา .proto จริง ๆ มาใน Module 2 — บทนี้เป็นเรื่องของการมี tooling พร้อมและยืนยันแล้วก่อนที่จะมีอะไรให้ generate
การทำ protobuf codegen ด้วยมือหมายถึงต้องติดตั้ง protoc บวก binary protoc-gen-go, binary protoc-gen-go-grpc และอื่น ๆ คอยดูแลให้เวอร์ชันของผู้ร่วมพัฒนาทุกคนตรงกันด้วยมือ buf แทนที่ทั้งหมดนั้น: buf.gen.yaml อ้างอิงถึง remote plugin ที่ host บน Buf Schema Registry (BSR) ดังนั้น buf generate จะดาวน์โหลดและรัน plugin เวอร์ชันที่ pin ไว้เป๊ะ ๆ ให้ทุกคน — ไม่ต้องติดตั้ง binary protoc-gen-* ในเครื่องเลย buf.yaml ยังให้เรามี buf lint ซึ่งดักปัญหาการออกแบบ proto API อย่างการตั้งชื่อไม่ดีหรือขาด option ได้ตั้งแต่ก่อนไปถึงขั้น review โค้ด
ข้อดีข้อเสีย
หัวข้อที่มีชื่อว่า “ข้อดีข้อเสีย”ข้อดี
- ไม่มี
protocหรือ binaryprotoc-gen-*ในเครื่อง — plugin รันแบบ remote ผ่าน BSR และถูก pin เวอร์ชันไว้ในbuf.lock buf lintบังคับใช้สไตล์ที่สม่ำเสมอและเป็นที่รู้จักกันดี (กฎSTANDARD) กับไฟล์.protoของทุกเซอร์วิสโดยอัตโนมัติ- คำสั่ง
buf generateครั้งเดียวรัน plugin ทั้งสี่ตัวและเขียนทุกอย่างลงgen/ deps:ดึงgoogle/api/annotations.protoจากbuf.build/googleapis/googleapis— annotation type ที่ gateway plugin ต้องใช้ — โดยไม่ต้อง vendor เองด้วยมือ
ข้อเสีย
buf generateต้องใช้ network เข้าถึง BSR ในครั้งแรกที่ resolve remote plugin (หลังจากนั้นจะ cache ไว้ในเครื่อง)- เป็นอีกไฟล์ config และอีก CLI ที่ต้องเรียนรู้ ซ้อนทับ protobuf เองอีกที
- เวอร์ชัน plugin ที่ pin ไว้ต้องอัปเดตเป็นครั้งคราวอย่างตั้งใจ เพราะจะไม่ไล่ตาม release ของ
protoc-gen-goต้นทางให้เองแบบเงียบ ๆ
ติดตั้ง
หัวข้อที่มีชื่อว่า “ติดตั้ง”1. ติดตั้ง buf
หัวข้อที่มีชื่อว่า “1. ติดตั้ง buf”brew install bufbuild/buf/bufVerify:
buf --version2. buf.yaml — proto workspace
หัวข้อที่มีชื่อว่า “2. buf.yaml — proto workspace”ไฟล์นี้กำหนดว่าไฟล์ .proto ของคุณอยู่ที่ไหน, กฎ lint แบบไหนที่ใช้ และ dependency ของ module ภายนอกตัวเดียวที่ gateway plugin ต้องการ:
version: v2modules: - path: protodeps: - buf.build/googleapis/googleapislint: use: - STANDARDบันทึกเป็น buf.yaml ที่ root ของ repo modules ชี้ไปที่ proto/ (ตอนนี้ยังว่างเปล่า — Module 2 จะเติมให้); deps ประกาศว่า google/api/annotations.proto (จาก buf.build/googleapis/googleapis) พร้อมให้ import ได้ นั่นคือสิ่งที่ทำให้ไฟล์ .proto แนบ HTTP route สำหรับ grpc-gateway ได้; lint.use: STANDARD เปิดกฎ naming และ style มาตรฐานของ buf ให้กับทุกไฟล์ใต้ proto/
3. buf.gen.yaml — codegen plugin ทั้งสี่ตัว
หัวข้อที่มีชื่อว่า “3. buf.gen.yaml — codegen plugin ทั้งสี่ตัว”version: v2clean: trueplugins: - remote: buf.build/protocolbuffers/go out: gen opt: paths=source_relative - remote: buf.build/grpc/go out: gen opt: paths=source_relative - remote: buf.build/grpc-ecosystem/gateway out: gen opt: paths=source_relative - remote: buf.build/grpc-ecosystem/openapiv2 out: geninputs: - directory: protoบันทึกเป็น buf.gen.yaml ที่ root ของ repo clean: true ล้าง gen/ ก่อนรันทุกครั้ง จึงไม่มีวันสะสมไฟล์เก่าจาก message ที่เปลี่ยนชื่อหรือโดนลบ ส่วน paths=source_relative ทำให้ path ของไฟล์ที่ generate ออกมาสะท้อนโครงสร้าง package .proto ของคุณ แทนที่จะโดน flatten ตาม Go import path — เป็น convention ที่คอร์สนี้ใช้ตลอด
4. แต่ละ plugin ผลิตอะไร (เมื่อ Module 2 เติมไฟล์ .proto แล้ว)
หัวข้อที่มีชื่อว่า “4. แต่ละ plugin ผลิตอะไร (เมื่อ Module 2 เติมไฟล์ .proto แล้ว)”protocolbuffers/go— struct Go สำหรับทุกmessage: field getter,Marshal/Unmarshalและทุกอย่างแบบencoding/jsonที่โค้ดคาดหวังจาก protobuf typegrpc/go— gRPC client และ server stub สำหรับทุกservice/rpc: interfaceCatalogServiceClientให้เรียก และ interfaceCatalogServiceServerที่แต่ละเซอร์วิส implementgrpc-ecosystem/gateway— โค้ด reverse-proxy ที่แปล request REST/JSON ที่เข้ามาให้กลายเป็นการเรียก gRPC ขับเคลื่อนด้วย annotationgoogle.api.httpในไฟล์.protoนี่คือสิ่งที่ API Gateway (Module 5) รันgrpc-ecosystem/openapiv2— เอกสาร OpenAPI (Swagger) v2 ที่ generate จาก annotation ชุดเดียวกัน ทำให้ REST surface ที่ gateway expose มีเอกสารอัตโนมัติ โดยไม่ต้องเขียน API doc ด้วยมือให้ตรงกันเลย
ทั้งสี่ตัวตั้งค่าให้เขียนลง directory gen/ เดียวตาม canonical layout ของคอร์สนี้
ตรวจสอบผล
หัวข้อที่มีชื่อว่า “ตรวจสอบผล”buf --versionbuf lintตอนนี้ proto/ ยังว่างเปล่า (Module 2 จะเพิ่มไฟล์ .proto แรก) ดังนั้น buf lint ยังไม่มีอะไรให้ตรวจ และควรจบแบบสะอาด ไม่มี violation พิมพ์ออกมา — นั่นคือสิ่งที่คาดหวัง ไม่ใช่สัญญาณว่ามีอะไรพัง สิ่งที่เรายืนยันตรงนี้คือ buf.yaml parse ได้, workspace resolve ได้ และ dependency googleapis เข้าถึงได้ เมื่อ Module 2 เติมไฟล์ .proto จริงเข้ามา คำสั่ง buf lint เดิมนี้จะเริ่มตรวจของจริง และ buf generate จะผลิต output จริงลงใน gen/ โดยใช้ plugin ทั้งสี่ตัวที่ตั้งค่าไว้ข้างบน
buf ติดตั้งแล้ว และมีไฟล์ config สองไฟล์พร้อมใช้: buf.yaml กำหนด workspace proto/, กฎ lint แบบ STANDARD และ deps: ที่พึ่งพา buf.build/googleapis/googleapis สำหรับ annotation type ที่ gateway ต้องใช้; buf.gen.yaml เดินสายไปหา remote plugin สี่ตัว — protocolbuffers/go (Go message type), grpc/go (client/server stub), grpc-ecosystem/gateway (REST-to-gRPC reverse proxy) และ grpc-ecosystem/openapiv2 (เอกสาร API ที่ generate) — เขียนลง gen/ ทั้งหมด buf lint รันสะอาดกับ proto/ ที่ว่างเปล่าวันนี้; Module 2 คือที่ที่ไฟล์ .proto จริงตัวแรกจะมาถึงและ tooling นี้จะเริ่มผลิตโค้ด Go ที่ generate ออกมา ต่อไป Infra & Compose → จะตั้ง PostgreSQL, Kafka และ RabbitMQ ที่โปรเจกต์นี้รันด้วยในเครื่อง