对于架构师的职责,最近感慨颇深;很认同Martin Fowler说的:
Improving the development team’s ability gives an architect much greater leverage than being the sole decision maker. —— Martin Fowler
大意就是提高团队整体能力的架构师比只做决策的架构师更牛逼。因为对于每个业务系统,我们都需要从这个业务的架构视角进行代码开发/优化和迭代,所以每个业务系统的开发者,都应该要有架构师属性,这里我将其定义为纵向架构师;对于目标在提高整个团队水平、确定多业务边界的,则定义为横向架构师。纵向架构师需要聚焦业务和系统,做问题和结果的定义,做系统、模块、代码设计;横向架构师需要解决跨域问题,确定跨团队边界,定义规范,统一语言,定义纵向架构能力基础/标准的成长路径和方法论,让人人都成为架构师,让团队更加敏捷;简而言之,其核心过程是探讨如何完成架构设计,如何拥有架构思维。
2.1 什么是架构师
引用wiki的定义:
软件架构师定义和设计软件的模块化,模块之间的交互,用户界面风格,对外接口方法,创新的设计特性,以及高层事物的对象操作、逻辑和流程。软件架构师与客户商谈概念上的事情,与经理商谈广泛的设计问题,与软件工程师商谈创新的结构特性,与程序员商谈实现技巧,外观和风格。
总结一下,架构师需要提炼需求方关于项目的概念并和需求方有统一的沟通语言;需要基于利益相关方总结关键质量属性,给出满足质量属性的软件系统架构设计;需要与开发工程师共同进行软件系统的落地。
这里的质量属性指的是评估系统性能的非功能性需求,如可扩展性,可用性,一致性等;wiki中的定义如下:
Within systems engineering, quality attributes are realized non-functional requirements used to evaluate the performance of a system.
基于wiki对架构师的定义我们可以看出,架构师的能力模型需要包含
总而言之,架构师是需要在日常中,多维且持续去坚持锻炼的,包括业务抽象能力、代码能力、复杂问题的解决能力、计算机基础、sql调优,项目管理推进能力等。
2.2 方法论
这里把方法论单独拎出来,一是因为要聊如何成为架构师,就要聊成为架构师的方法论和实践的故事;二是在刚参加工作的时候,我应该和大多数同学一样,认为方法论很虚头巴脑;原因也很简单,因为那个时候没有足够的经验对方法论感同身受,也没有接纳方法论的那份心情。现在回过头来看,学习方法论->实践->基于方法论对实践进行反思 的循环,正正是我们自身能力螺旋上升的典型模式。
再讲一个更亲切地能说明其重要性的例子。我们都知道关于路程/时间/加速度有两条非常容易通过实验得出的公式:s = 1/2 * (V0 + Vt) * t、a = (V0 - Vt) / t;基于这两个公式,我们一顿推导,将时间t消掉,可以得到 Vt^2 - V0^2 = 2as的公式,应用非常的广;经过我们多次的刷题实践和思考,这个公式在我们心里已经是一条基础公式,做题的时候,我们完全不需要再耗费精力去将它推导得出。Vt^2 - V0^2 = 2as 这条公式,就是一个【方法论】。初看这条公式的时候,觉得也不过如此,静下来推导一番,也能够得出,然后再用它去解决问题。但是推导它是需要耗费精力的,人最宝贵的资源之一就是精力;当你将这个公式作为一个方法论铭刻于心后,在解决关于这个公式的相关问题时,就可以释放出推导它的这部分精力,用来解决更有挑战性的部分。可见,我们还在读书的时候,已经深刻且频繁地在使用方法论了。
这篇文章的标题是人人都是架构师,这里最重要的其实是希望人人都具备架构师思维;忘了之前看的哪篇文章里面有写到,“不一定人人都能够是经济学家,但可以人人都具备经济思维;不一定人人都能够是历史学家,但可以人人都具有鉴往知来的思维”;写得很精辟。要完成这一章的探讨和编写,需要回答好这么几个问题。架构是什么?架构师要解决的问题有哪些?解决这些问题的方法论是什么?
3.1 架构是什么
在ISO/IEC 42010:20072中对架构有如下定义:
The fundamental organization of a system, embodied in its components, their relationships to each other and the environment, and the principles governing its design and evolution.
这个定义对架构的描述高屋建瓴;它描述了架构是由组成模块、特定上下文环境下模块间的关系、指导模块和关系发展的原则。就像代码中的基类一样,越顶层的抽象描述事物的范围越广,对于软件系统架构,我们其实可以从一个软件系统最终落地的结果来看,得到软件系统架构关于架构的具体实现:
3.2 架构师要解决什么问题
在3.1 中我们讲到软件系统架构有哪些,这些也正是架构师要产出以及要落地的内容,即输出;对于业务系统而言,我们的输入往往是业务需求/技术需求的描述集合。如下图所示,其中的长方形就是架构师要解决的问题:如何将业务/技术 需求描述 加工成 落地的软件系统架构。
从3.1中讲到的 数据存储架构/对象架构/系统模块架构 向上推导,我们需要有业务领域模型,才能够设计软件系统的领域模型;而要得到业务领域模型,我们需要对需求充分了解,充分挖掘,并评估各个利益相关方关注的核心质量属性和业务目标。因此,架构师要解决的问题如下图所示:
从上往下,我们首先需要了解需求,包括利益相关方的业务目标/动机、关键架构需求和用例集;其中利益相关方的业务目标和动机会影响质量属性的优先级以及架构的设计选型;关键架构需求一般包含以下四块:
最后是用例集,它是一组相关的成功和失败的场景集合,用来描述参与者如何使用系统来实现目标;优秀的产品的prd文档,应该要无限接近于标准的格式化用例描述集合,它是原始产品方案通过多轮沟通对焦,得出来的标准化语言,一个用例应该要包含 参与者(可以是系统或者用户)+前置条件(在什么场景下)+如何使用系统+经过什么规则+产生什么结果;而这样的用例集合,应该要能够描述出产品系统的功能全貌。
当能够从 业务目标、关键架构需求和用例集 将需求描述清晰了之后,就可以开始用业务模型来描述项目产品,这一层设计的是问题空间中的领域模型,它是对客观物理世界中概念、规则、关系的分析和描述,与软件系统无关。引用前辈的总结,“这个层次上的实体我们称之为概念实体,这部分内容是用在需求和业务分析上的,讨论业务概念模型时完全不需要考虑软件的实现,这个过程是一个分析过程,即使不做软件研发,做其他的研发,类似的分析过程也应该是有的”。《实现领域模型驱动》这本书也对问题空间做了定义,“问题空间是顶级域和其他域的组合,以及域之间的关联关系,即使没有软件的存在,这些域还是存在的,域之间的关系也还是存在的,领域模型之间的关系也还是存在的”。最后我们要把问题空间的领域模型映射到解决问题空间,《实现领域模型驱动》中对解决问题空间的定义是“一组特定的软件模型,它通过软件的方式来实现解决方案”,这一组特定的软件模型,也就是我们要产出的软件系统架构(系统模块架构、对象架构、存储架构)。现在有很多讲架构的文章,大多是讲的这一层的故事,甚至于讲的是这一层中系统模块架构的故事。比如mvc架构,六边形架构,洋葱架构,整洁架构等。《领域驱动设计,软件核心复杂性应对之道》 中则花了非常大的篇幅,讲实体、值对象、服务、聚合根、工厂、仓库、界限上下文等,这是一套将问题空间领域模型映射到解决空间对象架构的设计模式。
3.3 怎么解决架构师要解决的问题
千里之行,始于足下。只有了解了需求,了解了利益相关方关注点,了解了业务目标,我们才能制定设计策略。
首先我们要梳理需求的利益相关方关注点图表:
这里将相关方进行排序,优先级越高的,他的关注点就更能影响后续决策。与利益相关方进行沟通之后,我们要与他们明确业务目标:背景(原因)+ 主体 + 结果。业务目标是架构的主要驱动因素,多个利益相关方冲突时,以业务目标进行排序抉择。
关键架构需求指的是能够显著影响架构中的结构选择的需求,一般有四类:约束、质量属性、影响较大的功能需求、其他影响因素。具体的定义在3.2中有讲到,这里不做赘述;
用例集是一组相关的成功和失败的场景集合,用来描述参与者如何使用系统来实现目标。单独一个用例,应该要能完整地表达一次或一类业务行为。比如 [拥有88vip的消费者][在购物车同时勾选两个同店铺活动商品时],[点击查看明细按钮],[经过同店活动限购1次的优惠计算规则],[得到只享受一个优惠的价格]。其范式为参与者(可以是系统或者用户)+前置条件(在什么场景下)+如何使用系统+经过什么规则+产生什么结果。优秀的产品的prd文档,应该要包含无限接近于标准的格式化用例描述集合,它是原始产品方案通过多轮沟通对焦,得出来的标准化语言。这里的用例集和我们常说的tc(测试用例)集是有差别的;用例相比tc会更强调过程规则,强调对功能的描述;tc则更关注的是输入和输出,强调覆盖场景,强调对业务功能、业务模型、技术模型的功能性/非功能性验证;
大多数同学都不喜欢写技术方案、架构文档,因为它会占用写代码的时间,而且由于返工和迭代的必然性,架构文档相比代码往往显得过时。但是优秀的架构描述是有用的资产,在多人协作的场景下,在系统需要被多方了解评估或开发测试时,能有效地促进沟通和协作,将设计决策和思想有效地传递给每一个人,提高软件的开发测试质量。口口相传的信息传递效率永远都是最低的。无论是3.2中讲到的业务模型,还是软件系统模型,对其架构的描述,都不可避免地涉及到通过画图来描述全貌。复杂的系统架构,想用单一的模型图来描述,一般只能粗具梗概;所以 ISO/IERC/IEEE 42010:2011 中提出了“视点”的概念,并做了定义:
视点:从不同的视角或者专业领域来看待系统的方法。
常见的视点方法论有以下这些。
场景视图:从外部视角,描述系统的参与者(用户)与系统功能用例的关系。反映的是系统的最终用户需求和交互设计。
逻辑视图:从结构化视角,描述该系统对用户提供的所需功能服务所具备的组件结构和数据结构,以及一些边界约束条件,清晰地描述给用户提供的功能需求服务是如何构建的。描述该系统内部所具备了哪些组织结构,以达到实现对外功能。开发视图:从结构化视角和行为视角,去描述实现系统功能的各个组件和模块是如何实现的。处理视图:从行为视角,描述系统各个组件和模块是如何进行通信的。物理视图:从交互视角,描述系统可以部署到哪些物理环境(如服务器、PC端、移动端等)上和软件环境(如虚拟机、容器、进程等)上。——Phillipe Krutchen
本文从下面两小节的问题空间和解决方案空间的模型来做架构描述:业务领域模型图:描述需求的分析结果,突出业务领域概念和业务模型关系,统一产品需求方、领域专家、开发人员的概念语言;系统模块图:描述系统内的分层模式和模块/领域依赖关系,描述系统间的依赖关系和数据交互方式;对象模型图:描述核心类职责,类与类继承关系,类与类的依赖关系;数据存储架构图:描述库表结构,分库分表策略,存储选型,以及不同数据表之间的一对多/一对一等依赖关系;这些讲的都是一个视点想要表达的内容,而不是只用有一张图来表达的内容;为了让系统的细节更丰富地呈现,我们可以按需绘制精细视图,来描述某个局部的系统细节;为了表达设计原则,我们可以增加质量属性视图,描述对于某个质量属性的设计,如可用性、数据准确性、一致性等,突出质量属性是这个视图可以表现的故事,因为质量属性通常不那么清晰可见,容易被忽略,我们可以在这里将它化虚为实。对于单个视图的绘制,UML是一个比较完善的标准,它可以很好地表现设计构思,但是并不是所有人都对UML了如指掌,我很赞同Simon Brown的观点,“虽然UML很有用,但我更喜欢用简单的方框和线条表现架构。为避免混淆,我建议尽可能使用简单的、不言自明的符号,并添加必要的图例”,这里表现的就是尊重架构受众的思想,因为设计的本质是社交,我们要让我们的设计以更高的效率传递到其他人那里,包括技术人员和非技术人员。
业务模型是问题空间的领域模型,描述业务和产品,与软件系统无关,具体描述的是客观物理世界的概念、规则、关系。要对业务领域建模,首先需要对3.3.1中的用例进行分析,引用前辈的做法:
a、从准确的用例中剥离出名词;b、根据名词梳理领域模型和其属性;c、根据名词的修饰梳理出属性值;d、根据名词的定义完善属性值;e、从用例集合中剥离出动词&形容词;f、根据动词&形容词梳理出领域模型之间的关系;
但仅仅是对用例集进行提取,会遗漏一些隐藏概念,比如关于“一个店铺拥有多个子账号,每个子账号对店铺的可操作权限不同”的描述,我们很容易将操作权限作为子账号的一个属性,子账号则挂靠在店铺实体上。
相信做软件开发的大家对这个例子应该很亲切,因为我们一般都会将权限拎出来,作为单独的实体概念,再将权限关联在帐号上,也方便权限的维护的后续的继承:
这里就涉及到架构元模型(元模型定义了模型中使用的概念和使用规则)中隐藏概念的建立,好奇心循环是我们建立元模型的手段, 从提问开始,建立模型、检验模型、分离概念。
关于前面讲的提取的名词(概念和属性),还有很重要的需要注意的点是,这些词语需要与各个相关方统一语言;Michael Keeling和Eric Evans在他们的书中,都不约而同地强调了业务与技术之间统一通用语言的重要性;如果产品需求方、领域专家、开发人员对业务模型的描述语言都局限在自己的圈子内,那么沟通将引入巨大的翻译开销。有过跨团队协作的同学应该能够深刻理解这种翻译带来的误解的风险和成本有多高,并且这种翻译将使得沟通不畅,各方在做领域知识消化的时候,都需要耗费额外的精力。所以在业务模型的设计时,开发人员与业务相关方之间要建立通用语,所有利益相关方都有权理解架构,有权知道系统中的各个模块是如何协同运作。
系统模块架构核心描述系统内的分层模式和模块/领域依赖关系,描述系统间的依赖关系和数据交互方式;关于系统分层模式和模块/领域的依赖关系方法论,《DDD中常提到的应用架构总结(六边形、洋葱、整洁、清晰)》中有一张图画的非常好,一图即可概览主流架构分层思路在ddd方向上的演化,这里引用一番:
最开始的MVC架构,对系统做简单分层,描述了从数据层 到 业务逻辑层 到 数据出口层的关系;由于mvc中,业务逻辑对数据出入口的依赖是确定的,所以这个依赖变化时,改动成本高,无法突出领域模型的独立性;于是六边形架构横空出世,它将领域层独立出来,不依赖任何确定的外部服务,以端口/适配器的方式定义外部服务交互协议,只要能实现这套交互协议,数据出入口的依赖变化,对领域层是没有侵蚀的;这里体现的是领域驱动设计的思想,将领域知识立在最重要的地位,不为任何模块影响。最后是清晰架构,它的最内层是领域层,包含领域模型和领域服务,实现相关的领域知识和概念模型;向外是应用服务,可以依赖领域层,做业务用例的编排实现,比如操作某个领域服务后,再操作某个实体进行某项行为,最后发送某个领域事件等;和应用服务层同级的,还有CQRS和事件/消息处理器,接受不同类型的命令执行类似应用服务的事情;以上这些即应用核心,应用核心与外界的交互分为两类:图中的左半边为主动适配器,做类似于Controller或是HSF服务的系统最外层请求实现;图中的右半边为被动适配器,定义消息出口、数据持久化接口、搜索引擎接口等,由外部具体的基础设施实现。可见,清晰架构属于集前人所长,提供的一份以领域知识为核心的分层架构指南。除了系统内部的分层结构,我们还需要描述系统间的关系,应用间的关系,以及数据流转的方式,如下图:
按照3.3.3中抽象出来的业务领域模型来编写代码,能够使代码更好地表达设计含义,并且使模型与实际的系统相契合;面向对象编程之所以强大,就是因为它为架构概念提供了实现方式,能描述现实物理世界中的关系(操作、继承、组合)和模型(定义、属性、职责)。《领域驱动设计,软件核心复杂性应对之道》中有大量的篇幅讲解用面向对象的思路对类的类型进行划分,并将业务模型映射到对象模型中的模式:
对于更加关注"行为"而非"唯一性"的纯计算型应用,给出划分实体与值对象的另一种思路:
1、实体是会对自身属性做出强解释行为的类型。2、值对象是轻属性解释,重属性设计的类型。理由是,纯计算型应用,业务关注重点是行为,当一个类需要承载复杂的计算逻辑,即对自身属性需要进行强解释行为时,它往往就承载了系统中更重要的职责,能更加凸显领域业务概念。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。