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

阅读数:2150 2017 年 6 月 4 日

要点

  • 自包含系统(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 都是一个自主的 Web 应用。因此,它会包含 Web UI、业务逻辑和持久化层。实现一个用户故事通常只需要对一个 SCS 进行修改,包括对 UI、业务逻辑和持久化层的修改。SCS 需要有自己的数据存储,这样它们就可以独立地修改自己的数据库 schema。
  • SCS 可能会提供服务 API。如果一个 SCS 的逻辑可能被其他系统调用,比如一个移动应用,那么这个应用就可以使用这个系统提供的 API。不过 API 不是必需的。如果 SCS 的 UI 是整个系统唯一的客户端,那么就不需要提供 API。
  • SCS 之间不应该共享 UI。SCS 要求 UI、逻辑和持久化都在一个系统内实现,而且变更也只在这个系统内进行。共享 UI 会破坏这种模式,因为很多变更需要对服务和共享 UI 做出改动。
  • 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

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论