รูปแบบการออกแบบ Protobuf


19

ฉันกำลังประเมินบัฟเฟอร์ของ Google Protocol สำหรับบริการบน Java (แต่คาดว่าจะมีรูปแบบของผู้ไม่เชื่อเรื่องภาษา) ฉันมีสองคำถาม:

คำถามแรกคือคำถามทั่วไปที่กว้างขวาง:

เราเห็นรูปแบบอะไรที่ผู้คนใช้? รูปแบบดังกล่าวเกี่ยวข้องกับองค์กรระดับ (เช่นข้อความต่อไฟล์ .proto, บรรจุภัณฑ์และการจัดจำหน่าย) และคำจำกัดความของข้อความ (เช่นเขตข้อมูลซ้ำแล้วซ้ำกับเขตข้อมูลที่ห่อหุ้มซ้ำ *) เป็นต้น

มีข้อมูลประเภทนี้น้อยมากในหน้าความช่วยเหลือของ Google Protobuf และบล็อกสาธารณะในขณะที่มีข้อมูลจำนวนมากสำหรับโปรโตคอลที่กำหนดเช่น XML

ฉันยังมีคำถามเฉพาะเหนือรูปแบบที่แตกต่างกันสองแบบ:

  1. แสดงข้อความในไฟล์ .proto, จัดเก็บเป็น jar แยกต่างหากและจัดส่งไปยังผู้บริโภคเป้าหมายของบริการ - ซึ่งเป็นวิธีการเริ่มต้นที่ฉันเดา

  2. ทำเช่นเดียวกัน แต่รวมถึงการห่อด้วยมือที่สร้างขึ้น (ไม่ใช่คลาสย่อย!) รอบ ๆ แต่ละข้อความที่ใช้สัญญาที่สนับสนุนอย่างน้อยสองวิธีนี้ (T คือคลาส wrapper, V คือคลาสของข้อความ (ใช้ชื่อสามัญ :

    public V toProtobufMessage() {
        V.Builder builder = V.newBuilder();
        for (Item item : getItemList()) {
            builder.addItem(item);
        }
        return builder.setAmountPayable(getAmountPayable()).
                       setShippingAddress(getShippingAddress()).
                       build();
    }
    
    public static T fromProtobufMessage(V message_) { 
        return new T(message_.getShippingAddress(), 
                     message_.getItemList(),
                     message_.getAmountPayable());
    }
    

ข้อได้เปรียบอย่างหนึ่งที่ฉันเห็นด้วย (2) คือฉันสามารถซ่อนความซับซ้อนที่แนะนำV.newBuilder().addField().build()และเพิ่มวิธีการที่มีความหมายเช่นisOpenForTrade()หรือisAddressInFreeDeliveryZone()อื่น ๆ ในห่อของฉัน ข้อได้เปรียบที่สองที่ฉันเห็นด้วย (2) คือลูกค้าของฉันจัดการกับวัตถุที่ไม่เปลี่ยนรูป (สิ่งที่ฉันสามารถบังคับใช้ในคลาส wrapper)

ข้อเสียอย่างหนึ่งที่ฉันเห็นด้วย (2) คือฉันทำซ้ำรหัสและต้องซิงค์คลาส wrapper ของฉันกับไฟล์. proto

ใครบ้างมีเทคนิคที่ดีกว่าหรือวิจารณ์เพิ่มเติมเกี่ยวกับวิธีการใดวิธีการหนึ่งในสอง?


* การห่อหุ้มฟิลด์ซ้ำฉันหมายถึงข้อความเช่นนี้:

message ItemList {
    repeated item = 1;
}

message CustomerInvoice {
    required ShippingAddress address = 1;
    required ItemList = 2;
    required double amountPayable = 3;
}

แทนข้อความเช่นนี้:

message CustomerInvoice {
    required ShippingAddress address = 1;
    repeated Item item = 2;
    required double amountPayable = 3;
}

ฉันชอบคนหลัง แต่ดีใจที่ได้ยินข้อโต้แย้ง


ฉันต้องการ 12 คะแนนเพิ่มเพื่อสร้างแท็กใหม่และฉันคิดว่า protobuf ควรเป็นแท็กสำหรับโพสต์นี้
Apoorv Khurasia

คำตอบ:


13

ที่ทำงานฉันตัดสินใจปิดบังการใช้โปรโตบูฟ เราไม่แจกจ่าย.protoไฟล์ระหว่างแอปพลิเคชั่น แต่แอปพลิเคชันใด ๆ ที่แสดงอินเตอร์เฟส protobuf จะเอ็กซ์พอร์ตไลบรารีไคลเอ็นต์ที่สามารถพูดคุยได้

ฉันได้ทำงานกับหนึ่งในแอปพลิเคชั่นที่เปิดโปง protobuf เหล่านี้ แต่ในนั้นแต่ละข้อความ protobuf สอดคล้องกับแนวคิดบางอย่างในโดเมน สำหรับแต่ละแนวคิดมีอินเตอร์เฟส Java ปกติ จากนั้นมีคลาสตัวแปลงซึ่งสามารถใช้อินสแตนซ์ของการนำไปใช้งานและสร้างวัตถุข้อความที่เหมาะสมและนำวัตถุข้อความและสร้างอินสแตนซ์ของการใช้งานอินเทอร์เฟซ (ตามที่เกิดขึ้นโดยทั่วไปแล้ว ภายในตัวแปลง) คลาสข้อความที่สร้างขึ้น protobuf และตัวแปลงร่วมกันสร้างไลบรารีซึ่งถูกใช้โดยทั้งแอ็พพลิเคชันและไลบรารีไคลเอ็นต์ ไลบรารีไคลเอ็นต์เพิ่มโค้ดจำนวนเล็กน้อยสำหรับการตั้งค่าการเชื่อมต่อและการส่งและรับข้อความ

แอปพลิเคชันไคลเอนต์นำเข้าไลบรารีไคลเอนต์และนำไปใช้ของอินเทอร์เฟซใด ๆ ที่พวกเขาต้องการส่ง แน่นอนทั้งสองฝ่ายทำสิ่งหลัง

หากต้องการชี้แจงนั่นหมายความว่าหากคุณมีรอบการตอบสนองคำขอที่ลูกค้ากำลังส่งคำเชิญปาร์ตี้และเซิร์ฟเวอร์กำลังตอบกลับด้วย RSVP สิ่งที่เกี่ยวข้องคือ:

  • ข้อความ PartyInvitation ที่เขียนใน.protoไฟล์
  • PartyInvitationMessage คลาสที่สร้างโดย protoc
  • PartyInvitation อินเตอร์เฟสกำหนดไว้ในไลบรารีที่แบ่งใช้
  • ActualPartyInvitationPartyInvitationแอปพลิเคชันไคลเอนต์ที่กำหนดไว้อย่างเป็นรูปธรรม(ไม่ได้เรียกจริง ๆ !)
  • StubPartyInvitationการใช้งานง่าย ๆ ของการPartyInvitationกำหนดโดยห้องสมุดสาธารณะ
  • PartyInvitationConverterซึ่งสามารถแปลง a PartyInvitationเป็น a PartyInvitationMessageและ a PartyInvitationMessageเป็น aStubPartyInvitation
  • ข้อความ RSVP เขียนใน.protoไฟล์
  • RSVPMessage คลาสที่สร้างโดย protoc
  • RSVP อินเตอร์เฟสกำหนดไว้ในไลบรารีที่แบ่งใช้
  • ActualRSVPการใช้งานที่RSVPกำหนดไว้อย่างชัดเจนของแอปเซิร์ฟเวอร์ (หรือที่เรียกว่า!)
  • StubRSVPการใช้งานง่าย ๆ ของการRSVPกำหนดโดยห้องสมุดสาธารณะ
  • RSVPConverterซึ่งสามารถแปลง a RSVPเป็นRSVPMessageและRSVPMessagea เป็น aStubRSVP

เหตุผลที่เรามีการใช้งานจริงและการแยกส่วนที่ไม่แน่นอนคือการใช้งานจริงโดยทั่วไปคือคลาสเอนทิตี JPA ที่แมป เซิร์ฟเวอร์จะสร้างและคงอยู่หรือค้นหาจากฐานข้อมูลจากนั้นส่งไปยังชั้น protobuf ที่จะส่ง ไม่รู้สึกว่าเป็นการเหมาะสมที่จะสร้างอินสแตนซ์ของคลาสเหล่านั้นในด้านการรับของการเชื่อมต่อเนื่องจากพวกเขาจะไม่เชื่อมโยงกับบริบทการคงอยู่ นอกจากนี้เอนทิตีมักจะมีข้อมูลมากกว่าที่จะถูกส่งผ่านสายดังนั้นมันจึงเป็นไปไม่ได้ที่จะสร้างวัตถุเหมือนเดิมที่ด้านรับ ฉันไม่เชื่อว่านี่เป็นการย้ายที่ถูกต้องเพราะมันทำให้เรามีคลาสต่อข้อความมากกว่าที่เราจะมี

ที่จริงแล้วฉันไม่เชื่อว่าการใช้โปรโตบูฟเลยเป็นความคิดที่ดี หากเราติดอยู่กับ RMI แบบเก่าและการทำให้เป็นอนุกรมเราจะไม่ต้องสร้างวัตถุเกือบเท่ากัน ในหลายกรณีเราสามารถทำเครื่องหมายคลาสเอนทิตีของเราว่าเป็นอนุกรมและทำต่อได้

ตอนนี้เมื่อฉันพูดทั้งหมดฉันมีเพื่อนที่ทำงานที่ Google บนโค้ดเบสที่ใช้โพรโทฟัสจำนวนมากเพื่อการสื่อสารระหว่างโมดูล พวกเขาใช้วิธีที่แตกต่างอย่างสิ้นเชิง: พวกเขาไม่ห่อคลาสข้อความที่สร้างเลยและส่งพวกเขาอย่างลึก (ish) ลงในโค้ดของพวกเขาอย่างกระตือรือร้น สิ่งนี้ถูกมองว่าเป็นสิ่งที่ดีเพราะมันเป็นวิธีที่ง่ายในการทำให้อินเตอร์เฟสมีความยืดหยุ่น ไม่มีรหัสนั่งร้านที่จะซิงค์เมื่อข้อความวิวัฒนาการและคลาสที่สร้างขึ้นให้hasFoo()วิธีการทั้งหมดที่จำเป็นสำหรับการรับรหัสเพื่อตรวจสอบว่ามีหรือไม่มีฟิลด์ที่เพิ่มเข้ามาเมื่อเวลาผ่านไป อย่างไรก็ตามพึงระลึกไว้ว่าคนที่ทำงานกับ Google มักจะเป็น (a) ค่อนข้างฉลาดและ (b) เป็นถั่วนิดหน่อย


ณ จุดหนึ่งฉันมองไปที่การใช้JBoss เป็นอนุกรมแทนมากขึ้นหรือน้อยลงแทนการเป็นอนุกรมมาตรฐาน มันค่อนข้างเร็วกว่ามาก ไม่เร็วเท่ากับโปรโตบูฟ
ทอมแอนเดอร์สัน

การจัดลำดับ JSON โดยใช้ jackson2 นั้นค่อนข้างเร็วเช่นกัน สิ่งที่ฉันเกลียดเกี่ยวกับ GBP คือการทำซ้ำของคลาสอินเทอร์เฟซหลักที่ไม่จำเป็น
Apoorv Khurasia

0

หากต้องการเพิ่มคำตอบของ Andersons จะมีเส้นแบ่งในการซ้อนข้อความอย่างชาญฉลาดและซ้อนทับกันมากเกินไป ปัญหาคือแต่ละข้อความสร้างคลาสใหม่ที่อยู่เบื้องหลังและทุกประเภทของ accessors และ handler สำหรับข้อมูล แต่มีค่าใช้จ่ายคือถ้าคุณต้องคัดลอกข้อมูลหรือเปลี่ยนค่าหนึ่งหรือเปรียบเทียบข้อความ กระบวนการเหล่านั้นอาจช้าและเจ็บปวดมากหากคุณมีข้อมูลจำนวนมากหรือถูกผูกมัดตามเวลา


2
สิ่งนี้อ่านได้มากขึ้นเช่นความเห็นแทนเจนต์ดูวิธีการตอบ
gnat

1
มันไม่ดี: ไม่มีโดเมนที่มีคลาสที่เป็นปัญหาถ้อยคำทั้งหมดในตอนท้าย (โอ้ฉันกำลังพัฒนาทุกสิ่งใน C ++ แต่สิ่งนี้ต้องไม่มีปัญหากับผึ้ง)
Marko Bencik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.