2011-11-08 26 views
6

Tôi đang chỉ định giao thức trong protocol buffers. Tầng vận chuyển đang khai thác hỗ trợ Bộ đệm Giao thức Netty - ý nghĩa là số ProtobufDecoder của Netty chấp nhận một và chỉ một loại, là MessageLite.Cách tốt nhất để chỉ định Protobuf để sử dụng với Netty (tốt nhất là sử dụng hỗ trợ protobuf được tích hợp sẵn)

Bây giờ, tôi muốn gửi nhiều loại thông báo khác nhau xuống kênh này, mỗi loại phụ có thông tin có cấu trúc được liên kết với nó. Bộ đệm giao thức không có cơ chế thừa kế, vì vậy tôi đang sử dụng một loại thành phần. Tôi không chắc liệu tôi có đi đúng hướng không.

Cách tiếp cận của tôi là phân loại các sự kiện khác nhau của tôi bằng enum và đóng gói sự khác biệt của chúng bằng cách sử dụng các thành viên tùy chọn. Xem số .proto bên dưới của tôi, tôi đã đơn giản hóa nó vì mục đích rõ ràng.

Vấn đề của tôi ở đây là mã nhận cần phải tạo liên kết giữa EventType.ERROR và ErrorEventDetail. Điều này chỉ cảm thấy một chút vụng về.

Giản Events.proto:

package events; 

option java_package = "com.example"; 
option java_outer_classname = "EventProtocol"; 

message Event { 
    enum EventType { 
    START = 0; 
    DELEGATE = 1; 
    ERROR = 2; 
    STOP = 3; 
    } 
    required events.Event.EventType event_type = 1 [default = START]; 
    required int32 id = 2; 
    required int64 when = 3; 
    optional StartEventDetail start_event_detail = 4; 
    optional DelegateEventDetail delegate_event_detail = 5; 
    optional ErrorEventDetail error_event_detail = 6; 
    optional StopEventDetail stop_event_detail = 7; 
} 

message StartEventDetail { 
    required string object_name = 1; 
} 

message DelegateEventDetail { 
    required int32 object_id = 2; 
    required string task = 3; 
} 

message ErrorEventDetail { 
    required string text = 1; 
    required int32 error_code = 2; 
    optional Event cause = 3; 
} 

message StopEventDetail { 
    required int32 object_id = 2; 
} 

là tối ưu này? Tôi có nên sử dụng tốt hơn việc mở rộng bằng cách nào đó hoặc có thể sử dụng một số cách khác là enum?

Hoặc thậm chí, tôi có nên tạo toàn bộ OneToOneDecoder mới có thể xác định loại thư theo một loại tiêu đề nào đó không? Tôi có thể làm được điều này, nhưng tôi không muốn ...

Cảm ơn

Trả lời

6

Có vẻ như bạn là khá chặt chẽ/đã sử dụng một trong những kỹ thuật protobufs của Google mà gọi Union Types

Các ý chính là bạn phải một type lĩnh vực chuyên dụng, mà bạn sẽ "chuyển đổi" trên để biết được thông điệp tới nhận được:

message OneMessage { 
    enum Type { FOO = 1; BAR = 2; BAZ = 3; } 

    // Identifies which field is filled in. 
    required Type type = 1; 

    // One of the following will be filled in. 
    optional Foo foo = 2; 
    optional Bar bar = 3; 
    optional Baz baz = 4; 
} 

nơi Foo, Bar và Baz được/có thể được định nghĩa trong các tập tin khác như thông điệp riêng biệt. Và bạn có thể chuyển vào loại để có được tải trọng thực tế (đó là Scala, nhưng bạn có thể làm điều tương tự với switch Java):

OneMessage.getType match { 

    case OneMessage.Type.FOO => 

    val foo = OneMessage.getFoo 
    // do the processing 
    true 

    case OneMessage.Type.BAR => 

    val bar = OneMessage.getBar 
    // do the processing 
    true 

    case OneMessage.Type.BAZ => 

    val baz = OneMessage.getBaz 
    // do the processing 
    true 

} 
+0

