原文:https://www.thoughtworks.com/insights/blog/microservices-evolutionary-architecture 本文翻译时《演进式架构》已由人民邮电出版社出版。
微服务架构风格正在风靡全球。 2015年3月 O'Reilly 举办了第一次软件架构大会,委员会收到的大部分摘要都涉及微服务的某些方面。为什么这种架构风格突然风靡一时?
微服务是 DevOps 革命后的第一个架构风格,也是第一个完全接受持续交付工程实践的架构风格。它也是一个演进式架构的例子,把支持增量式非破坏性的变更作为众多应用架构维度的第一原则。然而,在众多支持某些特定架构演化行为的架构中,它仅是其中一种。 本文探讨了这类架构风格的一些特点和原则。
演进式架构
软件行业中的常识曾坚持认为架构元素“以后很难改变”。一个演进式的架构会把支持增量变更的设计作为首要原则。演进式架构之所以吸引人,是因为软件变更在历史上难以预测且改造成本高昂。如果将演进的变更内置到架构中,软件的变更将变得更容易、更便宜。进而允许开发实践、发布实践的变更以及整体的敏捷性发生改变。
微服务之所以符合这个定义,是因为其强大的限界上下文原则,使得在领域驱动设计(Domain Driven Desgin)中描述的逻辑划分成为物理上隔离的原则。微服务通过机器配置、测试和自动化部署等高级 DevOps 实践来实现这种物理上隔离。因为每个服务都与所有其他服务(在结构级别)解耦,所以微服务的替换就像替换乐高积木中的一片。
演进式架构的特征
演进式架构会表现出几个共同的特征。我们在《演进式架构》一书定义了大量的特征;本文将介绍其中的一小部分。
模块化和耦合
如果开发人员想要进行非破坏性更改,沿着明确定义的边界分离组件的能力具有明显的好处。没有任何架构元素会因为缺乏边界划分而无法演进,例如通常说的大泥球架构。
大泥球架构
上图来自某客户项目的大泥球中的类(圆形边界上的点)之间的耦合。
不适当的耦合会以难以预测的方式传递变化从而抑制架构演进。演进式架构都支持某种程度的模块化,往往表现在技术架构上(例如经典的分层架构)。
围绕业务能力组织架构
受领域驱动设计的启发,现代成功的架构也越来越多地在领域架构级别具有模块化的特征。基于服务(Service-based)的架构与传统面向服务架构(Service-Oriented Architecture,SOA)的主要区别在于分区策略:面向服务架构严格按照技术层次进行服务划分,而基于服务的架构则倾向于微服务的按领域划分。
实验
实验是演进式架构向业务提供的“超级能力”之一。一些运维成本较低的细微应用程序变更使得持续交付中的 A/B 测试、金丝雀发布 等实践变得可行。通常,微服务架构是围绕服务之间的路由设计的,用以定义应用程序,并允许特定服务的多个版本同时存在。这又反过来允许对现有服务的功能进行实验和逐步替代。最终,这种力量能让你的企业用更少的时间来评估需求优先级,取而代之的是通过假设驱动开发。
演进式架构的原则
通过原则是思考演进式架构的一种方式。这些原则描述了架构本身或设计架构设计方法的各种特征。一些原则将注意力集中在设计架构过程中何时做出特定的架构决策。
适应度函数
我们区分新生(emergent)和演进式(evolutionary)的架构。这种区分很重要,就像在遗传算法等演化计算技术中一样,架构适应度函数规定了目标架构的特性。一些系统需要较长的正常运行时间,而另一些则更关心吞吐量或安全性。
适应度函数
上图是用于突出显示适用于该软件系统的重要适应度函数的雷达图。
首先需要思考的问题是用什么样的适应度函数应该为特定系统的提供决策指导。架构决策是通过适应度函数进行评分的,因而可以看到架构是否正在朝着正确的方向演进。
负痛前行
受极限编程社区的启发,持续交付和演进式架构中的许多实践都体现了“负痛前行”的原则。 当项目中的某些事情有可能导致痛苦时,强迫自己更早更频繁去做这些事,这反过来又会鼓励你将痛苦自动化并及早发现问题。 常见的持续交付实践,如部署流水线、自动机器配置和数据库迁移,通过消除变更中的常见痛点,使架构演进更容易。
最后的负责时刻
何时做出架构决策是传统架构和演进架构之间的主要区别。这些决策可能围绕应用程序的结构、技术栈、特定工具或通信模式。在传统架构中,这些决策会在编写代码之前很早就表明。而在演进式架构中,我们会等待最后的需要负责的时刻做出决定。延迟决策的好处是可获得可用于做出决策的额外信息。成本是一旦做出决定就可能产生的返工工作量。这可以通过适当的抽象来减轻,但成本仍然是真实存在的。过早做出决定的代价也是真实的。考虑选择消息工具。不同的工具支持不同的功能。如果我们选择了一个比我们最终的需要更重的工具,它就会成为项目中技术债务的来源。这种债务以使用错误工具造成的演进负担的形式出现。这不是先发制人地“抽象所有事物”的借口,而是在适当的时候做出决定的明智尝试。我们仍然支持敏捷中的 YAGNI(You aren't gonna need it,你不需要它)原则。
当然,最直接的问题是什么时候才是“最后的负责时刻”。适应度函数为“最后的负责时刻”的决定提供指导。应尽早做出对架构设计取舍产生重大影响的决策或影响项目关键成功因素的决策。 推迟这样的决定对项目的影响往往大于等待的收益。
结论
软件架构师有责任阐明关于系统如何满足所有需求的决策。通常是采用创建图表的方式。太多的架构师没有意识到,静态的架构图保质期很短。软件行业是一个不断变化的领域;它是动态的而不是静态的。架构不是一个方程式,而是一个正在进行过程的快照。
持续交付和 DevOps 运动说明了忽视实现架构和保持更新所需工作量的陷阱。对架构进行建模并完成这些工作没有错,但这只是第一步,而架构在运行之前是抽象的。 换句话说,你不能真正判断任何架构的长期可行性,除非你不仅实现了架构并且对其进行了升级。甚至可能使它能承受更不寻常的事件。
架构师的运维意识对于演进式架构至关重要。演进会影响实现的细节,因此不能忽略实现细节。持续交付对架构的要求使架构的实现更加可见并简化了架构的演进。 因此,持续交付是任何演进架构的重要推动力。