这篇文章讲的太好了,生怕之后被删掉。转到我博文列表里把-。-前端
原文地址
ios
正文:数据库
开发者在刚开始尝试实现本身的微服务架构时每每会产生一系列问题 :架构
回答上面的问题须要首先了解微服务设计的逻辑,科学的架构设计应该经过一些输入并逐步推导出结果,架构师要避免凭空设计和“拍脑门”的作法。app
解耦的单体应用和微服务系统在逻辑上是同样的。对于服务拆分的逻辑来讲,先设计高内聚低耦合的领域模型,再实现相应的分布式系统是一种比较合适的方式。框架
服务的划分有一些基本的方法和原则,经过这些方法能让微服务划分更有操做性。最终在微服务落地实施时也能按图索骥,不管是对遗留系统改造仍是全新系统的架构都能游刃有余。运维
在开始划分微服务以前,架构师须要在大脑中有一个重要的认识:微服务只是手段,不是目的。dom
微服务架构是为了让系统变得更容易拓展、更富有弹性。在把单体应用变成靠谱的微服务架构以前,单体系统的各个模块应该是合理、清晰地。异步
也就是说,从逻辑上单体系统和微服务没有区别,某种理想状况下微服务只是把单体系统的各个模块分开部署了而已(最近流行的monorepo把多个服务的代码仓库以模块的形式组织到了一块儿,证实了这一点)。分布式
大量的实践教训告诉咱们,混沌的微服务架构,比解耦良好的单体应用会带来更多麻烦。
(混乱的微服务VS良好的单体)
开源社区为此进行了大量讨论,试图对系统解耦寻找一种行之有效的方法,所以具备十几年历史的领域驱动设计(DDD)方法论被从新认识。领域驱动设计立足于面向对象思想,从业务出发,经过领域模型的方式反映系统的抽象,从而获得合理的服务划分。
采用 DDD 来进行业务建模和服务拆分时,能够参考下面几个阶段:
从 DDD 的限界上下文往微服务转化,并获得系统架构、API列表、集成方式等产出。
(使用DDD划分微服务的过程)
抽象须要找到看似无关事务的内在联系,对微服务的设计尤其重要。
假设有一天,你在某电商网站购买了一台空调,当你支付了空调订单的费用后,又让你再次支付安装订单费用,你确定大为光火。缘由仅仅多是架构师在设计系统时,为空调这种普通产品生产了一个订单,而安装做为了另外业务逻辑生成了单独的订单。
你必定以为这个例子太傻了,架构师不会这点都没考虑到,”安装“ 应该被抽象成一个产品,而”安装行为“能够做为另一个服务实现。然而现实的例子比比皆是,电信或移动营业厅还须要用户分两步办理号卡业务、宽带业务。原始是不合适的抽象模型形成的,并最终影响了微服务的划分。
咱们可使用概念图来描述一些概念的抽象关系。
(商品这一律念的概念图)
若是没有抽象出领域模型,就得不到正确的微服务划分。
经过利用DDD对系统从业务的角度分析,对系统进行抽象后,获得内聚更高的业务模型集合,在DDD中一组概念接近、高度内聚并能找到清晰的边界的业务模型被称做限界上下文(Bounded Context)。
限界上下文能够视为逻辑上的微服务,或者单体应用中的一个组件。在电商领域就是订单、商品以及支付等几个在电商领域最为常见的概念;在社交领域就是用户、群组、消息等。
DDD的方法论中是如何找到子系统的边界的呢?
其中一项实践叫作事件风暴工做坊,工做坊要求业务需求提出者和技术实施者协做完成领域建模。
把系统状态作出改变的事件做为关键点,从系统事件的角度触发,提取能反应系统运做的业务模型。再进一步识别模型之间的关系,划分出限界上下文,能够看作逻辑上的微服务。
事件是系统数据流中的关键点,相似于电影制做中的关键帧。在未创建模型以前,系统就像是一个黑盒,不断的刺探系统的状态的变化就能够识别出某种反应系统变化的实体。
例如系统管理员能够登陆、建立商品、上架商品,对应的系统状态的改变是用户已登陆、商品已建立、商品已经上架;相应的顾客能够登陆、建立订单、支付,对应的系统状态改变是用户已登陆、订单已建立、订单已支付。
因而能够经过收集上面的事件了解到,“哦,原来是商品相关事件是对系统中商品状态作出的改变,商品能够表达系统中某一部分,商品能够做为模型”。
(利用事件刺探业务黑盒并抽象出模型)
在获得模型以后,经过分析模型之间的关系得出限界上下文。例如商品属性和商品相对于用户、用户组关系更为密切,经过这些关系做出限界上下文拆分的基本线索。
其次是识别模型中的二义性,让限界上下文划分更为准确。
例如,在电商领域,另一个不恰当设计的例子是:把订单中的订单项当作和商品一样的概念划分到了商品服务,但订单中的商品实际上和商品库中的商品不是同一个概念。
当订单须要修改订单下的商品信息时,须要访问商品服务,这势必形成了订单和商品服务的耦合。
合理的设计应该是:商品服务提供商品的信息给订单服务,可是订单服务没有理由修改商品信息,而是访问做为商品快照的订单项。
订单项应该做为一个独立的概念被划分到订单服务中,而不是和商品使用同一个概念,甚至共享同一张数据库表。
(典型具备”二义性“陷阱的场景)
”订单下的商品“和”商品“在不一样的系统中实际上表达不一样的含义,这就是术语”上下文“的由来。一组关系密切的模型造成了上下文(context),二义性的识别能帮咱们找到上下文的边界(bounded)。一样的例子还有 “订单地址” 和 “用户地址”的区别。
固然,在DDD中具体识别限界上下文的线索还不少,例如模型的生命周期等,咱们会在后面的文章中逐步展开。在后续的文章中,咱们会介绍更多关于 DDD 和事件风暴的思想和原理。
前面咱们说到限界上下文能够做为逻辑上的微服务,但并不意味着咱们能够直接把限界上下文变成微服务。在这以前很重要的一件事情是对模型进行验证,若是咱们获得的限界上下文被抽象的不良好,在微服务实施后并不能获得良好的拓展性和重用。
限界上下文被设计出来后,验证它的方法能够从咱们采用微服务的两个目的出发:下降耦合、容易扩展,能够做为限界上下文评审原则:
原则1,设计出来的限界上下文之间的互相依赖应该越少越好,依赖的上游不该该知道下游的信息。(被依赖者,例如订单依赖商品,商品不须要知道订单的信息)。
原则2,使用潜在业务进行适配,若是能在必定程度上响应业务变化,则证实用它指导出来的微服务能够在至关一段时间内足以支撑应用开发。
(通常抽象程度的领域模型)
上图是一个电信运营商的领域模型的局部,这部分展现了电信号码资源以及群组、用户、宽带业务、电话业务这几个限界上下文。
主要业务逻辑是,系统提供了号码资源,用户在建立时会和号码资源进行绑定写卡操做,最后再开通电话或宽带业务。在开通电话这个业务流程中,号码资源并不须要知道调用者的信息。
可是理想的领域模型每每抽象程度、成本、复用性这几个因素中获取平衡,软件设计每每没有理想的领域模型,大多数状况下都是平衡各类因素的苟且,所以评审领域模型时也要考虑现实的制约。
(”抽象”的成本)
用一个简单的图来表达话,咱们的领域模型设计每每在复用性和成本取得平衡的中间区域才有实用价值。
前面电信业务一样的场景,业务专家和架构师表示,咱们须要更为高度的抽象来知足将来更多业务的接入,所以对于两个业务来讲,咱们须要进一步抽象出产品和订单的概念。
可是同时须要注意到,咱们最终落地时的微服务会变得更多,也变得更为复杂,固然优点也是很明显的 —— 更多的业务能够接入订单服务,同时订单服务不须要知道接入的具体业务。
对于用户的感知来讲,能够一次办理多个业务并统一支付了,这正是某电信当前的痛点之一。
(高度抽象的领域模型)
在大量使用DDD指导微服务拆分的实践后,咱们发现不少系统设计存在一些常见的误区,主要分为三类:未成功作出抽象、抽象程度太高、错误的抽象。
在实际开发过程当中,你们都有一个体会,设计阶段只考虑了一些常见的服务,可是发现项目中有大量能够重用的逻辑,并应该作成单独服务。当咱们在作服务拆分时,遗漏了服务的结果是有一些业务逻辑被分散到各个服务中,并不断重复。
如下是一个检查单,帮助你检查项目上常见的抽象是否具有:
对微服务或DDD理解不够。模型具备二义性,被放到不一样的限界上下文。例如,订单中的收货地址、用户配置的经常使用地址以及地址库中的标准地址。
这三种地址虽然名称相似,可是在概念上彻底不是一回事,假如架构师将”地址“划分到了标准地址库中,势必会形成用户上下文和系统配置上下文、订单上下文存在没必要要的耦合。
(左边为抽象错误带来的依赖,右边为正确的依赖关系)
上图的右边为正常的依赖关系,左边产生了不正常的依赖,会进一步产生双向依赖。
在系统设计时,领域模型的二义性是一个比较难以识别和理解问题。好在咱们能够经过画概念图来梳理这些概念的关系,概念图是中学教辅解释大量概念的惯用手段,在表达系统设计时同样有用。
(电商系统中“地址”概念的梳理)
与地址相似的常见还有商品和订单项中的商品;用户和用户组之间有一个成员的概念;短信的概念应该更为具体到一条具体的短信和短信模板的区别。
组织对架构的干预
另一种使人感到惊讶的架构问题是企业的组织架构和团队划分影响了领域模型的正确创建。
有一些公司按照渠道来划分了团队,甚至按照 To C (面向于用户)和 To B(面向企业内部)划分的团队,最终设计出来的限界上下文中赫然出现 ”C端文章服务“,”B端文章服务“。
不乏有一些公司由于团队职责的关系,将本应该集中的服务不得已下放给应用或者BFF(面向前端的backend)。对于这类问题,其实超出了DDD能解决的范围,只能说在建模时警戒此类行为对系统形成很严重的影响。
另外企业组织架构和技术架构的关系,请参考康威定律的叙述。一个由无数敏捷团队组成的企业,和微服务有自然的联系;传统实时瀑布模型的企业,在大型软件时代竞争力十足,可是在互联网时代却无力应对变化。
(常见一些公司的组织架构)
抽象程度太高最典型的一个特征是获得的限界上下文极端的微小。回到咱们成本、复用性和抽象程度这几个概念上来,上面咱们讨论过,抽象程度虽然能够带来复用性的提升,可是带来的成本很是高,甚至不可接受。
抽象程度太高带来的成本有:更多的微服务部署带来的运维压力、开发调试难度提升、服务间通讯带来的性能开销、跨服务的分布式事务协调等。所以抽象不是越高越好,应根据实际业务须要和成本考虑。
那相应的,微服务到底应该多小呢?
业界流传一句话来形容,微服务应该多小:“一个微服务应该能够在二周内完成重写“。这句话可能只是一句调侃,若是真的做为微服务应该多微的标准是不可取的。
微服务的大小应该取决于划分限界上下文时各个限界上下文内聚程度。
订单服务每每是不少IT系统中最为复杂、内聚程度最高的服务,每每比较庞大,但没法强行分为 ”订单part1“ ”订单part2“ 等多个微服务;
一样,短信服务可能仅仅负责和外部系统对接,表现的极为简单,但咱们每每也须要单独部署。
在经过 DDD 获得领域模型和限界上下文后,理论上咱们已经获得了微服务的拆分。可是,限界上下文到系统架构还须要完成下面几件事。
一个合理的分布式系统,系统之间的依赖应该是很是清晰地依赖,
在软件开发中指的是一个应用或者组件须要另一个组件提供必要的功能才能正常工做。所以被依赖的组件是不知道依赖它的应用的,换句话说,被调用者不须要知道调用方的信息,不然这不是一个合理的依赖。
在微服务设计时,若是 domain service 须要经过一个 from 参数,根据不一样的渠道作出不一样的行为,这对系统的拓展是致命的。例如,用户服务对于访问他的来源不该该知晓;用户服务应该对订单、商品、物流等访问者提供无差异的服务。
所以,微服务的依赖关系能够总结为:上游系统不须要知道下游系统信息,不然请从新审视系统架构。
拆分微服务是为了更好的集成到一块儿,对于后续落地来讲,还有服务集成这一重要的阶段。微服务之间的集成方式会受到不少因素的制约,前面在讨论微服务到底有多微的时候就顺便提到了集成会带来成本,处于不一样的目的能够采用不一样的集成方式。
这三种集成方式耦合程度由高到低,适用于不一样的场景,须要根据实际状况选择,甚至在系统中可能同时存在。服务间集成的方式还有其余方式,通常来讲,上面三种微服务集成的方式能够归纳目前常见系统大部分需求。
第一次读DDD相关的资料和书籍时,没有记住DDD的不少概念,可是子域划分像极了潮汕牛肉火锅的划分图,给我留下深入的印象。DDD 强调技术人员和业务人员共同协做,DDD 对图的绘制表现的很是随意天然。
可是在作系统设计时,应该使用更为准确和容易传递的架构图,例如使用 C4 模型中的系统全景图(System Landscape diagram)来表达微服务之间的关系。固然你也可使用UML来完成架构设计。C4 只是层次化(架构缩放)方式表达架构设计,和UML并不冲突。
系统架构图除了微服务的关系以外,也须要讲技术选型表达出来。
微服务集成方式除了经过架构图标识以外,最好也经过API列表的方式将事件风暴中的事件转换为API;除此以外,能够将DDD领域模型细化成聚合根、实体、值对象,请参考DDD的战术设计。
逻辑每每比经验更为重要。写这篇文章的初衷是为了回答一个问题:若是老板问我,你这个微服务划分的依据是什么,我该怎么有说服力的回复?
我该回答 “具体状况具体分析?By experience?”仍是说,我是经过一套方法对业务逻辑进行分析获得的。当没有足够的经验直接解决问题,或问题庞大到不足以使用经验解决时,能支撑你作出决策就只有对输入问题进行有效的分析。
使用 DDD 指导微服务划分,能在必定程度上弥补经验的不足,作出有理有据的系统架构设计。