服务拆分与架构演进

领域驱动设计和服务自演进能力是内功。html

前言

《微服务的团队应对之道》提到,微服务帮助企业提高其响应力,而企业须要从DevOps、服务构建、团队和文化四点入手,应对微服务带来的复杂度和各类挑战,从而真正获益。若是说运维能力是微服务的加油站,服务则是其核心。ios

gas

企业想要实施微服务架构,常常问到的第一个问题是,怎么拆?如何从单体到服务化的结构?第二个问题是拆完后业务变了增长了怎么办?另外,咱们想要改变的系统每每已经成功上线,并有着活跃的用户。那么对其拆分还须要考虑现有的系统运行,如何以安全最快最低成本的方式拆分也是在这个过程当中须要回答的问题。数据库

本文会针对以上问题,介绍咱们团队在服务拆分和演进过程当中的实践和经验总结。后端

咱们项目架构的演化历程

1-evolution-course

该项目始于2009年,到如今已有7年的时间。在这7年中覆盖的业务线不断扩大,从工单、差旅、计费、文件、报表、增值业务等;业务流程从部分节点到用户端的全线延伸;7年间打造多个产品,架构经历了屡次调整,从单体架构、RPC、服务化、规模化到微服务。安全

主要架构变迁以下图所示:架构

2-process

在这7年架构演进路上,咱们遇到的主要挑战以下:前后端分离

  • 如何拆?即如何正确理解业务,将单体结构拆分为服务化架构?
  • 拆完后业务变了增长了怎么办?即在业务需求不断发展变化的前提下,如何持续快速地演进?
  • 如何安全地持续地拆?即如何在不影响当下系统运行状态的前提下,持续安全地演进?
  • 如何保证拆对了?
  • 拆完了怎么保证不被破坏?

问题1:如何将单体结构拆分为服务化架构?

就如庖丁解牛同样,拆分须要摸清内部的构造脉络,在筋骨缝隙处下刀。那么微服务架构中,咱们认为服务是业务能力的表明,须要围绕业务进行组织。拆分的关键在于正确理解业务,识别单体内部的业务领域及其边界,并按边界进行拆分。运维

1. 识别业务领域及边界。

首先须要将客户、体验设计师、业务分析师、技术人员集结在一块儿对业务需求进行沟通,随后对其进行领域划分,肯定限界上下文(Boundary Context),也称战略建模。模块化

如下咱们常用的方法和参考的红蓝宝书:微服务

  • Inception-> User Journey | Scenarios,用于梳理业务流程,由粗粒度到细粒度逐一场景分析。
  • 四色建模,用于提取核心概念、关键数据项和业务约束。
  • 领域驱动设计-战略设计,用于划分领域及边界、进行技术验证。
  • Eventstorming,用于提取领域中的业务事件,便于正确建模。

3-modeling

Inception与DDD战略设计的对比:

4-inception

一个业务领域或子域是一个企业中的业务范围以及在其中进行的活动,核心子域指业务成功的主要促成因素,是企业的核心竞争力;通用子域不是核心,但被整个业务系统所使用;支撑子域不是核心,不被整个系统使用,该能力可从外部购买。一个业务领域和子域能够包括多个业务能力,一个业务能力对应一个服务。领域的边界即限界上下文,也是服务的边界,它封装了一系列的领域模型。

一个业务流程表明了企业的一个业务领域,业务流程所涉及的数据或角色或是通用子域,或是支撑子域,由其在企业的核心竞争力的角色所决定。好比企业有统一身份认证,决策不一样部门负责不一样的流程任务,那么身份认证子域并不产生业务价值,不是业务成功的促成因素,可是全部流程的入口,于是为通用子域,可为单独服务;而部门负责的业务则为核心子域。

举个例子

工单业务流程:

某企业为服务人员提供工单服务的业务流程简化以下。首先搜索服务人员,选取服务人员购买的服务,基于目标国家的工单流程,向服务人员收取资料,对其进行审计,最后发送结果。

5-work-order识别的领域:

其中服务为其核心竞争能力,包括该企业对全球各国的政策理解,即法律流程,服务资料(问卷),计算服务,资料审计服务,相比其余竞争对手的服务(价位/效率等),这些都为改企业提供核心的业务价值,天然也是核心子域。而其余用于统计改企业员工工做的工单,组织结构和员工为支撑子域,并不直接产生业务价值

6-recognition

领域划分的原则

