协议缓冲区和 OO 设计

Posted

技术标签:

【中文标题】协议缓冲区和 OO 设计【英文标题】:Protocol buffer and OO design 【发布时间】:2012-08-02 08:19:06 【问题描述】:

我在客户端-服务器架构中使用协议缓冲区作为有线数据格式。域对象(java bean)将经历以下生命周期。

    用于客户端业务逻辑 转换为 protobuf 格式 传输到服务器 转换回域对象 用于服务器端业务逻辑

ProtoBuf 文档中的"Protocol Buffers and O-O Design" 部分建议将生成的类包装在适当的域模型中。

我想找出最好的方法。

例如我有一个简单的原型定义。

package customer;

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

message Customer 
    required string name = 1;
    optional string address = 2;

这就是定义域模型的方式。如您所见,数据完全存储在 proto builder 对象中。

package com.example;

public class CustomerModel

    private CustomerProtos.Customer.Builder builder = CustomerProtos.Customer.newBuilder();

    public String getName()
    
        return builder.getName();
    

    public void setName(String name)
    
        builder.setName(name);
    

    public String getAddress()
    
        return builder.getAddress();
    

    public void setAddress(String address)
    
        builder.setAddress(address);
    

    public byte[] serialize()
    
        return builder.build().toByteArray();
    


这是一个好习惯吗?因为这些对象在生命周期的各个阶段都会用到,而我们只在客户端-服务器传输阶段需要protocolbuf格式。

在原型定义复杂且嵌套的情况下,访问原型构建器类的 getter/setter 方法时是否存在任何性能问题?

【问题讨论】:

你找到了解决这个问题的好方法吗(proto vs OO)? 【参考方案1】:

我没有使用协议缓冲区的经验,但我建议实施为特定序列化/传输框架量身定制的域对象。你以后可能会后悔。

软件应用程序的域对象和逻辑应尽可能独立于特定的实现问题(在您的情况下是序列化/传输),因为您希望您的域易于理解并且将来可重用/可维护。

如果您想定义独立于序列化/传输的域对象,您有两种选择:

    在序列化/传输之前,将信息复制到协议 缓冲特定对象并将它们发送到您的服务器。你在那里 必须将信息复制回您的域对象。 使用非协议序列化库,如Kryo 或 ProtoStuff 直接将您的域对象转移到 服务器。

选项 1 的缺点是您的域被定义了两次(这对于修改来说是不可取的)和信息的复制(这会产生容易出错和不可维护的代码)。

选项 2 的缺点是您丢失了 schema evolution(尽管 ProtoStuff 显然是 supports it)并且完整的(可能很大)对象图被序列化和传输。虽然您可以在序列化/传输之前修剪对象图(手动或使用JGT)。

【讨论】:

完全同意@Barry。如果将相同的 protobuf 生成的 pojo 用作构建逻辑的 domian 对象,它很快就会变得混乱。正如所建议的,它基本上只是为了方便的服务-服务通信,与语言无关,仅此而已。有像 wrapper 这样的解决方案,但是为每个 protobuf 对象加上 imgine wrapper,这完全是一团糟。在我看来,最好为每个 proto 使用转换器工具或使用任何映射器库(如果有)。【参考方案2】:

我们创建了protobuf-converter 来解决将域模型对象转换为 Google Protobuf 消息的问题,反之亦然。

如何使用:

必须转化为protobuf消息的领域模型类必须满足条件:

类必须由 @ProtoClass 注释标记,其中包含 相关 protobuf 消息类的参考。 类字段必须由 @ProtoField 注释标记。这些字段必须有 getter 和 setter。

例如:

@ProtoClass(ProtobufUser.class)
public class User 

    @ProtoField
    private String name;
    @ProtoField
    private String password;

    // getters and setters for 'name' and 'password' fields
    ...

将用户实例转换为相关protobuf消息的代码:

User userDomain = new User();
...
ProtobufUser userProto = Converter.create().toProtobuf(ProtobufUser.class, userDomain);

后向转换代码:

User userDomain = Converter.create().toDomain(User.class, userProto);

对象列表的转换类似于单个对象的转换。

【讨论】:

是否有适用于 Dart Lang 的等效解决方案?或者,出现了任何新的最佳实践来转换消息/域对象,反之亦然?

以上是关于协议缓冲区和 OO 设计的主要内容,如果未能解决你的问题,请参考以下文章

协议缓冲区如何比 XML 和 JSON 快?

5. 设计应用层协议(粘包)

红外协议解析

第二十七天- 网络通信协议 TCP UDP 缓冲区

协议缓冲区中的多态性 3

6LwIP协议规范翻译——缓冲及内存管理