今日洞见
文章作者来自ThoughtWorks:Neal Ford& Rebecca,译者来自ThoughtWorks:禚娴静。
本文所有内容,包括文字、图片和音视频资料,版权均属ThoughtWorks公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。已经本网协议授权的媒体、网站,在使用时必须注明"内容来源:ThoughtWorks洞见",并指定原文链接,违者本网将依法追究责任。
微服务架构风格正在席卷全球。去年三月,O'Reilly举办了他们的第一届软件架构大会,提交的话题中与微服务相关的占绝大部分。那么为什么这种架构风格会突然如此流行了呢?
微服务是后DevOps革命时代出现的第一种全新架构风格。它第一个全面拥抱持续交付的工程实践,也是演进式架构家族的一员。演进式架构以支持增量的、非破坏的变更作为第一原则,同时支持在应用程序结构层面的多维度变化。不过,微服务仅仅是支持某些演进行为的众多架构之一。本文分享了演进式架构风格的一些特点和原则。
演进式架构
软件行业曾经有这样一个共识,架构一旦确定,“日后很难改变”。演进式架构将支持增量式变更作为第一原则。由于历来变更都是很难预测的,改造的成本也极其昂贵,所以演进式架构听上去很吸引人。如果真的可以在架构层次做到演进式地变更,那么变更就会更容易、成本更低,也能发生在开发实践、发布实践和整体敏捷度等多个地方。
微服务满足这一定义,因为它遵循强边界上下文的原则,从而使得Evan的领域驱动设计中描述的逻辑划分变成物理上的隔离。微服务能够通过采用自动化基础设施建构、测试、自动化部署等先进的DevOps实践,获得这种分离。因为每个服务在结构层面与其他服务都是解耦的,替换服务就像替换乐高积木一般。
演进式架构的特点
不同的演进式架构展示了一些共同的特点,我们已经识别了很多特点,并收录进即将出版的《演进式架构》一书里。本文将分享其中的几个特点。
模块化和耦合
边界划分明确的组件,显然可以给希望做出非破坏性变更的开发人员以更大的便利。而毫无架构元素的大泥球架构就无法做到演进式变更,因为它缺少模块化。
[在一个大泥球项目中类(图中圆边上的点)与类之间的耦合度 - 来自一个不愿透漏名字的项目]
不适当的耦合将变更导向难以预料的方向,从而阻碍演化。而演进式架构都支持一定程度的模块化,这种模块化通常体现在技术架构层面(例如经典的分层架构)。
围绕业务能力组织
受领域驱动设计的启发,越来越多的成功架构都以在领域架构层的模块化为特色。基于服务的架构与传统的SOA主要区别在于模块划分的策略,SOA是严格按照技术层进行模块划分,而基于服务的架构则倾向于按业务领域划分。
试验
试验(experimentation)是演进式架构给商业交付带来的最大价值之一。从操作角度来讲,可以采用A/B测试、金丝雀发布等常见的持续交付实践对应用进行低成本的、微小的变更。微服务架构常常是围绕服务之间的路由来定义应用程序的。通常,微服务架构围绕服务之间的路由来定义应用程序,允许同一个服务的多个版本同时运行。这反过来也使得试验和现有功能的逐步替换成为可能。最终,这使得你的业务可以花更少的时间去猜测待办故事项,从而投入到假设驱动开发中。
演进式架构的原则
思考演进式架构的另一个方法就是通过考察它的原则。原则描述了架构本身的不同特点或者设计架构的方法,其中一些原则专注在特定的架构决策的过程。
适应度函数
我们将浮现式和演进式架构区别对待,并且认为这种区分是很重要的。就像在进化计算技术中的遗传算法一样,一个架构级别的适应度函数指明了我们的目标架构是什么样子,其中一些系统关注高运行时间,而其他一些系统则会更关注吞吐量或者安全。
[这张雷达图展示了适用于软件系统的几个重要的适应度函数]
在项目前期思考一个特定系统的适应度函数,可以对决策的制定和决策的时机提供指导。适应度函数是对架构决策的衡量,通过适应度函数,我们可以预见架构是否在向正确的方向演进。
痛苦的事提前做
受极限编程社区的启发,许多持续交付和演进式架构的实践都体现了“痛苦的事提前做”的原则。在做一个项目的时候,如果一些事情可能会很痛苦,那么你需要强迫自己更早更频繁地去做这些事情。这反过来会鼓励你用自动化的手段消除这些痛苦并能提前识别问题。那些基础的持续交付实践,如部署流水线、自动化基础设施建构、数据库迁移,就是这一原则的应用,它们会提早解决变更带来的常规问题,从而使架构的演进更容易。
最后责任时刻
决策的时机是传统架构和演进式架构的最主要区别。这些决策可以是围绕应用程序的结构、技术栈、特定的工具或通信模式。在传统的架构中,这些决策发生在早期写代码之前。而在演进式的架构中,它们发生在最后责任时刻。延迟决策的好处是有更多可用的信息来辅助决策。其成本是在做出决定后任何可能出现的重复工作,这些工作可以通过合适的抽象得到减轻,但成本仍然是实打实存在的。相比而言,决定太早的成本也是很明显的。比如一个通讯工具的选择,不同的工具有不同的特性。如果我们选择一个可能最终也不会用到的重量级工具,那就是在项目中引入了技术债。这个技术债是由于使用了错误的工具导致开发进度延缓而出现的。你不能拿事先“抽象一切”作为借口。我们仍然支持敏捷YAGNI(You Ain’t Gonna Need It,你不会需要它)的原则,但会更倾向于在合适的时候做出决策。
当然,我们在考虑最后责任时刻的一个最直接的问题就是,什么时刻是最后责任时刻。适应度函数对这个问题提供了指导建议。那些会对架构、设计选型或者项目的关键成功有重大影响的决策,都必须尽早做出,延缓这些决策往往得不偿失。
总结
软件架构师常常用画图的方式来解释系统之间的适配,然而太多的架构师并没有意识到,一个静态的二维架构图的生命是很短暂的。软件世界是不断变化的,它是动态的而不是静态的存在。而架构也并不是一个等式,它是持续过程的快照。
持续交付和DevOps运动都诠释了一个问题,即由于忽略实现架构并使之保持常新所要付出的精力。其实架构建模和付出这些精力本没有什么问题,但是架构的实现仅仅是第一步。架构只是抽象,直到真正投入运维。换句话说,如果你没有实现并升级过一个架构,甚至帮它度过难关,那么你就无法真正判断架构的长期生命力。
架构师的运维意识是演进式架构的关键。演进会影响具体的实现,因此这些实现细节是不能忽略的。持续交付在架构层面的要求,使得实现更可视化,演进更简单化。因此,持续交付是所有演进式架构重要的助力。