cảm ơn rất nhiều, tôi đã bỏ lỡ tài liệu về Loại liên kết. Thật tốt khi biết tôi đang đi đúng hướng. Chúc mừng – laher

+0

Tôi đã thực sự thay đổi định nghĩa của mình ngay bây giờ, bằng cách đọc lên các loại Liên minh. Loại 'Liên minh' của tôi không còn chứa bất kỳ thứ gì ngoài trường Loại cộng với 'loại phụ' tùy chọn. Các trường phổ biến ('id' và 'when' trong ví dụ của tôi) hiện được lưu giữ trong một thông báo 'EventCommon', được tạo thành mỗi 'subtype'. Vì vậy, bây giờ mỗi 'subtype' chứa tất cả các dữ liệu cần thiết. Điều này dường như làm việc tốt hơn. – laher

0

phương pháp khác là sử dụng các cơ chế mở rộng mà Protobuf đang hỗ trợ. Tôi đang sử dụng cách tiếp cận này trong các tình huống mà loại công đoàn quá lớn.

3

tôi ban đầu được giải quyết cùng một vấn đề bằng cách sử dụng cơ chế mở rộng, mà tôi ghi lại here

Nhưng tôi tìm thấy mã trong Java cần thiết để đối phó với phần mở rộng là khủng khiếp xấu xí và tiết, vì vậy tôi chuyển sang phương pháp Liên minh như mô tả . Mã này sạch hơn nhiều khi mã Java được tạo ra cung cấp một cách để nhận và xây dựng mỗi thư trong một lần.

Tôi sử dụng hai cơ chế để quyết định thông điệp tùy chọn cần trích xuất. Tôi sử dụng phương thức chuyển đổi cũng được mô tả trong câu trả lời khác khi hiệu suất là cần thiết và tôi sử dụng phương thức phản chiếu khi hiệu suất không phải là vấn đề và tôi không muốn phải duy trì câu lệnh chuyển đổi, tôi chỉ tạo một xử lý (Message) cho mỗi thông điệp. Một ví dụ về phương pháp phản chiếu được đưa ra dưới đây, trong trường hợp của tôi java wrapper là một lớp được gọi là Lệnh, và được giải mã bởi Netty cho tôi.Đầu tiên nó cố gắng tìm một trình xử lý có thông báo cụ thể dưới dạng tham số, nếu không, nó sẽ gọi phương thức sử dụng tên trường hợp lạc đà. Đối với điều này để làm việc Enum phải là tên gạch dưới của thông báo trường hợp lạc đà.

// Helper that stops me having to create a switch statement for every command 
// Relies on the Cmd enum naming being uppercase version of the sub message field names 
// Will call the appropriate handle(Message) method by reflection 
// If it is a command with no arguments, therefore no sub message it 
// constructs the method name from the camelcase of the command enum 
private MessageLite invokeHandler(Commands.Command cmd) throws Exception { 
    Commands.Command.Cmd com= cmd.getCmd(); 
    //String name= CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_UNDERSCORE, com.name()); 
    String name= com.name().toLowerCase(); 
    jlog.debug("invokeHandler() - Looking up {} from {}", name, com.name()); 
    FieldDescriptor field= Commands.Command.getDescriptor().findFieldByName(name); 
    if(field != null) { 
     // if we have a matching field then extract it and call the handle method with that as a parameter 
     Object c = cmd.getField(field); 
     jlog.debug("invokeHandler() - {}\n{}", c.getClass().getCanonicalName(), c); 
     Method m = getClass().getDeclaredMethod("handle", String.class, c.getClass()); 
     return (MessageLite) m.invoke(this, cmd.getUser(), c); 
    } 
    // else we call a method with the camelcase name of the Cmd, this is for commands that take no arguments other than the user 
    String methodName= "handle"+CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, com.name()); 
    jlog.debug("invokeHandler() - using method: {}", methodName); 
    Method m = getClass().getDeclaredMethod(methodName, String.class); 
    return (MessageLite) m.invoke(this, cmd.getUser()); 
} 
+0

sử dụng phản ánh tốt đẹp! : D –

Các vấn đề liên quan