在划分的过程当中,常常纠结的一个问题是:这个模型(概念或数据)看起来放这个领域合适,放另外一个也合适,如何抉择呢?

  • 第一,依据该模型与边界内其余模型或角色关系的紧密程度。好比,是否当该模型变化时,其余模型也须要进行变化;该数据是否一般由当前上下文中的角色在当前活动范围内使用。
  • 第二,服务边界内的业务能力职责应单一,不是完成同一业务能力的模型不放在同一个上下文中。
  • 第三,划分的子域和服务需知足正交原则。领域名字表明的天然语言上下文保持互相独立。
  • 第四,读写分离的原则。例如报表需有单独报表子域。核心子域的划分更多基于来自业务价值的产生方,而非不产生价值的报表系统。
  • 第五,模型在不少业务操做中同时被修改和更新。
  • 第六,组织中业务部分的划分也是一种参考,一个业务部门的存在每每有其独特的业务价值。

简单打个比方,同一个领域上下文中的模型要保持近亲关系,五福之内,同一血统(业务)。

领域划分的误区和建议

  • 业务能力仍是计算能力?在划分一些貌似通用的领域时,其实只是用到了通用的计算能力而不是业务能力,只需采用通用库的方式进行封装,而无需使用服务的方式。如咱们系统的模板服务,是构建通用的模板服务,服务于整个平台的服务;仍是每一个服务拥有独立的模板模块?
  • 尽早识别剥离通用领域。如身份认证与鉴权领域,是企业系统中最复杂、有相对多变的领域,须要及早隔离它对核心业务的干扰。
  • 时刻促成技术人员与客户、业务人员的对话。业务领域的划分离不开对业务意图的真正理解。而需求人员和体验设计师对于User Journey的使用更熟悉,而技术人员、架构师对领域驱动设计、Eventstorming更熟悉。无论哪一种方法都要求跨角色的群体协同工做,即客户人员、业务分析师、体验设计师与技术人员、架构师。而现实的状况中,User Journey更多的在Inception,在需求阶段进行,而领域驱动设计、Eventstorming更多的在开发设计阶段被使用,故而需求阶段常常缺失技术人员,而开发设计阶段常常缺失客户、业务人员的参与。 另外一个常见的现象是,Inception的参与人员和真正的开发团队有可能不是同一个群体,那么Inception中的业务沟通每每以UI的方式做为传递,所以在开发中常常只能经过UI设计来理解业务的真正意图。 因此要想将正确的理解业务,作对软件,须要时刻促成技术人员与客户、业务人员的对话。

识别了被拆对象的结构和边界,下一步须要决定拆分的策略和拆分的步骤。

2.拆分方法与策略

拆分方法须要根据遗留系统的状态,一般分为绞杀者与修缮者两种模式。

  • 绞杀者模式 指在遗留系统外围,将新功能用新的方式构建为新的服务。随着时间的推移,新的服务逐渐“绞杀”老的一流系统。对于那些老旧庞大难以更改的遗留系统,推荐采用绞杀者模式。
  • 修缮者模式 就如修房或修路同样,将老旧待修缮的部分进行隔离,用新的方式对其进行单独修复。修复的同时,需保证与其余部分仍能协同功能。

咱们过去所作的拆分中多为修缮者模式,其基本原理来自Martin Fowler的branch by abstraction的重构方法,以下图所示:

7-branch-by-abstraction

就如咱们团队所总结的16字重构箴言,我以为十分的贴切:

“旧的不变,新的建立,一步切换,旧的再见”。

经过识别内部的被拆模块,对其增长接口层,将旧的引用改成新接口调用;随后将接口封装为API,并将对接口的引用改成本地API调用;最后将新服务部署为新进程,调用改成真正的服务API调用。

同时,拆分建议从业务相对独立、耦合度最小的地方开始。待团队获取相应经验和基础设施平台构建完善后,再进行核心应用迁移和大规模的改造。另外,核心通用服务尽可能先行,如身份认证服务。

3. 拆分步骤

对于模块的拆分包括两部分:数据库与业务代码,能够先数据库后业务代码,亦可先业务代码后数据库。然而咱们的项目拆分中遇到的最大挑战是数据层的拆分。在2015年的拆分中发现,数据库层因为当时系统性能调优的驱动,在代码中出现了跨模块的数据库连表查询。这致使后期服务的拆分很是的困难。所以在拆分步骤上咱们更多的推荐数据库先行。

