我对这个问题的篇幅事先表示歉意,它需要一些解释。我会尽量把它弄清楚。
我正在开发和维护一个移动应用程序(F#,Xamarin),它可以与web (ASP.NET Core,F#)通信。服务器端API仅用于与应用程序通信,并根据需要执行数据库操作和其他HTTP请求。可以说,领域模型在应用程序和API中大致相同。例如,应用程序和API都有一个CardId的概念,对于什么才是有效的卡ID有相同的概念。
由于应用程序不能立即升级,我必须对API进行版本化,以便过时的应用程序版本的用户仍然可以使用该服务。目前,应用程序和服务器通过序列化/反序列化简单的DTO类类型(通过HTTP作为JSON有效负载发送)进行通信,该程序和API都引用了一个公共程序集。例如,卡ID表示为一个简单的字符串。这个共享程序集中的类型是版本化的,应用程序和API在这些类型和它们内部使用的相应类型之间进行转换。
这意味着应用程序和服务器之间共享的任何概念/类型至少需要3种类型(一个版本)--一个在应用程序中,一个在API中,一个在共享程序集中被序列化/反序列化--以及2-4转换器( app/API中每个转换器1到2个,这取决于它是否只需要接收/发送类型或两者)。这是非常健壮的,因为它将内部表示(以及处理它们的逻辑)与应用程序和服务器之间的通信分离开来,但这是一个非常麻烦的例子。此外,它还导致我经常从应用程序中切割逻辑--例如,应用程序中的卡片ID只是被建模为一个简单的字符串,API对它进行验证,并且可以返回一个错误代码,指示“无效的卡片ID”。
现在,我真正想做的是简单地在客户机和服务器之间共享对象。如果他们能以某种方式分享记忆(荒谬,但解释了我的观点),那将大大简化事情。由于客户端和服务器都使用相同的平台/语言(.NET/F#),而且这种情况不太可能发生改变(如果是这样的话,我们总是可以回到当前的解决方案),所以我已经考虑将一个类型的所有三个版本(app、API和shared)合并成一个类型(当然是版本化的),然后简单地序列化/反序列化。例如,使用某种二进制序列化(我对此没有经验)。
这确保了服务器和应用程序真正讲的是同一种语言,并且平等对待平等的概念。例如,我可以通过构造(例如上面提到的CardID)来共享具有某些不变量的保证的复杂对象,并且知道当我在另一端反序列化它时,所有不变量仍然保持不变,因为它是完全相同的对象。然后我也可以消除一些服务器端的检查和错误,比如“无效卡ID”,因为应用程序一开始就无法构造和发送无效的身份证ID。
这也意味着,在我的应用程序和API中,都会引用特定版本的对象,但我认为这不会比当前的解决方案更糟糕。
然而,我无法摆脱这样一种感觉,即我会以某种方式自命不凡,或者版本控制会更加困难和脆弱,尽管我无法确定为什么我会有这种感觉(也许我只是觉得用专用的对象来实现序列化/反序列化,并从内部转换到/从内部转换,但我仍然依赖于这些DTO对象的序列化/反序列化,那么为什么不一直这样做呢?)因此,问题是:
根据上述解释,在客户机和服务器之间使用二进制序列化等共享对象是一个很好的选择,还是我忽略了这种方法的一些明显问题?我是否完全错过了另一个更好的选择?
发布于 2017-12-07 19:44:18
二进制序列化可能非常脆弱。我只会在非常特殊的情况下使用二进制序列化,在这种情况下,您需要这种序列化提供的经济(例如文件传输),您不需要通过防火墙,您可以完全控制通信的两端,而且数据格式很少改变。
在线路上发送任意JSON;使用Newtonsoft.JSON将其反序列化为dynamic、键/值对字典或ExpandoObject;然后将这些任意字段映射到具体表示或直接使用它们;这是非常合理的做法。潜在地,您可以在JSON顶部包含一些元数据,这些元数据将提示正在传输的数据类型。
您也可以尝试类似于协议缓冲区之类的东西。
如果您想坚持静态类型,那么在客户机和服务器之间共享DTO类是不可避免的,除非您想要编写一个产生正确对象的工厂方法。
发布于 2017-12-07 11:40:00
我有点被你的设置搞糊涂了,让我把我通常的做法说出来:
当然,从技术上讲,一切都是版本化的。但是对于客户端和服务器的匹配,真正重要的是通信。
当您有多个版本时,每个版本都有不同的结束点。(版本化的客户端知道并自动处理的)
现在,这里的主要区别似乎是,我在客户机和服务器上有不同的逻辑。我倾向于使用ADM方法并将此逻辑编码为服务,但您也可以将其放入对象中。
我不需要在服务器和客户端之间共享任何逻辑,因为它们所做的事情不同。如果我确实有一些共享逻辑,比如信用卡验证,我会将它放在一个单独的服务/dll中,并共享它。根据需要将其注入其他服务。
现在,我可以将序列化的数据直接放入共享对象/服务中,但我不会真正保存任何代码。
而不是在客户机和服务器中进行反/序列化,而是在对象构造函数中使用该代码。映射到对象的私有成员,而不是DTO的公共成员。
这将使我的反/序列化逻辑与我的业务逻辑类紧密地结合在一起,而我所保存的只是一些类定义代码。
我的感觉是,您的版本控制复杂性来自于运行能够处理多个版本的服务器代码。我建议您拆分这一点,为每个版本创建一个服务器部署,这样您的代码只需要担心一个版本。当客户端更新缓慢进行时,您可以解压缩旧版本。
然后,与多个转换器不同,您只有多个版本的同一个转换器,您的代码库和测试也大大简化了。
JSON与二进制与XML或其他什么并不重要。.Net是内存管理的,所以(据我所知!)没有办法获取二进制流,将其直接写入内存,然后像调用已实例化的类一样调用该内存。
你不能绕过构造函数,只说你刚刚下载的东西,那是一个MyObject。所以不管你做什么,你总是要把输入的数据说,那个位是一个字符串,我把它分配给MyObject.Id或者其他什么的。
https://softwareengineering.stackexchange.com/questions/361963
复制相似问题