BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

采访ServiceStack的项目领导Demis Bellot——第2部分

| 作者 Roopesh Shenoy 关注 0 他的粉丝 ,译者 邵思华 关注 3 他的粉丝 发布于 2013年11月22日. 估计阅读时间: 29 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

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更适合呢?

DemisMVC是一个功能全面的web框架,它更适合于那些拥有大量的服务端生成内容的网站。而ServiceStack更专注于为那些拥有一个重量级服务组件的web应用提供优秀的体验,例如单页面应用就经常会用到一些尖端的JavaScript框架,比如Backbone.jsAngularJS,还不断有令人兴奋的新贵加入这个阵营,例如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,NancyFxServiceStack也尽了它们最大的力量去壮大开源.NET社区,它们都各自吸引了超过200位贡献者,这些贡献者中有许多都是首次为开源项目贡献力量。MonoGameRavenDB是另两个值得关注的项目,它们正在逐渐流行起来。我们对于能够成为GitHub上排名最高的项目之一,以此促进.NET的开源活动感到非常自豪,但我们也期望能够看到更多的吸引人的.NET社区发展壮大,并且鼓励更多的开发者去尝试开源开发的模型。

我相信微软的合作模式所带来的最大好处,是让那些可作为替代选择的类库与框架走入了人们的视线。在这种情况下,建立一个更大的开源.NET社区比起让微软独自提供更多的功能能够带来更多的好处。在这一点上,Scott Hanselman在他那著名的个人博客上多次提到了各种开源类库与框架,以一己之力让大众了解到了这些信息。除了Scott之外,Glenn Block也在其非常活跃的个人twitter帐号@gblock上推广了许多框架。如果微软能够投入更多力量去提升公众对这些项目的认知度,这将鼓励更多的.NET开发者投身开源世界,并给予那些现有开源项目的开发足够的动力,促使他们继续增强自己的项目,这两点对于维持开源社区的繁荣都是至关重要的组成部分。

作为一家利益驱动的公司,微软需要一些财政上的激励,以促使它们去推广各种其它的类库与框架。我希望某个组织能够建立一个成功的商业案例,以证明建立一个繁荣的开源.NET社区将鼓励更多的开发者选择.NET,并且因此为Windows服务器工具与Azure服务带来了更多的潜在客户君。即使微软只在Windows Azure的运行环境中推广一些其它的.NET框架,例如它们对Node.jsPython以及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

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

个人使用ServiceStack的一些经验 by 倪 嘉华

1 在我决定使用ServiceStack的时候,ASP.NET Web API还处于RC,当时的Web API和ServiceStack在功能方面差距太大,现在两者间的差距相对来说比较少了。
2 ServiceStack缺乏文档,但是因为有很多使用者通过Blog分享他们的经验,以及作者在StackOverFlow上问答了几乎所有常见的问题,因此大部分情况下使用ServiceStack都不会碰到任何问题。但是如果你想定制部分流程的时候,要有去看源代码的心理准备。
3 ServiceStack的对单元测试的支持很好(我觉得得益于他的设计),在定制使用的过程中可以很好的使用单元测试覆盖很多情况。
4 代码中一些遗留的设计(比如New-API和以前的API)和缺乏文档有的时候会让人很困惑,不过按照Wiki和StackOverFlow上的问答能解决大部分的问题。另外作者使用ServiceStack准备大量各种场景的Sample,参考他们可以为你节省大量时间。
5 Demis现在已经从StackExchange离职专职去搞ServiceStack 4了,新的版本有新的授权。现有的BSD的3系列版本不会加入新的功能(比如文章里面提到的Async已经对RabbitMQ的支持)
6 ServiceStack.ORMLite是一个很值得注意的子项目,设计上基本和Dapper很接近,但是在对各种数据库的支持上ServiceStack.ORMLite应该做得比较好(比如对PostgreSQL或者MySQL的支持)
7 ServiceStack在你能想到的所有方面都支持定制,基于ServiceStack进行定制二次开发非常方便。

现在国内总算有人开始注意到ServiceStack了 by 倪 嘉华

在我开始使用ServiceStack(大概半年前)的时候,国内几乎没有任何关于ServiceStack的文章,当时如果使用ServiceStack进行搜索,得到的结果绝大部分都是指向ServiceStack.Redis这个子项目...

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

2 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT