ServiceStack是一个开源的、支持.NET与Mono平台的REST Web Services框架。InfoQ有幸与Demis Bellot深刻地讨论了这个项目。在这篇两部分报道的第1部分中,咱们主要谈论了ServiceStack项目创建的原动力,以及项目中的各类设计方案选择。html
InfoQ: 你是否定为微软对服务的实现方式有什么问题?ServiceStack又是怎样解决这些问题的呢?git
Demis: 有一些问题是因为微软一直以来自认为良好的框架设计的方式所形成的常见问题,另外一些问题的根源是因为微软的设计老是倾向于知足设计器优先的工具,而且试图为使用设计器做为向导的开发者提供一套熟悉的API,而这种设计倾向形成了一些反作用:github
因为微软过于追求为开发者提供一套熟悉的RPC API,并在VS.NET中提供丰富的UI工具(例如“添加服务引用”对话框)以保证上手的简易性,其代价就是推广了远程服务中的各类反模式,从长期来看将致使没必要要的不协调与脆弱性。不幸的是,微软长期以来在其发布的每一个web service框架中都持续使用这种设计方式,这种方式提倡建立RPC服务API的设计方式,形成了开发者在乎识中将远程服务也看成本地方法同样调用。这种结果的害处是多方面的,远程服务意味着它包装了一个外部依赖,对它的调用要比本地方法慢上几百万倍,而且更容易受外界的影响而发生错误。将隐式的服务契约与服务端RPC方法签名绑定在一块儿,就意味着你的API服务契约和服务端的实现紧密耦合在一块儿了。因为没有规定一个定义良好的边界,致使了糟糕的关注分离,而且出现了将繁重的ORM数据模型经过网络返回给客户端的糟糕实践。从定义上来讲,这种数据模型的关系型结构与循环式关系对于数据迁移对象(DTO)来讲并不是一种好的选择,它使调用可能偶尔会失败。这种方式还将隐式的服务契约与你的底层关系型数据库(RDBMS)数据模型耦合在一块儿,这样在进行修改时就会产生额外的冲突。并且远程API与调用它们的服务端网站是无链接的,而这些调用的方法在客户端代理部署后也会不断地演化。理想的框架应该可以提倡可演化的、灵活的API设计,以免在服务端改变时产生运行时错误。web
这些有一张关于WCF提倡的细粒度的RPC API方式与ServiceStack所鼓励的基于消息的API方式的不一样之处的对比图。此外,这个用ServiceStack重写Web API的入门教程的示例则展现了基于消息的API如何减小细粒度调用,增长了服务的可重用性。redis
ServiceStack采用了Martin Fowler所推荐的远程服务最佳实践,所以避免了以前困扰着.NET web service开发者的不少潜在问题:数据库
远程门面提倡使用基于消息的、粗粒度的批量调用接口。它可以将通讯次数减至最低,并促进了建立更少可是可用性更好、而且支持版本化的服务接口。ServiceStack采起了基于消息的设计确保开发者始终建立粗粒度的API设计。apache
数据迁移对象要求使用特定用途的POCO以建立web service响应的数据格式。ServiceStack一直鼓励使用DTO,它自己与它的实现彻底解耦,而且存在于一个没有外部依赖也不须要具体实现的程序集中。这种策略容许将定义服务端的服务类型共享给全部.NET客户端,于是提供了一个端到端的类型化API,而且无需使用代码生成。编程
网关的做用是将全部服务端通讯包装起来,隐藏在一个显式定义而且可重用的客户端网关以后。这种最佳实践和代码生成的代理(例如WCF)有所不一样,后者将代码生成的类型与底层的服务客户端揉合在一块儿,使得生成结果难以模拟(mock)、测试以及替换为不一样的实现,也常常致使对现有服务的改变会形成客户端代码的编译错误。这一点对ServiceStack来讲不多会成为问题,由于它可以重用通用的服务客户端,所以在接口方面惟一须要改变的东西仅限于类型自己而已。而且因为基于消息的设计的优点,不管新增或移除任何功能都不会影响到现有的客户端。json
ServiceStack在它的通用而且可重用的类型化.NET服务客户端采用了网关模式。咱们同时支持Silverlight、JavaScript、Dart甚至是MQ(消息队列)的客户端。让全部.NET服务客户端都共享相同的接口,这使测试(经过注入或者写日志方式)变得简单,并且可以方便地在JSON、XML、JSV、MessagePack以及ProtoBuf等现有的客户端之间进行切换,而无需改动应用程序的代码。这就容许你在开发时使用相似于JSON这样便于调试的文本格式,随后在部署构建时切换为使用.NET上速度最快的二进制格式,例如Message Pack或ProtoBuf,以下降数据传输量并实现最高的性能。浏览器
以当前的状况来看,SOAP这种技术是不该该继续存在的,或者说应该仅仅限制在那些它还可以带来一些好处的地方使用。它的存在自己就是基于一个错误的假设,即为了实现数据的互交换性,必须表现出复杂性,要尽量的严格与明确,这使得基于它的全部现实都必须实现一个复杂的schema以进行通讯。而事实上,之与相对设计方式反而被证实是正确的,若是使用了相似于JSON、CVS、Protocol Buffers、MessagePack、及BSON等最小化而且灵活的格式,就会大大减小实现的障碍与复杂性,结果的生成会更快,互操做性与版本能力也会更强。
我老是以为这件事很难想象,尽管SOAP是创建在HTTP基础之上的,但它的设计者们在开发出WSDL时彷佛彻底没有吸收那些宝贵的经验。WSDL所引入的类型、消息、操做、端口、绑定以及服务这些概念真的可以取代HTTP中简单的URL标识符,以及Accept/Content-Type这些头信息吗?我以为这一点也不难判断啊。
虽然它名为SOAP(简单对象访问协议),但它既不简单,也不是什么对象访问协议,并且对于开发服务来讲,它在许多方面都显示出它是个糟糕的选择,例如:
因为SOAP明显背离了服务的核心优点,因此它可以流行起来实在是一个使人吃惊的事实。我曾有许多年为政府项目与大型企业开发服务的经验,很不幸的是其中的许多项目都有一个必需实现的要求,即把他们的服务暴露为SOAP终结点。不过许多互联网公司与专一于创造价值的创业公司都不太会使用这门技术了,他们更多地选择建立简单的HTTP API,返回单纯的XML或者JSON格式的响应。
虽然在多数状况下SOAP是一个糟糕的选择,但ServiceStack仍是为你建立的服务提供了对SOAP终结点的支持,由于现在仍然有许多现存的企业系统只可以与SOAP终结点进行通讯。这一点也和ServiceStack的核心目标相一致,即为你的服务提供最大化的功能性、可访问性以及链接方式。
SOAP在设计上是一种很是脆弱的格式,若是某个服务稍稍偏离了WSDL,而客户端又是由这个WSDL生成的,那你一般就会看到运行时错误的发生。虽然在静态类型系统中,快速失败被视为一种优点,但对于远程服务来讲就不是一种理想的方式了,由于你一般但愿可以实现向前兼容与向后兼容,所以服务端的API改变不会破坏现有的客户端。这一目标显然被WCF忽略了,它提倡RPC方法签名、SOAP格式以及代码生成,这能够说是当今的全部web service实现技术中最为脆弱的组合了。
将SOAP与Google的Protocol Buffers进行一下对比是颇有趣的,它是Google推出的一种简单的接口描述语言(IDL),Google在其内部的全部RPC协议与文件格式中几乎都使用了该技术:
MessagePack RPC以及用于开发Facebook的Apache Thrift是另外两个流行的开源选择,它们都提供了快速的,简单的IDL,而且为多数主流平台提供了绑定。
虽然是由不一样的公司开发,但以上每一个IDL都比SOAP更简单、快速而且易于使用。从SOAP的规格说明书的厚度来看,它应该是由委员会所开发的,但显然彻底没有考虑过性能,大小以及易于实现。
虽然在大小与性能方面有优点,但Protocol Buffers与MessagePack都有一个问题,即它们使用了二进制格式。因为基于文本的格式与协议更易于建立、维护及调试,它们常常成为整个开放web的更流行的选择。近年来,在基于文本的数据互交换格式上的赢家显而易见是JSON,这是一个简单的、紧凑的自描述文本格式,它的主要优点是可以在全部支持JavaScript的浏览器上进行直接应用,只需使用浏览器内置的“eval()”语句就可以当即将其转换为JavaScript对象了。因为它的流行性,主流浏览器现在都加入了对JSON的原生支持,它们为eval()方法提供了一个更安全的替代方案,能够在老浏览器上提供一个基于JavaScript的备选实现方式。若是想更多的了解JSON以及它的好处,我推荐你去看看Douglas Crockford这个有趣的演讲——“Heresy & Heretical Open Source”。
因为它的广泛性、功能多样而且易于使用,JSON在多数平台上都获得了原生的支持,而且很快地成为了开发Web API的首选数据格式,尤为是使用在Ajax客户端的时候。
从我自身的经验来看,我认为JSON对于实现服务是一种优秀的格式。它是一种更精简、灵活、容忍度高而且适应性更强的格式,而且它易于建立、调用以及调试,比起SOAP减小了冲突并提升了生产力。当咱们开始ServiceStack项目时,JSON的惟一问题就是.NET Framework自带的序列化器比XML序列化器还要慢。出于对性能上的严谨态度,咱们并不肯意接受这点不足,所以咱们发布了咱们本身实现的序列化器,它比任何其它.NET JSON序列化器都要快上3倍以上,而且比.NET Framework中的任何序列化器(包括二进制序列化器)都要快上2.5倍以上。
继承了JSON保持体积最小的精神,咱们也开发了JSV格式,它相似于JSON,但使用了CVS风格的转义功能,使得它比JSON略微加快,更精简而且更便于人类阅读,它不只可以用于.NET与.NET服务的交互,并且可以用以解析查询字符串,在ServiceStack的GET请求中是可以容许传递复杂的对象图的。
虽然WCF与ServiceStack在实现方式上是彻底不一样的服务框架,但它们却有着很是相似的目标。ServiceStack在建立服务上采起了一种很是面向服务的方式,它的设计通过了作好,可以以最大程度重用的方式达到服务的实现。经过代码优先、类型化设计的方式,咱们可以为你的服务提供更强大的智能推断,以容许你自动地生成XSD、WSDL文件,自动生成元数据页,而且自动暴露预约义的路径。每一个新加入的功能、特性、终结点与Content-Type都是围绕着你的现有模型建立的,而且不须要任何额外的操做以及对应用程序代码的任何改动就能够当即得到新功能。咱们将这种方式理解为从一个代码优先的模型开始,做为最权威的部分,随后再将其功能暴露出来。
WCF的目标也是提供一个服务框架,以支持在多种终结点上运行服务,但他们站在一个不一样的视角,而且为全部网络终结点提供了一个统一的抽象,你必须对服务进行配置以绑定到终结点。做为WCF的主要目标之一,他们是少数几个在最终实现中对WS-*系列技术提供了深刻支持的框架。
咱们最终的目标都是提供一个易于使用的服务框架,咱们只是在如何取得简便性上有着彻底不一样的想法。
WCF看起来比较喜欢繁重的抽象,用复杂的运行时XML配置进行管理,以及用大型工具为开发者提供端到端的链接能力。这个抽象层包含了许多复杂的技术:通过抽象的人工服务端对象模型很复杂、配置很复杂、WSDL很复杂、SOAP/WS-*也很复杂。最终结果是,为了对这些主题有一个良好的理解,对每一个主题都须要看好几本书才行。
有一种陈旧的观念认为处理复杂性的解决之道就是添加更高层的抽象。这种方式的问题在于,只有这种抽象的设计很完美,它才可以显出效果。(例如编程语言比机器代码更合适)不然当你遇到了一些预料以外的行为,或者是配置、整合与互操做性上的问题是,你须要去理解在这些抽象层之下到底发生了什么。在这种状况中添加更多的抽象实际上只会增长开发者必须去熟悉的概念,以及当他们针对高级别抽象层进行开发时额外的认知。这也使得理解你的代码库更加困难,由于你必须理解每个之下都发生了些什么。WCF的服务端对象模型是全新的、而且不够天然,这进一步放大了问题,新的开发者们对此一无所知,由于这种模型是独一无二的,而且它彻底没有对服务或HTTP领域中的概念知识做出任何指导。所以当开发者们以后转而使用最好的服务框架时,他们从WCF中学到的东西彻底没有用武之地。与其投入时间去阅读那些如何使用WCF的书籍,还不如把时间花在学习HTTP与TCP/IP上,由于即便你转而使用其它web service框架,这些知识依然能起到做用,它们是彻底独立于编程语言或平台的。
从技术实现的层面来讲,过多的抽象会致使没必要要的性能负担,而且这种性能问题很难优化。由于当你尝试直接与底层API打交道时,这些抽象会产生阻碍以及各类强制性的阻力。而咱们的方式是只在绝对必要时才会添加抽象,例如咱们的IHttpRequest与IHttpResponse封装对于为ASP.NET与自托管的HttpListener提供一个通用的API就是必需的。相比起添加抽象层的方式,咱们更倾向于添加一些非强制性的特性,它们只是将一些底层接口与一些常见功能包装起来,以实现DRY。这种方式带来的好处就是容许终端用户自由地调用各类方式,并在他们本身开发的类库中加入本身定义的功能加强,以促进他们实现精益的、DRY以及可读性良好的代码库。暴露底层API可以确保咱们保持框架的灵活性,用户能够经过多种方式进行访问而得已完整地控制系统的输出,也由于用户能够定制返回的响应,所以他们不会受到任何限制,而得已控制返回的每个字节。
WCF为服务采起了一种统一的方式,它促进所支持的全部终结点都标准化为一个单一的人为抽象模型。这种方式也遇到了抽象的通用原则所产生的反作用,即一个统一的抽象模型要为每一种试图抽象化的实现承担全部的复杂性,于是它们只可以处理各类终结点的特性的交集部分,并且为每一个结终点实现最低程度的通用功能。结果就是生成了一个不完整的外部API,而且因为抽象的存在阻碍了对底层终结点的访问,使得这种API的可访问性与可配置能力都大大削弱了。
要完全了解WCF的功能须要强大的技术能力,须要你投入巨大的精力研究各类技术资源。但本质上来看这种精力投入是一种浪费行为,由于WCF与其它平台上的一些采用了标准方式开发的框架相比,生产力与功能都有所欠缺。对于终端用户来讲,哪怕是为了稍稍掌握一些WCF的使用经验,也须要投入大得多的精力。出于以上缘由,我不但愿看到有人去使用相似于WCF之类的框架,或者是它那种统一的终结点的抽象方式。因为它的复杂性与庞大的技术实现细节,我也很怀疑它是否可以保持演化,以适应新的开发范式或是服务开发模式。我怀疑WCF会遭受到与WebForms相同的命运,退化为一个过期的技术,最终被微软下一代框架取而代之。
除了现有的REST、SOAP、MQ以及RCON以外,咱们也计划为ServiceStack加入更多的终结点。但因为咱们采起了一种基于约定的方式,咱们会避免使用繁重的配置。咱们所采用的基于消息的设计方式会避免使用大型工具,并简化必须实现的外部接口部分。首先从C#开始,逐渐移植其它语言,这使得终端用户能够当即使用新功能,而无需任何额外的投入。咱们的松耦合架构不存在统一的抽象模型,这能够容许咱们加入互不冲突的新终结点与特性,而不会为现有的组件带来强制的复杂性。
总而言之,咱们提供了一个特性完整、性能更好而且更易于理解的服务框架,它的代码库也更加精益和灵活,而且使用它进行开发和维护所需的技术投入要小的多。
WCF所推崇的另外一个概念是XML配置,而这也是咱们没法苟同的,它妨碍了测试,而且须要投入更多精力进行维护。只有你的应用程序中真正可配置的部分才应该放在你的应用配置文件中,定义及组织你的服务依赖应该交给代码去作,这种方式的额外优势在于它便于调试,并且能够在编译时静态地进行验证。WCF须要大量的配置,在ServiceStack中建立一个完整的应用程序所需的代码量比起一个的WCF Service所需的XML WCF 配置还要小。若是把配置信息放在IOC(控制反转)这一怪,那代码会更干净与整洁,由于它直接就能够引用你所需的特性的.NET API了。
大型工具的问题在于,当你打算在其中尝试些新功能,或者是某些不在其设计初衷里的功能时,它就无能为力了。也就是说你成为了这个工具的奴隶,只能提供一些它所支持的功能。并且框架一旦进行了重写,大型工具就没法继续使用,由于它们所提倡的繁重的、复杂的代码库是很难演化或者是重构的。我推测这就是微软为何老是重写新的服务框架,而不是改进现有的框架的缘由。也是为何Web API不可以重用MVC现有的抽象方式,而且不支持SOAP的缘故,尽管它们都采用了相同的RPC API设计方式,而且它的自托管功能选项也是基于WCF建立的。
因为ServiceStack是基于ASP.NET的最底层建立的,所以它提供了对ASP.NET MVC良好的集成能力,也可以方便地重用MVC中许多组件现有的功能实现,例如Authentication filter attribute、Caching与Session provider等等。你也能够在MVC中方便地调用ServiceStack服务,比起C#方法直接调用只是稍稍麻烦一点。
ServiceStack与WCF相反,它看起来要简单许多。例如ServiceStack的架构只要一页纸就能画下。启用它只需在web.config中加入一行,告诉ASP.NET将全部请求转发给ServiceStack便可。
ServiceStack与微软开发类库与框架所采用的方式,其观念上的最大不一样或许就是在如何最好地处理复杂性这一点上了。微软倾向于明确性,XML方式的可配置能力,引入繁重的抽象层(为了前瞻性而支持全部潜在的用例),依赖于抽象层将简单的面向用户的门面通过改头换面后暴露出去,为了方便使用设计器工具及新手开发者进行优化。微软的主要动力是为开发者提供一个熟悉的编程模型,而且经过使用大型工具来简化模型的上手难度。从WebForms中就能够看出这一点,它为WinForm开发者提供了一个基于事件的编程模型来开发网站,WCF则让开发者使用传统的C#方法与接口方式去建立远程服务。它们采起的方式一般会致使完成功能须要投入大量的技术精力去研究巨大的代码库。
与之相反,咱们将大型的代码库看成代码的最大敌人,而且避免引入人为的复杂性,这是咱们的主要目标之一。比方说咱们强烈反对引入新概念、抽象或编程模型。咱们崇尚小巧的低级别接口,它与底层领域1对1进行映射,以实现灵活性的最大化,减小冲突,而且在须要进一步定制化时将转换的复杂性降至最低。经过可重用的工具类与扩展方法保证了DRY与高级别的功能。咱们采起一种代码优先的开发模型,经过代码去捕捉用户意愿的本质,这促进了一种更优雅、不妥协的设计方式,避免了使用设计器工具时所带来的转换的复杂性。咱们的全部类库都使用POCO,以得到最大的可重用性,而且咱们内置的转换器能够简化在领域特定模型中进行转换所需的精力。
咱们为用户展示了一种基于消息设计的最佳实践,为从头开始开发的服务提供了一种最优的方式。服务就是普通的C#类,无需关注任何终结点的问题,由于这种依赖是能够自动解析的,这种方式会促使用户采用良好的代码实践。
约定以及减小对外部知识的了解是咱们处理复杂性最好的武器。相比明确的指定实现方式,更好的办法是提供一个基于约定的默认行为,使它按预计的方式运行。这就让用户没必要必定要学习你的框架API,由于咱们能够假定预计的标准行为的结果,并容许咱们随时提供更多的能力,比方说,它可以自动支持外部所调用的全部内置终结点与格式。你也可让你的服务返回任意的响应,它会自动序列化为所请求的Content-Type。
咱们相信,达成简单性的最好方式就是在第一时间避免各类复杂性,咱们采起了如下方式:
咱们并不喜欢代码生成,由于咱们相信它为项目带来了没必要要的麻烦,咱们也反对在开发工做的核心部分依赖于任何大型工具。
因为你的ServiceStack服务的服务契约在设计上是由你的请求与响应DTO维护的,咱们就可以提供一个类型化的端到端的API,只须要你用来建立服务的那些类型以及那些通用的、可重用的.NET客户端就能够了。这容许咱们为任何.NET上的服务框架提供一个最简洁的、类型化的以及端到端的API。
基于WCF的设计方式以及它对配置的严重依赖,看起来WCF在设计时彻底没有考虑到可测试性。而这一点是ServiceStack的核心目标之一,咱们默认提供了一个内置的(而且可重写的)IOC容器,鼓励在一开始就遵循良好的开发实践。开发一个服务只需实现一个不包括任何方法的IService接口,或者从其它现有的Service类、或ServiceBase类继承便可,这些类都是可独立测试的,而且彻底能够mock。这一系列努力的成果就是,你所实现的单元测试能够用做XML、JSON、JSV以及SOAP的集成测试。咱们所提供的自托管HttpListener类型使得进行内存中的集成测试很是方便。
性能是ServiceStack的首要目标之一,咱们将性能视为最重要的特性。基于消息的设计提倡更少的网络调用,咱们也很当心地保证只暴露那些运行快速的面向用户的API。通常状况下咱们不接受来自外部的贡献代码,由于它们会包含速度较慢的代码。咱们对本身的实现是尽心尽力的,咱们没有使用任何运行时的反射或者正规表达式匹配,而是采用其它更快的解决方案。
咱们开发并维护着.NET上最快的JSON、JSV以及CSV文本序列化器,同时容许以插件的方式使用Message Pack与Protocol Buffers这两个.NET上最快的二进制序列化器。
因为缓存是建立高性能服务不可或缺的技术,咱们提供了一个丰富的Caching Provider模型,包含了对各类内存缓存、Redis、Memcached、Azure以及Amazon后台的实现。缓存API会保证使用最优化的格式,比方说,若是客户端支持,咱们会将压缩后的JSON输出缓存起来,并在以后发生调用时直接将缓存中的内容输出到响应流中,这就保证了托管代码能产生最快的响应速度。
每次咱们找出微软的类库中的瓶颈时,咱们都会使用更快速的实现去替代它。咱们已经实现了本身的JSON序列化器,做为插件的Gzip和Deflate压缩类库,而且提供了本身的Session实现,它可以与以上任意一种Caching provider相配合使用,它的出现也避免了困扰ASP.NET开发者已久的严重性能问题。
出于咱们对开发高性能服务的技术追求,咱们为最快的分布式NoSQL数据库Redis开发并维护着一个.NET上最好的C# Redis客户端。