协议缓冲区中的多态性 3
Posted
技术标签:
【中文标题】协议缓冲区中的多态性 3【英文标题】:Polymorphism in Protocol Buffers 3 【发布时间】:2017-03-27 07:23:42 【问题描述】:目前的设计
我正在重构一些为用户返回事件源的现有 API 代码。该 API 是一个普通的 RESTful API,当前的实现只是查询一个 DB 并返回一个提要。
代码又长又麻烦,所以我决定将提要生成转移到将从 API 服务器调用的微服务。
新设计
为了解耦,我认为数据可能会作为 Protobuf 对象从 API 服务器到微服务来回移动。这样,我可以在任一端更改编程语言,并且仍然享受 protobuf 的类型安全和纤细的尺寸。
问题
Feed 包含多种类型(例如点赞、图片和语音消息)。将来,可以添加新类型。例如,它们都共享一些属性时间戳和标题 - 但除此之外,它们可能完全不同。
在经典 OOP 中,解决方案很简单 - 一个基类 FeedItem
继承所有提要项,一个 Feed
类包含一系列 FeedItem
类。
如何在 Protocol Buffers 3 中表达多态性的概念,或者至少在一个列表中启用不同类型的消息?
什么have I checked
Oneof
: "一个不能重复"。
Any
:太宽泛了(就像 Java 的 List<Object>
。
【问题讨论】:
您可以通过将 oneof 放在重复的子消息中来重复它。 最近在 protobuf 邮件列表上有一个关于此的线程:groups.google.com/d/msg/protobuf/ojpYHqx2l04/bfyAhqBxAQAJ 我认为这是一个常见问题,通常的解决方案是您应该获取公共数据并将其放入一条消息中不同的类型都可以只包含一个子消息。 @AdamCozzette 太好了,这就是我想要的。看来我们不能做得比这更好了。关心重写线程的要点作为答案(我很乐意接受),还是你想让我这样做? 我今天有点忙,如果你能做到,那就太好了! 特别是处理对我来说很有趣。如何避免没有继承且无法预先“窥视”消息的切换案例? 【参考方案1】:序列化协议的答案是使用基于鉴别器的多态性。传统的面向对象继承是一种具有一些非常糟糕特征的形式。在 OpenAPI 等较新的协议中,这个概念更加清晰。
让我解释一下它是如何与 proto3 一起工作的
首先你需要声明你的多态类型。假设我们研究经典的动物物种问题,其中不同物种具有不同的属性。我们首先需要为所有能够识别物种的动物定义一个根类型。然后我们声明一个扩展基本类型的 Cat 和 Dog 消息。注意判别器species
被投影到所有3个中:
message BaseAnimal
string species = 1;
message Cat
string species = 1;
string coloring = 10;
message Dog
string species = 1;
int64 weight = 10;
这是一个简单的 Java 测试,用于演示实际工作方式
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
// Create a cat we want to persist or send over the wire
Cat cat = Cat.newBuilder().setSpecies("CAT").setColoring("spotted")
.build();
// Since our transport or database works for animals we need to "cast"
// or rather convert the cat to BaseAnimal
cat.writeTo(os);
byte[] catSerialized = os.toByteArray();
BaseAnimal forWire = BaseAnimal.parseFrom(catSerialized);
// Let's assert before we serialize that the species of the cat is
// preserved
assertEquals("CAT", forWire.getSpecies());
// Here is the BaseAnimal serialization code we can share for all
// animals
os = new ByteArrayOutputStream(1024);
forWire.writeTo(os);
byte[] wireData = os.toByteArray();
// Here we read back the animal from the wire data
BaseAnimal fromWire = BaseAnimal.parseFrom(wireData);
// If the animal is a cat then we need to read it again as a cat and
// process the cat going forward
assertEquals("CAT", fromWire.getSpecies());
Cat deserializedCat = Cat.parseFrom(wireData);
// Check that our cat has come in tact out of the serialization
// infrastructure
assertEquals("CAT", deserializedCat.getSpecies());
assertEquals("spotted", deserializedCat.getColoring());
整个技巧是 proto3 绑定保留他们不理解的属性并根据需要对其进行序列化。通过这种方式,可以实现一个 proto3 转换(转换),它可以在不丢失数据的情况下更改对象的类型。
请注意,“proto3 cast”是非常不安全的操作,只能在对鉴别器进行适当检查后应用。在我的示例中,您可以毫无问题地将猫扔给狗。下面的代码失败了
try
Dog d = Dog.parseFrom(wireData);
fail();
catch(Exception e)
// All is fine cat cannot be cast to dog
当同一索引的属性类型匹配时,可能会出现语义错误。在示例中,我的索引 10 在 dog 中是 int64 或在 cat proto3 中是 string 将它们视为不同的字段,因为它们在线路上的类型代码不同。在某些情况下,类型可能是字符串,而结构 proto3 实际上可能会抛出一些异常或产生完整的垃圾。
【讨论】:
传统继承不好有很多原因,这就是为什么 proto3 和 XML 之后的更新序列化协议不支持它的原因。考虑一个简单的问题,其中序列化端可以升级以使用新的更具体的异常类型,例如FileNotFound
从 IOError
扩展而来。消息的读者可能知道如何IOError
,但在网络上仅找到FileNotFound
并不能告诉读者这是IOError
的后代,因此读者会将其视为通用未知异常,可能会导致错误系统。类似的问题很少。以上是关于协议缓冲区中的多态性 3的主要内容,如果未能解决你的问题,请参考以下文章