15

我们开发了一个分布式系统,该系统由以不同编程语言(C++、C# 和 Python)实现的组件构建,并通过网络相互通信。系统中的所有组件都以相同的业务概念运行,并且也根据这些概念相互通信。

因此,我们在以下两个挑战中苦苦挣扎:

  1. 使我们的业务概念在这三种语言中的表达保持同步
  2. 跨这些语言的我们的业务概念的序列化/反序列化

对于这个问题,一个简单的解决方案就是定义相同的数据结构(和序列化代码)三次(对于 C++、C# 和 Python)。

不幸的是,这个解决方案有严重的缺点:

  • 它会产生很多“代码重复”</li>
  • 它需要大量的跨语言集成测试来保持一切同步

我们考虑的另一个解决方案是基于 ProtoBufs 或 Thrift 等框架。这些框架有一个内部语言,其中定义了业务概念,然后这些概念在 C++、C# 和 Python 中的表示(连同序列化逻辑)由这些框架自动生成。

虽然这个解决方案没有上述问题,但它还有另一个缺点:由这些框架生成的代码将代表底层业务概念的数据结构和序列化/反序列化这些数据结构所需的代码耦合在一起。

我们觉得这污染了我们的代码库——我们系统中使用这些自动生成的类的任何代码现在都“熟悉”这种序列化/反序列化逻辑(严重的抽象泄漏)。

我们可以通过用我们的类/接口包装自动生成的代码来解决它,但这让我们回到了幼稚解决方案的缺点。

谁能推荐一个解决所描述问题的解决方案?

4

7 回答 7

5

列夫,你可能想看看ICE。它提供面向对象的 IDL,并映射到您使用的所有语言(C++、Python、.NET(据我所知,所有 .NET 语言,不仅仅是 C#))。尽管 ICE 是一个中间件框架,但您不必遵循它的所有策略。

特别是在您的情况下,您可能希望在 ICE IDL 中定义组件的接口,并将它们作为代码的一部分进行维护。然后,您可以生成代码作为构建例程的一部分并从那里开始工作。或者,您可以使用 ICE 为您提供的更多功能。

ICE 支持 C++ STL 数据结构并支持继承,因此它应该为您提供足够强大的形式主义,以便随着时间的推移逐渐构建您的系统,并具有良好的可维护性。

于 2012-08-05T06:45:18.133 回答
2

系统中的所有组件都以相同的业务概念运行,并且也根据这些概念相互通信。

当我说对了时,您已经将系统分成不同的部分,通过定义明确的接口进行通信。但是您的接口共享您称为“业务概念”的数据结构(如果没有看到示例就很难理解),并且由于必须为您的所有三种语言构建这些接口,因此您在保持它们“同步”时遇到了问题。

当保持接口同步出现问题时,很明显你的接口太宽泛了。有不同的可能原因,有不同的解决方案。

可能的原因 1 - 你过度概括了你的界面概念。如果是这种情况,请在此处重新设计:放弃泛化并创建尽可能广泛的接口。

可能的原因2:用不同语言编写的部分没有处理单独的业务案例,它们之间可能有“水平”分区,但没有垂直分区。如果是这种情况,您就无法避免界面的广泛性。

如果原因 2 是您的问题,代码生成在这里可能是正确的方法。如果现有的代码生成器不满足您的需求,您为什么不自己编写呢?例如,将接口定义为 C# 中的类,引入一些元属性并在代码生成器中使用反射来在生成相应的 C++、Python 以及“实际使用的”C# 代码时再次提取信息。如果您需要带有或不带有序列化的不同变体,也可以生成它们。工作生成器的工作量不应超过几天(YMMV 取决于您的要求)。

于 2012-08-04T10:04:57.643 回答
2

嗯,曾几何时,MS 试图用 IDL 解决这个问题。好吧,实际上它试图解决的不仅仅是定义数据结构,但是,无论如何,这一切都已成为过去,因为这些天没有人会走上 COM 路线。

一个值得关注的选项是SWIG,它应该能够移植数据结构以及跨语言的实际调用。我自己没有这样做,但它有可能不会像 protobufs 那样紧密地耦合序列化和数据结构。

但是,你真的应该考虑一下前面提到的耦合到底是不是一件坏事。什么是您的理想解决方案?据说它做了两件事:它基于一个定义生成跨多种语言的兼容数据结构,它还提供序列化代码将它们拼接在一起 - 但在一个单独的抽象层中。这个想法是,如果有一天您决定使用不同的序列化方法,您可以直接切换该层,而无需重新定义所有数据结构。所以考虑一下 - 期望有一天只切换序列化代码而不接触接口到底有多现实?在大多数情况下,序列化格式是最永久的设计选择,因为您通常会遇到向后兼容性等问题。

现在让我们假设存在这样一个工具,它将数据结构生成与序列化分开。可以说,2 年后,您决定需要一种完全不同的序列化方法。除非此工具还支持可插入的序列化格式,否则您无论如何都需要开发该层,以便将现有结构缝合到新的序列化解决方案 - 这与完全选择新包一样多。因此,能够满足您的要求的唯一真正可行的解决方案是不仅支持跨所有语言的数据类型定义和代码生成,而且不仅与序列化无关,但也已经准备好实现您想要切换到的未来序列化格式 - 因为如果它只对序列化格式不可知,这意味着您仍然有自己实现它的任务 - 总而言之语言——这并不比重新定义一些数据结构少。

所以我的观点是,序列化和数据类型定义如此频繁地结合在一起是有原因的——它只是最常见的用例。我会仔细研究一下您希望使用所需的抽象级别能够实现什么,想想开发这样的解决方案需要做多少工作以及是否值得。我确定这些工具可以做到这一点,顺便说一句 - 可能只是昂贵的专有类型,每个许可证花费 1 万美元 - 我认为同样的论点适用于那里 - 它可能只是过度工程。

于 2012-08-04T14:55:27.990 回答
1

我同意 Tristan Reid(包装业务逻辑)。实际上,几个月前我遇到了同样的问题,然后我偶然发现了《Unix 编程艺术》一书(可在线免费获得)。引起我注意的是将策略与机制(即接口与引擎)分离的哲学。现代编程环境(例如 NET 平台)试图将所有内容集成到单个域下。在那些日子里,我被要求开发一个必须满足以下要求的 WEB 应用程序:

  1. 它必须轻松适应用户界面的未来趋势,而无需更改核心算法。

  2. 它必须可以通过不同的界面访问:Web、命令行和桌面 GUI。

  3. 它必须在 Windows 和 Linux 上运行。

我打赌完全在 C/C++ 中开发机制(引擎)并使用本机操作系统库(POSIX 或 WinAPI)和良好的开源库(postgresql、xml 等)。我将引擎模块开发为命令行程序,最终实现了 2 个接口:Web(使用 PHP+JQuery 框架)和桌面(NET 框架)。这两个接口都与机制无关:它们只是通过调用诸如 Windows 中的 CreateProcess() 或 UNIX 中的 fork() 之类的函数来启动核心模块可执行文件,并使用管道来监视它们的进程。

我并不是说 UNIX 编程哲学适用于所有目的,但我从那时起应用它并取得了很好的效果,也许它也对你有用。选择一种语言来实现该机制,然后使用另一种使界面设计变得容易的语言。

于 2012-08-04T00:08:18.313 回答
0

我将通过使用有关您的域实体的某种元信息(XML 或 DSL,取决于复杂性)来实现这一点,然后为每种语言生成代码。这将减少(手动)代码重复。

于 2012-08-04T09:31:30.257 回答
0

您可以使用诸如 UML 建模器之类的工具对这些数据结构进行建模(想到 Enterprise Architect,因为它可以为所有 3 个生成代码。)然后直接从模型中为每种语言生成代码。

虽然我会仔细查看之前关于使用 XSD 的评论。

于 2012-08-03T23:23:35.940 回答
0

您可以将业务逻辑包装为 Web 服务,并使用所有三种语言调用它 - 只需一个实现。

于 2012-08-03T21:04:20.827 回答