架构——自包含系统(SCS)

SCS: Self-Contained Systems

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

Introduction

The Self-contained System (SCS) approach is an architecture that focuses on a separation of the functionality into many independent systems, making the complete logical system a collaboration of many smaller software systems. This avoids the problem of large monoliths that grow constantly and eventually become unmaintainable. Over the past few years, we have seen its benefits in many mid-sized and large-scale projects.

The idea is to break a large system apart into several smaller self-contained systems, or SCSs, that follow certain rules.

English Info Deck Keynote PowerPoint

German Info Deck Keynote PowerPoint

SCS Characteristics

  1. Each SCS is an autonomous web application. For the SCS's domain, all data, the logic to process that data and all code to render the web interface is contained within the SCS. An SCS can fulfill its primary use cases on its own, without having to rely on other systems being available.

  2. Each SCS is owned by one team. This does not necessarily mean that only one team can change the code, but the owning team has the final say on what goes into the code base, for example by merging pull-requests.

  3. Communication with other SCSs or 3rd party systems is asynchronous wherever possible. Specifically, other SCSs or external systems should not be accessed synchronously within the SCS's own request/response cycle. This decouples the systems, reduces the effects of failure, and thus supports autonomy. The goal is decoupling concerning time: An SCS should work even if other SCSs are temporarily offline. This can be achieved even if the communication on the technical level is synchronous, e.g. by replicating data or buffering requests.

  4. An SCS can have an optional service API. Because the SCS has its own web UI, it can interact with the user — without going through a UI service. However, an API for mobile clients or for other SCSs might still be useful.

  5. Each SCS must include data and logic. To really implement any meaningful features both are needed. An SCS should implement features by itself and must therefore include both.

  6. An SCS should make its features usable to end-users via its own UI. Therefore the SCS should have no shared UI with other SCSs. SCSs might still have links to each other. However, asynchronous integration means that the SCS should still work even if the UI of another SCS is not available.

  7. To avoid tight coupling an SCS should share no business code with other SCSs. It might be fine to create a pull-request for an SCS or use common libraries, e.g. database drivers or oAuth clients.

  8. To make SCSs more robust and improve decoupling shared infrastructure can be minimized. E.g. a shared database make fail safeness and scalability of the SCSs depend on the central database. However, due to e.g. costs a shared database with separate schemas or data models per SCS can be a valid alternative.

  • 自包含系统(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会对这个系统产生依赖,它们之间就产生了耦合。

架构——自包含系统(SCS)_第1张图片

图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,还有其他的消息解决方案,它们默认就提供了异步交互模型。

架构——自包含系统(SCS)_第2张图片

图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应用提供了久经考验的架构模式,同时也给其他类型的应用带来了灵感。

你可能感兴趣的:(微服务,微服务)