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的替代方案,它更快、更简便并且更易于跨平台。 |