[关闭]
@xuemingdeng 2017-05-26T16:48:06.000000Z 字数 5994 阅读 1025

自包含系统:打开微服务的正确方式

微服务 架构


摘要:

现如今,似乎人人都在构建微服务。将一个系统拆分成微服务有很多种方式,微服务可以独立部署是一个不争的事实,但除此之外,人们并未能对微服务做出更好的定义。很多项目都使用了自包含系统。

正文:

要点

  • 自包含系统(SCS)与微服务有很多相似的特征。它们都可以独立部署,并以解耦系统为目的。不过,SCS一般具有更粗的粒度和更精确的定义。
  • 每一个SCS都是一个自主的Web应用,包含了Web UI、业务逻辑和持久化层。对于SCS来说,API是一个可选项,而且SCS不应该共享UI,当然,那些调用了多个服务的单页应用(SPA)除外。
  • 在进行领域驱动设计(DDD)时,为了尽可能降低SCS之间的耦合,每个SCS都应该实现一个边界上下文(Bounded Context)。可以通过对用户故事进行来定义边界上下文。
  • SCS之间可以通过多种方式进行交互:UI集成,如引用JavaScript文件、ESI或SSI;异步通信和事件;同步通信。
  • 一个SCS只能由一个单独的团队进行开发。一个用户故事一般只能由一个团队来实现。SCS致力于将每一个变更都包含在单个SCS里,所以开发效率会很高,因为不需要做太多的协调工作。
  • SCS可以保证一个特性只会在一个SCS里实现,因此可以单独部署到生产环境。微服务支持独立部署,但如果大多数变更要求部署多个微服务,那么在微服务上的投入就没有多大意义。

现如今,似乎人人都在构建微服务。将一个系统拆分成微服务有很多种方式,微服务可以独立部署是一个不争的事实,但除此之外,人们并未能对微服务做出更好的定义。很多项目都使用了自包含系统。

那么什么是自包含系统?

SCS官方网站对自包含系统进行了定义。自包含系统具有如下特征。

如何将一个系统拆分成SCS

在进行领域驱动设计(DDD)时,为了尽可能降低SCS之间的耦合,每个SCS应该实现一个边界上下文。每个系统不只拥有一个领域模型,事实上,一个系统可以包含多个不同的领域模型。每一个模型都有一个边界上下文。例如,在电子商务系统里搜索产品的当前价格时,产品的描述和数量是很重要的。而如果要向客户发货,则还需要其他的信息:产品的重量和客户的收货地址。将系统拆分成边界上下文是构建自包含系统最为有效的方式。

可以通过对用户故事进行分组来定义边界上下文。假设我们通过全文检索来搜索产品,那么通过分类和推荐来搜索也应该属于相同的边界上下文。当然,有时候拆分并不会有非常清楚的界线,这要取决于搜索的复杂性。

在将系统拆分成SCS时也需要考虑到用户体验。用户体验描述了客户与系统之间的交互步骤,比如搜索产品、结账或注册。每一个步骤都可能成为一个SCS。这些步骤之间一般只有很少的依赖。这些步骤之间有承上启下的关系:购物车在结账时就变成了一个订单,然后完成支付。

SCS不只处理某种特定的领域对象。例如,使用一个SCS来处理所有的客户数据就没有多大意义:很多不同的边界上下文都会用到客户数据。所以,为客户单独创建模型并在一个单独的SCS里实现是不可能的事情。如果真的这样子做了,那么每个需要用到客户数据的系统都会依赖它。这也就是为什么在将系统拆分成SCS时需要通过用户故事、边界上下文或用户体验来驱动,这种自上而下的方法会带来低耦合的系统。

虽然在后续有必要识别出公共部分,但这不应该成为关键点。公共逻辑可以被抽取到另一个系统里,但这意味着SCS会对这个系统产生依赖,它们之间就产生了耦合。

此处输入图片的描述
图1:SCS的边界上下文包含了逻辑、持久化层和Web UI

SCS之间的交互

