Mẫu thiết kế Protobuf


19

Tôi đang đánh giá Bộ đệm giao thức Google cho một dịch vụ dựa trên Java (nhưng tôi đang mong đợi các mẫu bất khả tri ngôn ngữ). Tôi có hai câu hỏi:

Đầu tiên là một câu hỏi chung chung:

Những mô hình nào chúng ta đang thấy mọi người sử dụng? Các mẫu đã nói có liên quan đến tổ chức lớp (ví dụ: thư trên mỗi tệp .proto, bao bì và phân phối) và định nghĩa thông báo (ví dụ: các trường lặp lại so với các trường được đóng gói lặp lại *), v.v.

Có rất ít thông tin thuộc loại này trên các trang Trợ giúp Google Protobuf và blog công khai trong khi có rất nhiều thông tin cho các giao thức đã được thiết lập như XML.

Tôi cũng có câu hỏi cụ thể về hai mẫu khác nhau sau đây:

  1. Thể hiện các thông điệp trong các tệp .proto, đóng gói chúng dưới dạng một lọ riêng biệt và gửi nó đến các khách hàng mục tiêu của dịch vụ - về cơ bản đó là cách tiếp cận mặc định mà tôi đoán.

  2. Thực hiện tương tự nhưng cũng bao gồm các trình bao bọc thủ công (không phải các lớp con!) Xung quanh mỗi thông báo thực hiện hợp đồng hỗ trợ ít nhất hai phương thức này (T là lớp trình bao bọc, V là lớp thông báo (sử dụng cú pháp chung nhưng đơn giản hóa cú pháp để đơn giản) :

    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());
    }
    

Một lợi thế tôi thấy với (2) là tôi có thể che giấu sự phức tạp được giới thiệu V.newBuilder().addField().build()và thêm một số phương thức có ý nghĩa như isOpenForTrade()hoặc isAddressInFreeDeliveryZone()vv trong trình bao bọc của mình. Ưu điểm thứ hai tôi thấy với (2) là các khách hàng của tôi xử lý các đối tượng bất biến (điều mà tôi có thể thực thi trong lớp trình bao bọc).

Một nhược điểm tôi thấy với (2) là tôi sao chép mã và phải đồng bộ hóa các lớp trình bao bọc của mình với các tệp .proto.

Có ai có kỹ thuật tốt hơn hoặc phê bình thêm về bất kỳ trong hai cách tiếp cận?


* Bằng cách đóng gói một trường lặp lại, tôi có nghĩa là các tin nhắn như trường này:

message ItemList {
    repeated item = 1;
}

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

thay vì các tin nhắn như thế này:

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

Tôi thích cái sau nhưng rất vui khi nghe những lập luận chống lại nó.


Tôi cần thêm 12 điểm để tạo thẻ mới và tôi nghĩ protobuf nên là thẻ cho bài đăng này.
Apoorv Khurasia

Câu trả lời:


13

Nơi tôi làm việc, quyết định được đưa ra để che giấu việc sử dụng protobuf. Chúng tôi không phân phối các .prototệp giữa các ứng dụng, nhưng thay vào đó, bất kỳ ứng dụng nào hiển thị giao diện protobuf đều xuất một thư viện máy khách có thể nói chuyện với nó.

Tôi mới chỉ làm việc trên một trong những ứng dụng phơi bày protobuf này, nhưng trong đó, mỗi thông điệp protobuf tương ứng với một số khái niệm trong miền. Đối với mỗi khái niệm, có một giao diện Java bình thường. Sau đó, có một lớp trình chuyển đổi, có thể lấy một thể hiện của một triển khai và xây dựng một đối tượng thông báo phù hợp, và lấy một đối tượng thông báo và xây dựng một thể hiện của một triển khai giao diện (như nó xảy ra, thường là một lớp ẩn danh hoặc cục bộ đơn giản được định nghĩa bên trong bộ chuyển đổi). Các lớp thông báo và bộ chuyển đổi được tạo bởi protobuf cùng nhau tạo thành một thư viện được cả ứng dụng và thư viện khách sử dụng; thư viện khách thêm một lượng nhỏ mã để thiết lập kết nối và gửi và nhận tin nhắn.

Các ứng dụng khách sau đó nhập thư viện máy khách và cung cấp các cài đặt của bất kỳ giao diện nào chúng muốn gửi. Thật vậy, cả hai bên làm điều sau.

Để làm rõ, điều đó có nghĩa là nếu bạn có chu kỳ phản hồi yêu cầu trong đó khách hàng đang gửi lời mời dự tiệc và máy chủ đang phản hồi với RSVP, thì những điều liên quan là:

  • Tin nhắn bên tham gia, được viết trong .prototập tin
  • PartyInvitationMessage lớp, được tạo bởi protoc
  • PartyInvitation giao diện, được xác định trong thư viện chia sẻ
  • ActualPartyInvitation, một triển khai cụ thể PartyInvitationđược xác định bởi ứng dụng khách (không thực sự được gọi như vậy!)
  • StubPartyInvitation, một triển khai đơn giản PartyInvitationđược xác định bởi thư viện chia sẻ
  • PartyInvitationConverter, có thể chuyển đổi a PartyInvitationthành a PartyInvitationMessagevà a PartyInvitationMessagethành aStubPartyInvitation
  • Tin nhắn RSVP, được ghi trong .prototệp
  • RSVPMessage lớp, được tạo bởi protoc
  • RSVP giao diện, được xác định trong thư viện chia sẻ
  • ActualRSVP, một triển khai cụ thể RSVPđược xác định bởi ứng dụng máy chủ (cũng không thực sự được gọi như vậy!)
  • StubRSVP, một triển khai đơn giản RSVPđược xác định bởi thư viện chia sẻ
  • RSVPConverter, có thể chuyển đổi RSVPthành một RSVPMessage, và RSVPMessagemộtStubRSVP

Lý do chúng tôi có các triển khai thực tế và còn sơ khai là vì các triển khai thực tế nói chung là các lớp thực thể được ánh xạ JPA; máy chủ sẽ tạo và duy trì chúng hoặc truy vấn chúng từ cơ sở dữ liệu, sau đó chuyển chúng đến lớp protobuf để truyền đi. Không cảm thấy rằng việc tạo ra các thể hiện của các lớp đó ở phía bên nhận của kết nối là không phù hợp, vì chúng sẽ không bị ràng buộc với bối cảnh dai dẳng. Hơn nữa, các thực thể thường chứa nhiều dữ liệu hơn là được truyền qua dây dẫn, do đó thậm chí không thể tạo ra các đối tượng nguyên vẹn ở phía bên nhận. Tôi không hoàn toàn tin rằng đây là một động thái đúng đắn, bởi vì nó đã để lại cho chúng tôi thêm một lớp cho mỗi tin nhắn so với những gì chúng tôi có.

Thật vậy, tôi không hoàn toàn tin rằng sử dụng protobuf hoàn toàn là một ý tưởng tốt; nếu chúng ta bị mắc kẹt với RMI cũ và tuần tự hóa, chúng ta sẽ không phải tạo ra gần như nhiều đối tượng. Trong nhiều trường hợp, chúng ta chỉ có thể đánh dấu các lớp thực thể của mình là tuần tự hóa và tiếp tục với nó.

Bây giờ, tôi đã nói tất cả những điều đó, tôi có một người bạn làm việc tại Google, trên một cơ sở mã hóa sử dụng protobuf rất nhiều để liên lạc giữa các mô-đun. Họ thực hiện một cách tiếp cận hoàn toàn khác: họ hoàn toàn không bao gồm các lớp thông báo được tạo và nhiệt tình chuyển chúng sâu (ish) vào mã của họ. Đây được coi là một điều tốt, bởi vì nó đơn giản là cách giữ cho giao diện linh hoạt. Không có mã giàn giáo để giữ đồng bộ khi thông báo phát triển và các lớp được tạo cung cấp tất cả các hasFoo()phương thức cần thiết để nhận mã để phát hiện sự hiện diện hoặc vắng mặt của các trường đã được thêm vào theo thời gian. Mặc dù vậy, hãy nhớ rằng những người làm việc tại Google có xu hướng (a) khá thông minh và (b) hơi hạt dẻ.


Tại một thời điểm, tôi đã xem xét việc sử dụng JBoss serialization như là một sự thay thế thả xuống ít nhiều cho việc tuần tự hóa tiêu chuẩn. Nó đã khá nhanh hơn rất nhiều. Không nhanh như protobuf, mặc dù.
Tom Anderson

Tuần tự hóa JSON bằng cách sử dụng jackson2 cũng khá nhanh. Điều tôi ghét về GBP là sự trùng lặp không cần thiết của các lớp giao diện chính.
Apoorv Khurasia

0

Để thêm vào câu trả lời của Andersons, có một dòng tốt trong việc lồng các thông điệp lồng vào nhau một cách khéo léo và lạm dụng nó. Vấn đề là mỗi thông điệp tạo ra một lớp mới đằng sau hậu trường và tất cả các loại trình truy cập và xử lý dữ liệu. Nhưng có một chi phí cho điều đó nếu bạn phải sao chép dữ liệu hoặc thay đổi một giá trị hoặc so sánh các tin nhắn. Những quy trình đó có thể gây ra rất chậm và đau đớn nếu bạn có nhiều dữ liệu hoặc bị ràng buộc bởi thời gian.


2
Điều này đọc giống như một bình luận tiếp tuyến, xem Cách trả lời
gnat

1
À không phải vậy: cuối cùng không có tên miền, tất cả đều là vấn đề về từ ngữ (ồ tôi đang phát triển tất cả mọi thứ trong C ++ nhưng điều này không phải là vấn đề)
Marko Bencik 22/07/18
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.