4.数据库拆分

咱们借鉴了重构数据库一书中提到的方法,经过重复schema同步数据,对数据库的读写操做分别进行迁移。以下图所示:

8-TDDL

虽然技术上是可行的,然而这仍然占用了大量没必要要的时间,包括大量的数据迁移。这也是致使当时的拆分没法在给定时间内完成的很大因素。

5. 咱们的结果:

系统架构图:

9-system-architecture

问题2:拆分后业务变了增长了怎么办?

随着客户业务的变化,咱们的服务也在持续的增长,而其中碰到了一个特大的服务。服务的大小如何衡量呢?该服务生产代码7万行+,测试代码14万行+,测试运行时间2个小时。团队中7个stream天天50%工做须要对这个服务进行更改,使得团队间的依赖很是严重,独立功能没法单独快速前行,交付速度及质量都受到了影响。

咱们的总结:

客户的业务是在变化的,咱们对业务的认知也是逐渐的过程,因此Martin Fowler在他的文章中提出,系统的初期建议以单体结构开始,随业务发展决定其是否被拆分或合并。那么这也意味着这样构建的服务在它的生命周期中必然会持续被拆分或合并。那么为了实现这样一个目标,使系统拥有快速的响应力,也要求这样的拆分必然是高效的低成本的。

所以,服务的设计须要知足以下的原则:

  • 服务要有明确的业务边界,以单体开始并不意味着没有边界。 服务要有边界,即便以单体开始也要定义单体时期的边界。咱们系统中有一个名为“Monkey”的服务,是在中国虎年启动的,由此它并非一个业务概念。当这个服务的名字为MonkeyAPI时,能够想象5年来它变成了什么?几乎全部和这个产品相关的功能都放入了这个服务中。脱离平台来看这一个产品的系统,其实它只是作了先后端分离而已。这个例子告诉咱们,没有边界就会致使大杂烩,以后对其进行整理和重造的代价很大,可能须要花费“几代人”的努力。
  • 服务要有明确清晰的契约设计,即对外提供的业务能力。
  • 服务内部要保持高度模块化,才可以容易的被拆分。
  • 可测试。

问题3:如何安全地持续地拆?

就如前言中提到的,系统已经上线大量的用户正在使用,如何在不影响当下系统运行状态的前提下,持续安全地演进?其实持续演进就是一场架构层次的重构,在这样的路上一样须要:

  • 坏味道驱动,架构的坏味道是代码坏味道在更高层次的展示,也就意味着架构的混乱程度一样反映了该系统代码层的质量问题。
  • 安全小步的重构。
  • 有足够的测试进行保护——契约测试。
  • 持续验证演进的方向。

真正有挑战的问题4:如何保证拆对了?

拆分不能没有目标,尤为在具备风险的架构层次拆分更需谨慎。那么咱们如何验证拆分的结果和收益?或许它能够提升开发效率,交付速度快,上线快,宕机时间也短,还能提升开发质量,可扩展性好,稳定,维护成本低,新人成长快,团队容易掌握等等。然而软件开发是一个复杂的事情,拆分能够引发多个维度的变化,度量的难度在于如何准肯定位由拆分这一单一因素引发的价值的变化(增长或下降)。

其实要回答这个问题,仍是要回到拆分之初:为何而拆? 我所见过的案例中有由于政治缘由拆的、业务发展须要的、系统集成驱动的等等;有因之而成功的,也有因之而失败的。拆并非一件容易的事,有诸多的因素。我认为无论表象是什么,拆以前须要弄清拆分的价值所在,这也是咱们能够保证拆分结果的源头。

总结

系统可由单体结构开始,不断的演进。而团队须要对业务保持敏感,与客户、业务人员进行业务对话,不断修炼领域驱动设计和重构的能力。

team

在拆分的路上,咱们的经验显示其最大的障碍来自意大利面同样的系统。无论咱们是什么样的架构风格,高内聚低耦合的模块化代码内部质量仍然是咱们架构演进的基石。具备夯实领域驱动设计和重构功底的团队才能够应对这些挑战,持续演进,保持其生命力。而架构变迁以前须要弄清背后的变迁动因与价值,探索性前进,及时反馈验证,才是正解。那么咱们如何保证架构不被破坏呢?这个问题会在后续的文章中持续探讨。

最后,勿忘初心,且行且演进。

相关文章
相关标签/搜索