SCS之间可以通过多种方式进行交互。

  1. UI集成提供了很大的灵活性。对于SCS来说,最简单的事情莫过于渲染一个超链接。这个虽然简单,不过已经足以说明其灵活性。链接页面可以被更改,甚至用PDF文档替代HTML文档。不过,有时候一个Web页面由多个不同的SCS生成的元素组成。例如,一个登陆页面的不同部分可能展示来自多个SCS的信息。在这种情况下,使用超链接是不行的,必须使用包含片段的方式,比如包含HTML片段。

    1. 可以在浏览器里进行片段包含,通过JavaScript加载HTML片段。JavaScript代码库基本上都支持这种操作。不过片段包含也可以在后端进行。

    2. Edge-Side Includes(ESI)就是一种标准,它通过HTTP缓存(如Varnish)和内容分发网络(CDN)来实现片段包含。ESI定义了一些HTML元素,从其他服务器加载的HTML会替换这些元素。Server-Side Includes(SSI)与ESI非常相似,不过它是通过Web服务器来实现的,如nginx或Apache httpd。所以,如果所有的请求都经过一个Web服务器,那么就可以使用SSI。

    3. UI集成带来低耦合的系统:它们只渲染UI的一部分。所以不需要定义数据schema,而且它本身就具备弹性。如果使用JavaScript进行集成,当集成的系统不可用时,JavaScript就不会被执行。

  2. 接下来是异步交互。异步交互也能降低耦合:一般是通过事件来解耦。接收事件的系统可以决定如何处理事件,所以逻辑变更可能只发生在接收系统内。这种方式也具有弹性,如果一个系统不可用,到最后总是能够再恢复过来,然后继续处理消息。换句话说,这样只是会增加一些延迟,不过异步系统还是要尽量降低延迟。

  3. 最后一种交互方式是同步调用。虽然可以使用这种方式,但不建议这么做,因为这样容易导致耦合,而且为了避免因其他依赖系统的不可用造成的错误级联问题,需要做额外的处理工作。

这里有必要说明同步交互和异步交互的区别。同步交互是指SCS在处理一个请求时调用另外一个系统,并等待响应。异步交互是指在没有可处理的请求时发生交互,没有必要等待响应。

可以使用像REST这样的同步协议来实现异步交互。例如,在没有请求可处理时,使用REST进行数据复制,这也算是一种异步交互。也可以使用REST来发送事件,例如Atom就经常被用在博客系统里。订阅者也可以对特定的URL进行轮询,获取最新的博文。这种方式也可以用于查找其他事件,如新订单或其他类似的事件。除了REST,还有其他的消息解决方案,它们默认就提供了异步交互模型。

此处输入图片的描述
图2:SCS可以通过同步交互或异步交互的方式在UI层进行集成。

SCS与微服务

自包含系统与微服务有很多相似的特征:它们都可以独立部署,并以解耦系统为目的。不过,SCS具有更精确的定义。微服务之间可以使用任何一种交互方式,它们可能不会提供UI,而且一个用户故事可以使用多个微服务来实现。SCS相对更为严格。

SCS取微服务之长,低耦合和独立部署让一个微服务的变更不会影响到其他微服务。所以,对微服务做出变更相对容易。SCS确保一个特性只在一个SCS里实现,因此可以单独部署到生产环境。微服务支持独立部署,但如果大多数变更要求部署多个微服务,那么在微服务上的投入就没有多大意义。

不过,SCS具有更粗的粒度。每个SCS都会实现一个由DDD定义的边界上下文。这样可以确保每个业务需求只在一个SCS里实现。不过,有时候SCS也可以包含细粒度的微服务。例如,假设订单处理的最后一个步骤需要进行大量的计算,那么计算逻辑就需要进行独立的伸缩。订单处理的其他步骤可以在一个SCS里实现,但最后一步可以在一个单独的微服务里实现,使用多个微服务实例来处理这些负载。

所以说,SCS最起码也算是一个微服务。作为一个Web应用,它可以独立部署。不过,出于伸缩性和安全方面的考虑,一个SCS可能包含多个微服务。

SCS的技术选型

SCS可以使用不同的技术来实现。这也是微服务和SCS的优势之一,你可以选择最好的技术来解决问题。既然SCS是Web应用,那么就可以使用Web框架。这方面的技术已经为人们所熟知,它们都可以被用在SCS上。

不过,SCS不应该共享UI。当然,那些调用了多个服务的单页应用(SPA)除外。SPA非常流行,可以使用Angular来实现。不过,如果把一个SPA作为多个服务的前端,那么它就是一个被共享的UI。这种架构不符合SCS的定义。SPA的变更会涉及到后端的服务和SPA本身,不太可能出现添加一个特性只修改UI或者只修改后端服务的情况。

SCS可以与SPA组合在一起,每个SCS可以有它自己的SPA。不过,如果从一个SCS切换到另一个SCS,那么就需要使用新的SPA,这个过程不会很快,而且会造成不好的用户体验。不过SCS仍然可以通过JavaScript提供更好地用户体验,前提是JavaScript代码只能用于增强UI,不能用于UI共享。虽然SPA目前很流行,而且SCS有很多好处,但要将SPA与SCS组合在一起使用会比较困难。

SCS之间的交互可以通过REST来实现。如果使用了REST,那么底层就是基于HTTP的,不过HTTP本来就是一个Web应用所需要的。另一种交互方式是消息系统。如果使用了消息系统,就要保证高可用和伸缩性,因为消息系统的崩溃会导致整个系统不可用。

