ServiceStack 是一个开源的、支持.NET 与 Mono 平台的 REST Web Services 框架。InfoQ 有幸与 Demis Bellot 深入地讨论了这个项目。在这篇两部分报道的第 2 部分中,我们更多地了解了 ServiceStack 的特性,并谈论了微软与 Mono 在.NET 开源世界中所扮演的角色。你可以在这里找到本次采访的第1 部分内容。
InfoQ:基于消息的 Web service到底是什么?
Demis:本质上,基于消息的服务是一个以传递消息作为通信方式的系统。不妨将 RPC 方法和一个基于消息的 API 做一个比喻,它们的区别就类似于 Smalltalk 或 Object-C 的消息发送机制与普通的静态 C 方法调用的区别。方法调用与它们所调用的实例是紧密耦合的,而在一个基于消息的系统中,请求会通过消息传递给接收者。而接收者不一定要亲自去处理该消息,因为它可以选择将该请求委托给某个替代的接收者去进行处理。
基于消息的设计在 ServiceStack 中是通过将某个服务请求查询(Services Request Query)转换为一个请求数据迁移对象(Request DTO)实现的,并且该对象与其它任何实现完全解耦。你可以从宏观角度上将一个 ServiceStack 请求想象成一个 Smalltalk 运行时方法调用,ServiceStack host 在这里就是扮演了接收者,HTTP 谓词(Verb)扮演了选择器,而请求 DTO 则扮演了消息。
请求被发送到哪个终结点(endpoint)上并不重要,因为你可以从 PathInfo、QueryString 与请求体中的任意组合中得出请求的详细信息。在请求绑定过程之后,该请求会遍历所有用户自定义的过滤器以及进行检查的预处理器,并且该请求在到达实际的服务实现之前可以选择在预处理器中进行处理。一旦请求达到了服务之后,它就会调用最配备的选择器,在默认情况下它会查找与当前的 HTTP 谓词同名的方法,如果该方法不存在,它就会转而调用一个能够捕获“任意”请求的方法,该方法可以处理来自任意终结点、或以任何格式进行路由的请求。即使在服务的实现内部,也可以继续将请求委托给某个替代的服务,如果需要的话,它还可以进一步将请求代理(proxy)发送至一个远程的分区(shard)实例。
从概念上来说,对 ServiceStack 的使用只是将某个消息发送给一个 ServiceStack 实例而已,客户端并不关注最终处理该消息的是谁,它只需知道某个响应对请求进行了回复,或者对于单向消息来说,它只需知道该请求已经被成功接受了就可以了。而一个 RPC API 调用从概念上来说意味着你调用了某个远程方法,这就使得请求与远程实现的方法签名被紧密耦合了。
采用基于消息的设计有许多天然的益处,与它们的远亲RPC 相比,它们提供了更好的适应性、灵活性以及版本控制能力。举一个这方面益处的例子吧,当你将一个请求发送至它的单向终结点时,如果ServiceStack 实例配置了一个消息队列(MQ)主机,该请求就会被自动委托给配置好的MQ 中介(Broker)并在后台进行处理。因此即使ServiceStack 主机停机,等待处理的消息也不会丢失,在主机下次重启后会自动重新进行处理。这种行为能力都可以由ServiceStack 自动实现。如果没有应用任何MQ 主机,那么请求就会按照一般的方式进行处理,比如由某个HTTP web 工作进程(worker)按同步方式进行处理。
随着时间的推移,当你不断深入地开发与升级现有的服务,并且所支持的客户端也越来越多时,基于消息的设计也就越来越体现出它所带来的益处。一个最直接的益处就是它不需要使用任何代码生成器就能够提供一个端到端的、类型化的API。而如果没有采用基于消息的设计是不可能实现这一点的,因为这种设计保证了你的Service Contract 的核心实现都被封装为可重用的DTO。由于你能够将服务端web service 所定义的DTO 共享给客户端,你就可以完全省略在传统的开发流程中必须从你的服务的临时WSDL/XSD 结构中重新生成客户端代理的这一步骤了。
类型化的客户端是Native SDK 的支柱,它为你的服务的终端用户提供了最大的价值,因为它大大减少了调用你的API 所必须承担的大部分重任。有一些公司非常、非常希望你能够使用他们定义的API,因为它的整体业务的成功都依赖于对API 的大量应用,而类型化客户端的方式在这种公司中非常之流行。Amazon EC2、Google App Engine、Azure、Facebook、Ebay、Stripe 与Braintree 等公司都首选采用这种方式。
更重要的是,基于消息的设计鼓励你设计粗粒度并且重用性更好的服务。与之相反,RPC 方法签名通常是为了实现某个单一目标而设计的,这就意味着你必须为满足每个客户端的需求不断地添加更多的RPC 方法(这就相当于每次都加入一个新的外部终结点)。相反,基于消息的设计鼓励你通过为现有的服务添加额外功能的方式增强现有服务的能力,因为增加这些额外的功能不会造成任何冲突。采用这种方式的一个额外的好处是,它为那些正在使用你的现有服务的客户端提供了直接的易用工具,因为这些客户端可以很容易地访问到新加的特性,而且不必为了调用新的外部终结点而加入新的代码路径。
无论任何时候,在实现类似SOA 平台这种服务密集型系统时,这种方式都是非常重要的。服务经常会因为新的客户需求而过期。因此,保证你的服务API 不要被随时随地来自于客户的特殊需求牵着鼻子走非常重要。从系统的角度出发,可以将API 设计想象成将你的内部系统功能暴露为一个通用的、可重用的API。这也是为什么我为所有的服务终结点都实现了基于消息的设计的主要原因,因为粗粒度的API 本质上就鼓励设计重用性更好、功能更丰富的API。
那些在业界处于前列的分布式框架的开发者已经熟知了基于消息设计的各种益处,他们在各种业界领先的平台上应用了基于消息的设计,例如Google 的Protocol Buffers、Amazon 的Web Services 平台、Erlang 进程、F#的mailbox、Scala 的行为者(actor)、Go 的信道(channel)、Dart 的Isolate 以及Clojure 的代理(agent)等等。
InfoQ: 最近你为 ServiceStack新加入了 razor引擎,这使得 ServiceStack看起来更像是一个完整的 web框架而不仅仅是一个 web service**** 框架,促使你这样做的动机是什么?
Demis:我们一直以来都想为 ServiceStack 加入良好的 HTML 处理功能。从一个服务框架的角度来说,HTML 只不过是另一种 Content-Type 而已,但其特殊之处在于它已经被所有的浏览器所支持,这使得它成为了可以在大多数计算机设备上显示一个通用界面的唯一格式。由于 ServiceStack 可以方便地使用在任何 ASP.NET 或者 MVC web 框架中,对于支持 HTML 的需求也就不那么急迫了,因为对于单页面应用来说,只要能够提供静态内容,并且在需要时去动态加载内容就够了,这一点完全可以通过调用所使用的 web 框架的功能就可以实现。虽然这种方式已经运行得足够好了,但我们仍然不是非常满意。之前我们只能选择要么在 WebForms 中使用 WCF,但我们觉得 WCF 在服务端的抽象上面做得太过头了。而如果选择 MVC 的话,虽然它的框架的功能很全面,但它的复杂性在不断增加,而且每次发布都会加入更多的东西,这使得它没有办法运行在 Mono 上。
而最终促使我们提供自己的 HTML 处理能力的,很大程度上是由于我们立志于为 Mono 提供完整支持的抱负,这样我们就可以在支持 Mono 的各种令人振奋的平台上运行我们的软件了。我们所提供的自托管组件 HttpListener 正在渐渐流行起来,但阻碍它进一步能够得到运用的原因是它不能够生成动态的 HTML 视图,而 WebForms 和 MVC 都对它们的 ASP.NET 托管服务有这方面的要求,因此我们决定提供一个集成的 HTML 视图引擎。不幸的是,在当时能够选择的视图引擎要么是我们不太喜欢的 WebForms,要么是 Razor,它虽然看上去很美,但在当时既不开源也没有良好的文档。因为我们决定基于两种最流行的标记语言:Markdown 以及 Razor 来创建我们自己的视图引擎。ServiceStack 的架构非常良好,我们能够在不破坏其它格式与终结点的前提下轻易地加入新的Content-Type。经过两周时间的开发后, Markdown Razor 诞生了,它结合了 Markdown 这个用以表现内容非常理想的标记语言以及 Razor 表现动态内容的能力。
我们对这一成果非常满意,因为我们现在可以使用 ServiceStack 以及 Markdown Razor 创建类似于 ServiceStack Docs 这样包含大量 ajax 功能的文档网站了。而 Markdown 的独特优势在于 GitHub 对它的原生支持,它能够让我们按原样导入 GitHub 页面,并且在我们的公共GitHub 库上直接进行在线编辑与内容预览。这种解决方案其实仍然不完整,因为Markdown 虽然在表现内容上很完美,但并不适合于表现精确的HTML 布局,而多数.NET 开发者熟悉的Razor 其实才是最理想的视图引擎,因此一等到它开源之后,我们就立即把握机会实现了它。来自于 NancyFx (另一个优秀的 web 框架)的一位好朋友也为我们提供了帮助,他告诉了我们让 Razor 支持 VS.NET 的智能提示的方法,我们终于为 ServiceStack 也加入了对 Razor 视图引擎的支持,这一来它在它所支持的所有主机与平台上都能工作得一样良好了。
但我们并没有停下脚本,由于我们已经完整的支持了所有特性,并且能够完全控制 HTML 的生成过程,我们就能够加入一些属于我们的独特功能了,这些功能可以在我们的展示网站 razor.servicestack.net 上看到。我们的虚拟文件系统使我们能够从文件系统之外的地方提供 Razor 视图,比方说可以内嵌在某个.NET dll 内。使用自托管功能,可以将你的网站与 Razor 视图打包在一个托管的.NET .exe 文件内。我们所引入的另一项独特功能,是能够 Partial view 嵌入在其它类型的视图引擎内,这种功能允许你使用 Razor 和 HTML 创建你的页面结构,同时使用 Markdown 维护你的页面内容,这些内容可以以 Partial view 的方式很方便地嵌入在页面中。
我们还为某些 MVC 特性提供了更有竞争力的替代方案,比如说 Cascading Layout 模板,它为维护多个网站布局上提供了比 MVC 中的 Area 更简便直观的方式。另一个例子是基于 node.js 的 ServiceStack Bundler ,作为 MVC Web Optimization 的替代方案,它更快、更简便并且更易于跨平台。
InfoQ: 你觉得在哪些场景中,WCF/Web API/MVC也许比 ServiceStack更适合呢?
Demis:MVC 是一个功能全面的 web 框架,它更适合于那些拥有大量的服务端生成内容的网站。而 ServiceStack 更专注于为那些拥有一个重量级服务组件的 web 应用提供优秀的体验,例如单页面应用就经常会用到一些尖端的 JavaScript 框架,比如 Backbone.js 、 AngularJS ,还不断有令人兴奋的新贵加入这个阵营,例如 Dart 的 WebComponents 。我们也期望我们所提供的集成的 Mardkdown 与 Razor 视图引擎能够吸引那些托管大量内容与文档的网站。
如果你在开发服务端驱动的系统时愿意相信遵循 REST 和 HATEOAS 约定所带来的价值,那你应该使用 WebAPI,并遵从那个社区的开发文化。而如果你希望为你的服务提供最大化的功能,并且将终结点托管在 SOAP、MQ(即将支持 TCP)上,那 ServiceStack 会是更好的选择。
如果你是一位 MVP 或是一位微软金牌合伙人,那你会自然地选择继续坚守 MVC 与 Web API 技术路线,因为微软会让你一路跟随他们的技术,从 SQL Server 到 AppFabric,最后到 Windows Azure。而我们看到了支持伸缩性更强、性能更好的平台所带来的更大的价值,我们将把精力集中在这些平台上,在 Amazon 的 EC2 以及 Google Compute Engine 这样的纯 Linux 云平台运行我们的软件,提供对替代的关系型数据库解决方案 OrmLite、以及各种高性能 NoSQL 解决方案的支持,并且会继续在 Redis 以及云端数据存储的集成适配器上加大投入力度。
InfoQ: 微软之前也和一些开源项目(例如 jQuery和 NuGet)达成了合作,而且像 Scott Hanselman**** 这样的微软员工看起来也对在微软技术平台上采用优秀的开源解决方案表现得非常开放 – 你觉得这种合作会在整体上为.NET**** 社区带来什么好处吗?
Demis:作为项目领导,我已经将 ServiceStack 作为一个开源项目运行了 4 年,打造一个繁荣的开源.NET 社区一直是我所非常关心的事,虽然我觉得到目前为止,微软与现有的开源项目的合作还不能让我竖起大拇指,尤其是在.NET 方面。目前来看,他们似乎只会在直接竞争失败后才会采用开源的类库。比方说,早在微软正式采用 jQuery 之前,大多数 JavaScript 开发者就已经抛弃 ASP.NET AJAX JavaScript 框架而转投 jQuery 的怀抱了。
当 NuGet 项目刚刚发布的时候,它就由于缺乏对现有的开源解决方案的支持而遭受批评。但总体而言,我认为NuGet 是微软所提供的一个很有帮助的贡献,由于它在VS.NET 中提供了一个界面,使得开发者可以方便地引用外部的构件,这就减少了使用开源框架的各种麻烦。自从ServiceStack 类库发布在NuGet 上以来,我们已经看到了大量的应用,最近的18 个月中它已经有超过20 万次下载了。
而在开源.NET 类库方面,微软仅仅在今年早期推出Web API 的时候,首次采用了开源的JSON.NET 这个.NET 类库。和其它公司一样,对微软来说,当出现了更优秀的开源软件时将其纳入麾下是个正常的选择,尤其是微软自己之前提供的JSON 序列化工具在对日期数据的格式化方面没有选择和JSON.NET 一样遵循标准,而且在性能上也比不过ServiceStack 的序列化工具。对于一个像JSON.NET 一样的单独的类库来说,这种方式让它的使用度产生了一次爆发,光是下载量就已经超过其它所有JSON 序列化工具的总和了。但这对于对其它类库的应用并没有带来一种光环效应,事实上反而带来了负面的影响。当它成为了这方面的默认类库之后,就意味着.NET 开发者如果打算与其背倒而驰而去采用其它替代方案,那他们就必须提出一个合适的理由。选择我们的产品作为替代方案其实已经有了一个非常好的理由,因为它是.NET 平台上最快的JSON 序列化工具,这使它在那些关注于性能的公司中非常流行,像StackOverflow就采用它处理JSON 。但我们在市场上虽然处于第2 位,却离头名有了巨大的差距,我们的市场占有率只是JSON.NET 的14 分之1,而排名第3 的开源JSON 序列化工具更是只有头名110 分之1 的市场占有率。在开发高性能的服务时,序列化的性能是至关重要的。因此我们一直将我们的序列化工具视作核心组件,我们承诺将尽力将它保持为最好与最快的序列化工具。
除了JSON.NET 之外,我相信DotNetOpenAuth 是在那之后唯一一个被采用的开源.NET 类库了,这对于使用者来说就可以避免重复发明轮子的尴尬了。看起来微软现在确实是对于采用他们感兴趣的、更优秀的开源类库持比较开放的态度了,虽然这一改变并没有为整个社区带来太多令人注目的好处。
但还是要感谢微软在商业模式上的变化,他们已经将更多的东西开源,Windows Azure 相关的大多数类库与框架就已经开源了。这是件大好事,一是它降低了每个人采用新软件的门槛,二来它也为减轻Mono 社区的负担的带来了直接的好处,因为Mono 之前每次都要消耗精力去重新实现相同的功能,而现在他们则可以使用微软的开源版本的软件,并且还可以为其贡献各种补丁包,以改善它对Mono 的支持了。 F#就是这方面一个很好的例子,它完全开源,并且对 Mono 的支持程度之高也令人感到吃惊。实际上我个人在 F#方面做的各种尝试都是在 Mono/OSX 平台上用 Sublime.Text 完成的。微软开源产品之一的 SignalR 更是拥有了一个活跃的社区,并且在 GitHub C#/.NET 的版块上跃居前列,而基于 SignalR 框架的 JabbR.net 聊天室也成为了.NET 开发者的主流选择。
有一些开源框架首先将其它平台上的流行功能引入到.NET 平台上,在微软推出一个完整的解决方案之前填补了某方面的功能空白,但是这些开源框架可谓命运多舛。比方说,在MVC MonoRail 框架推出了好几年之后,微软推出了自己的ASP.NET MVC 框架,这让该社区的成员感到非常泄气。微软近期在尝试在Entity Framework 中加入的ORM Data Access Layer 也对之前拥有一个活跃社区,并且在这方面处于领先地位的ORM NHibernate 产生了负面的影响。尽管 EF 的速度比起其它任何一个开源的.NET ORM 框架都要慢上好几倍,但它的下载量仍然超过了其它所有 ORM 框架的总和。与之类似的是,微软现在一再重复地创建和发布新的服务框架,许多技术不断出现随后又被淘汰,包括.asmx、CSF、WCF、WCF/REST、WSE、WCF DataServices、WCF RIA Services 以及最新的 Web API。而在这些年前,已经有许多可替代的开源服务框架提供了足以取而代之的能力。如果不是微软的举动有着许多不确定性,在许多领域都会出现更多的合适的替代方案,以提供给更广大的.NET 社区。
.NET 平台有着它独特的地方。微软的推广方式包括合作伙伴频道、传道士(evangelist)、MVP 奖励项目,并且完全掌控 VS.NET 的各个方面。这种方式让微软对大多数开发而言看起来就是整个.NET 生态系统的权威发言人,这使得它们完全控制了.NET 平台上的各种思想。在过去,微软只是在利用这方面的影响力去推广他们自己的类库与框架,这让许多使用.NET 的公司不愿意脱离微软的技术范围而去寻求其它替代方案。我们也在很多场合承受着痛苦:许多开发者希望在工作中采用 ServiceStack,但由于微软官方的解决方案的存在,他们无法说服他们的公司去采用其它框架,即使它们展现了各种有用的示例与更好的性能指标。我相信其它许多开源类库与框架也遭受过类似的命运。
整个大环境导致了开源社区难以振兴,而 C#/.NET 的流行程度相对也有所下降,近期已经滑出了 GitHub(这里可以被视为开源之家)的 Top 10 语言的榜单,但仍存在的少数开源.NET 项目挽回了这种劣势,并且依靠它们的独立技术建立了围绕它们的社区。Mono 项目是目前为止最耀眼的明亮,它的发展情况良好,并且在社区中具有很多优秀的开发者,这一点为.NET 应用程序运行在主流的其它平台上,例如 iOS、Android、Linux 及 OSX 作出了很大的贡献与价值。对许多项目来说,提供对 Mono 的支持能够最大程度上扩展它们的应用范围。而我们的最大动力之一就是永远保证 ServiceStack 在 Mono 上的第一等支持。不仅 Mono, NancyFx 与 ServiceStack 也尽了它们最大的力量去壮大开源.NET 社区,它们都各自吸引了超过 200 位贡献者,这些贡献者中有许多都是首次为开源项目贡献力量。 MonoGame 与 RavenDB 是另两个值得关注的项目,它们正在逐渐流行起来。我们对于能够成为 GitHub 上排名最高的项目之一,以此促进.NET 的开源活动感到非常自豪,但我们也期望能够看到更多的吸引人的.NET 社区发展壮大,并且鼓励更多的开发者去尝试开源开发的模型。
我相信微软的合作模式所带来的最大好处,是让那些可作为替代选择的类库与框架走入了人们的视线。在这种情况下,建立一个更大的开源.NET 社区比起让微软独自提供更多的功能能够带来更多的好处。在这一点上,Scott Hanselman 在他那著名的个人博客上多次提到了各种开源类库与框架,以一己之力让大众了解到了这些信息。除了Scott 之外,Glenn Block 也在其非常活跃的个人twitter 帐号 @gblock 上推广了许多框架。如果微软能够投入更多力量去提升公众对这些项目的认知度,这将鼓励更多的.NET 开发者投身开源世界,并给予那些现有开源项目的开发足够的动力,促使他们继续增强自己的项目,这两点对于维持开源社区的繁荣都是至关重要的组成部分。
作为一家利益驱动的公司,微软需要一些财政上的激励,以促使它们去推广各种其它的类库与框架。我希望某个组织能够建立一个成功的商业案例,以证明建立一个繁荣的开源.NET 社区将鼓励更多的开发者选择.NET,并且因此为 Windows 服务器工具与 Azure 服务带来了更多的潜在客户君。即使微软只在 Windows Azure 的运行环境中推广一些其它的.NET 框架,例如它们对 Node.js 、 Python 以及 Java 的支持,这也是一种良好的改进。想要完全壮大开源.NET,需要微软从心底里真正地将开源.NET 社区的成长视为它们打算积极进取的目标,到了那时微软就会在它们的 MVP 奖励计划中认可开源的贡献,并在合作伙伴频道中对其进行推广。我相信如果在未来微软仍旧无动于衷的话,那 Mono 项目就是鼓励更多的.NET 开发者加入开源的最后希望了。
InfoQ: 你最近在某个论坛中有这样一条留言:“我希望明年会发展的更好,我已经计划好了一些东西,它们会让你放弃选择其它框架。”你能详细地说明一下你已经为未来计划好了哪些特性吗?
Demis:呵呵,我是有意在论坛里有所保留的,这样当我们宣布这些功能时就能为人们带来极大的震撼。因为我相信我们能够推出一些独一无二的、值得关注的产品与特性。但总的目标依然是提供一个有价值的服务框架,并且实现 WCF 中其余的有用功能,以进一步提高 ServiceStack 的竞争力。我们已经公开了一部分打算在明年推出的特性,包括以下内容:
- 将 Async 分支与异步管道进行合并。
- 创建新的、快速的异步 TCP 终结点。
- 为 node.js 与 Dart 的服务提供快速的、原生的适配方案。
- 与更多的 MQ 终结点进行集成(例如 RabbitMQ 与 ZeroMQ)。
- 与 VS.NET 进行集成,并且为 WCF 的“增加服务引用”功能推出改良版的解决方案。
- 集成的开发工作流,以及对 Mono/Linux 的更多支持。
- 允许自动化发布到 Amazon EC2 与 Google Compute Engine 的云平台。
- 提供长期稳定的商业服务包签订。
- 提供一个初学者模板,包含各种流行的单页面应用常用技术,如 Backbone.js、AugnlarJS、Yeoman 以及 Dart。
- 提供创建 CRM 与支持 SharePoint 系统的初学者模板。
- 重新设计网站,并进一步改善文档。
InfoQ: Demis,非常感谢你抽出时间进行这些访谈。
关于受访者
Demis Bellot是来自 Stack Exchange 的一位开发者,他维护着 StackOverflow Careers 2.0 的后台网站以及基于 ServiceStack 创建的 MQ 服务。他同时也是 ServiceStack 的创始人以及项目领导。
查看英文原文: Interview With Demis Bellot, Project Lead of ServiceStack - Part 2
评论