实现SCS面临的挑战

每个SCS可以有自己的UI,所以要提供统一的外观。每个SCS有自己的Web接口,所以设计和外观上也有可能不一样。可以提供一个界面风格指南,用于定义UI元素。这不仅仅是指视觉设计,还包括用户体验和可用性。如果没有风格指南,在为复杂的系统创建UI时就会遇到困难,不管是开发一个单体还是开发一系列SCS。

风格指南里可以包含字体、图标、CSS,用于规范HTML,或者使用JavaScript实现更高级的UI元素。对于单体来说,这些文件与系统里其他代码一样,已经被集成在一起了。不过,SCS有自己的UI,所以每一个SCS都需要能够访问到这些文件。最简单的做法是将这些文件放在专门的服务器上,或者使用文件管道,然后在项目里包含这个管道,就像包含其他代码依赖一样。这是唯一一个违反SCS不共享代码原则的地方,但这个代价是不可避免的。

虽说SCS的外观要尽量保持统一,但在对外观做出更改之后,不要将变更推送给SCS,而是让每个SCS尽快拉取最新的版本,并确保每个SCS都通过了测试。所以,风格文件要进行版本管理,有时候需要同时使用新旧版本的文件。

UI层面的集成看起来很简单,不过也存在一些限制。例如,可能无法在同一个应用的不同页面上运行相同的JavaScript代码,因为不同页面可能使用了不同版本的JavaScript代码库。所以在理想情况下,不应该在SCS之间共享JavaScript代码。同时还要注意,从外部包含进来的HTML代码片段不要破坏了整个页面的布局。

虽然UI集成看似乎简单,不过还是要注意一些与界面有关的事项。实现SCS需要一定的前端开发技能。我们借助UI技术来解决SCS的架构问题,这反过来要求团队具备混合技能(跨功能的)。

开发效率和系统复杂性

微服务架构通常会带来额外的复杂性,因为有很多系统需要部署和运维。SCS也是微服务,所以也会有复杂性方面的问题。不过,因为SCS是粗粒度的,所以需要部署的服务不会太多。

实际上,SCS与Web应用使用的是相同的基础设施。所以,基础设施的复杂性不会很高,如果你知道怎么运行一个Web应用,就也能运行一个SCS,只是数量比以前要多一些。这是SCS的另一个优势,对高级技术的要求没有那么高。不过,SCS仍然具备了微服务的很多优势。与REST或消息系统的集成不是必需的,可以只通过超链接来完成集成,这点看起来很有意思。

SCS致力于将每个变更都包含在单个SCS里,所以开发效率会比较高,因为只需要改动和部署一个单独的SCS。因此,SCS的技术复杂度相对较低。它们就是普通的Web应用,对我们来说再熟悉不过了。不过,它们还提供了其他很多好处。

什么时候使用SCS

很显然,SCS只适用于Web系统。不过,对于其他一些系统,虽然它们没有非常清晰的隔离边界,但每个系统都有自己的边界上下文、数据库和逻辑,那么也可以使用SCS。对于没有持久化需求并且只包含了少量逻辑的portal来说,也可以使用SCS,比如UI集成。

SCS不应该依赖DDD的边界上下文,而且如果当前架构依赖了其他不同的技术,那么就很难迁移到SCS。迁移是一个非常重要的因素,如果一个架构无法进行迁移,那么它就一文不值。最后,我们必须再次强调,SCS要求对Web应用有很好的理解。虽说Web应用已经很普遍,但随着SPA的崛起,一些有关SPA的基本概念并没有被很好地理解。

从我们的经验来看,SCS解决了很多复杂的Web应用问题,在进行系统架构时,可以从它入手。现在有很多项目在使用SCS,比如Otto——世界上最大的电商公司之一。

结论

自包含系统是一种架构模式,它的想法源自微服务,并将这些想法与传统的Web应用开发结合在一起,用于开发一系列低耦合的系统。因为SCS本质上就是Web应用,所以大多数开发人员对它的基本概念都很熟悉。SCS为Web应用提供了久经考验的架构模式,同时也给其他类型的应用带来了灵感。

关于作者

此处输入图片的描述Eberhard Wolff拥有超过15年的架构和咨询经验,专注在业务和技术之间的领域。他在德国innoQ咨询公司工作。作为一个演讲者,他在很多国际性会议上呈现演讲。作为一个作者,他发表了100多篇文章,并出版了微服务相关书籍。他最新的著作是一本有关微服务的书。他专注于现代架构技术,包括云计算、持续集成、DevOps、微服务或NoSQL。

查看英文原文: Self Contained Systems (SCS): Microservices Done Right

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注