浅谈架构设计(吐血原创)

577 阅读17分钟

解决的问题

要谈架构设计,首先要确定架构要解决的主要问题、主要矛盾是什么。只有定义好问题,才能有的放矢。

我们知道,在软件开发过程中,作为开发者主要实现的是软件的功能性需求和非功能性需求。其中功能性需求是软件本身对外部世界表现的能力,比如从海量数据中推荐匹配用户偏好的内容,亦或用户的一次点击弹窗。非功能需求是蕴含在软件内部的能力,比如软件的扩展性、性能、可用性等。

首先要确定的是,架构跟软件的功能性需求关系不大,无论采用何种技术架构,基本都是能实现软件的功能。因此架构要解决的问题是软件的非功能性需求的约束。这需要开发者在众多的非功能性质量约束下(不同非功能性需求之间可能存在互相矛盾的特性),不断权衡取舍,最终实现软件的非功能性需求。下图是软件的质量属性分类图,包含软件的功能性和非功能性质量属性,供大家参考。

基本原则

原则是根本性的真理,它构成了行动的基础,通过行动让你实现生命中的愿望。原则可以不断地被应用于类似的情况,以帮助你实现目标。《原则》 作者:瑞·达利欧(Ray Dalio)

正如瑞·达利欧所说,原则是根本性的真理。因此在上一节确定了架构设计解决的问题后,本节会介绍架构设计过程中需要遵守的基本原则,有了基本原则就不会迷失在技术架构的细节中,保证大的方向不偏航。

在我看来,软件架构的基本原则总计下来主要有两点:合适原则和演进原则

合适原则

我的父亲拥有机械工程学的双学位,但他安装信箱的办法并没有显得与众不同:先在路边刨个坑,接着安放信箱的立柱,最后用水泥填满缝隙就大功告成了。他懂得计算动量、压力和拉力,但这并不意味着为了这么点儿小事,还得大动干戈。《恰如其分的软件架构》作者:George Fairbanks

上面的小故事来源于《恰如其分的软件架构》一书,书中描述了一种怡如其分的架构设计方法。作者建议根据项目面临的风险来调整架构设计的成本,并从多个视角阐述了软件架构的建模过程和方法。我觉得本书的英文名称对“合适原则”表达的更加贴切---《Just Enough Software Architecture》。“Just Enough”表达了既不过度架构设计,同时又满足了当前的非功能性需求的约束。

我们在做技术架构设计过程中,心中时刻要牢记合适原则。在实践中过程中发现,经常犯的错误是过度设计,看到业界有了新的技术、框架就无法克制内心冲动,没有从自身业务实际情况出发,盲目使用更新、更先进的技术。导致新技术在团队落地后“水土不服”,为后续架构演进埋下隐患。因此我们一定要克制过度设计的冲动,做到 “Just Enough”。

演进原则

关于架构的演进原则,我想大家一定都听过“好的架构不是设计出来的,是演进、进化出来的”这句名言。的确,在业界我们看到了太多的架构演进的成功案例。在这些案例中,我们都看到了架构演进的共同推动力量,总结下来主要有:

  • 业务的发展。这是架构不断演进的核心动力。在面临用户量级、业务复杂度、团队协作人数等诸多影响架构的因素发生变化的时候,就需要不断的调整架构以适应新的情况。
  • 技术的发展。软件开发属于相对新的领域,也不过几十年的历史。在编程语言、编程范式、架构模式、项目管理和协作方式等方面都在发展,在解决开发效率、软件质量等方面不断涌现新的技术和解决方案,在适当的时机,经过充分评估后,可以使用新技术提高软件开发生产力。
  • 架构的腐化。我们都知道热力学中的熵增定律:在一个孤立系统里,如果没有外力做功,其总混乱度(即熵)会不断增大。软件作为宇宙中的普通一份子,也逃脱不了熵增定律,正如薛定谔所说,人活着就是在对抗熵增定律,生命以负熵为生。为了抵抗软件的不断腐化,需要通过不断的“外力做功”减少软件的熵,这里的“外力做功”就是我们为架构演进不断进行的重构

更多关于架构演进的内容,推荐大家看下《演进式架构》一书,这本书创新性的提出了通过**“适应度函数”**来评估变化对架构重要特征的影响,并防止这些特征随着时间的推移而退化。

上面讲了架构的两大原则,“合适”+“演进”,这两个原则是指导软件架构方向的基本原则。“合适”给软件架构提供了一个符合现阶段的架构初始值,“演进”能够让软件架构不断地发展以适应不断产生的新变化,这恰好也是数学归纳法的基本逻辑所在,也从数学角度侧面论证了遵守两个原则是设计出好的软件架构的必要性条件

优秀架构

上面介绍的基本原则是大方向,在具体进行架构设计和选型的过程中,则很有多细节需要考虑和注意。下面将围绕如何做好架构设计以及在架构设计过程中需要考虑哪些因素,做更细致的介绍。

组织架构

在设计系统时,组织所交付的方案结构将不可避免地与其沟通结构一致。——梅尔文·康威

1968年4月,梅尔文·康威在《哈佛商业评论》上发表了一篇名为“How Do Committees Invent? ”的论文。在这篇论文中,康威提出:社会结构,特别是人与人之间的沟通途径,将不可避免地影响最终的产品设计。康威描述道,在设计的最初阶段,人们首先需要高瞻远瞩地思考如何将职责划分为不同的模式。团队分解问题的方式会左右他们之后的选择,这便是康威定律。

大家平时看一些架构相关的文章,主要还是在讲具体的技术。在我看来,影响架构设计好坏的第一个主要因素是组织架构。“康威定律”告诉我们,架构设计受组织架构的影响和制约,因为组织架构和软件架构往往具有高度一致性。目前很多软件公司在组织架构设计上,根据技术栈进行组织划分,比如有“后端团队”、“前端团队”、“客户端团队”、“测试团队”、“产品团队”和“运营团队”等。这种团队组织方式,在一定的历史时期,对软件开发质量和效率的提升确起到了积极作用。

近些年来,随着对业务迭代和交付速度要求的越来越高,对团队协作方式也提出了新的挑战。许多公司开始围绕垂直业务构建敏捷团队,其特点之一将开发团队转换为跨职能团队,改变之前按孤立的技术架构来划分的方式。团队作为一个整体,拥有创造产品增量所需要的全部技能。这种划分方式使得团队得以自治和闭环,减少了跨部门的协调和冲突,提高了开发效率,同时也必然会产出更合理的软件架构。因此通过组织结构的重构是可以重构技术架构,从而提高软件交付的质量和效率,即所谓“康威逆定律”。

技术特性

在具体实践软件架构设计的过程中,会面临很对技术上的选型。在开发语言选型时,需要在C++、Java、Go等众多语言中选择,在架构模式选型时,需要单体架构、分布式架构、微服务架构、无服务架构等架构模式之间选择。在开发框架或中间件选型的时候,客户端开发、前端开发和服务端开发每个技术栈和细化到每个功能模块都有数不尽的开源或闭源的实现,开发同学在面临这些选择的时候,需要从如下几个方向去考察每个技术的特性,最终从多个方案中对比、权衡选择出最适合的技术方案

  • 社区活跃度、规模。社区活跃度高、规模大的技术,在技术支持、最佳实践等方面都有比较大的优势,因此在选择具体技术的时候,需要进行相关的考察。
  • 技术是否成熟。在技术选型的时候,建议选择主流、成熟的技术。可以从技术的存续时间和是否有大型公司、大规模、高并发等复杂环境的实践等方面去判断。
  • 开闭源的状态。选择开源还是闭源的技术,这个还是要跟企业的发展阶段和业务特性去决定,二者各有优缺点,没有绝对的优劣。如果选择开源技术,可以从star数量、是否持续维护、issue数量和解决速度、开源协议等方面考量。如果选择闭源技术,可以从费用、技术支持、公司品牌等方面考量。
  • 技术指标。具体到某个技术的指标,设计内容十分繁杂,没法有普适性的方法。在架构选型的时候,通常需要考量的技术指标主要有可用性、性能、扩展性、跨平台、安全、维护性等方面考量,同时结合自身业务需求和团队技术特点,选择最合适的技术。

合适度

合适度是对“合适原则”的具体阐释,这里简单介绍下,在架构设计过程中关于“合适原则”需要注意的事项。

  • 适合业务特点。在架构设计时,一定要了深入解业务现状,要选择、设计与现有业务阶段匹配的技术架构。否者设计出来的架构将会是空中楼阁,极易倒塌。关于业务的适配度,主要考察业务的领域逻辑上线时间、生命周期、重要程度和发展规模等
  • 适合团队特点。无论何种技术架构,最终还是需要具体到团队的开发同学落地。如果架构跟团队不适配,也会导致水土不服。关于团队特点主要有人员规模、技术栈、熟悉领域、现有架构和未来规划等。

不断演进

不断演进是对“演进原则”的具体阐释,在“演进原则”一节中我们介绍了架构演进的推动力量,主要有**“业务的发展”、“技术的发展”和“架构的腐化”。**在软件迭代演进的过程中,我们需要做的事情主要有:

  • 保障机制。这里的保障机制主要是保障软件架构不会随着迭代,架构越来越腐化。首先需要确定保障的技术指标(参考“解决的问题”一节中的图,选择现阶段最重要的结束指标。其次在选择好需要保障的技术指标后,需要为其量化。最后需要有卡口机制,保障相应的技术指标不会随着迭代腐化,建议在CI/CD时机进行卡口,同时在团队内部制定相应的制度。

  • 持续重构。持续重构是对抗熵增的主要手段,为了防止“破窗效应”的发生,需要我们在软件开发过程中成频繁重构、小幅重构。强烈推荐大家深入阅读Martin Fowler写的《重构 改善既有代码的设计》一书。

合理的架构划分

好的架构设计需要有合理的、清晰的划分,从宏观角度看,架构需要做好横向领域边界划分和纵向领域内层次划分。

横向领域边界划分

世界是复杂的,面对复杂的问题,有**“系统论”和“还原论”两种理论去求解。软件是真实世界在软件领域的模拟,在软件领域“还原论”是更贴近答案的解法**。还原论认为复杂的系统、事务、现象可以通过将其化解为各部分之组合的方法,加以理解和描述,特化到软件开发领域就是我们常说的“分治”,通过将软件递归划分为更小的模块来更好地解决软件的复杂度

在划分领域边界时,DDD通过科学的领域划分,可帮我们更好地应对领域的复杂问题(关于DDD推荐《领域驱动设计:软件核心复杂性应对之道》和《实现领域驱动设计》两本书)。业务开发中的领域有:**核心领域、支撑领域和易变领域。**领域边界划分是一个递归的过程,方法论是一致的。

  • 核心领域提供核心、创新价值,需要我们着重设计,亲自编写领域逻辑代码。
  • 支撑领域是支撑性、非核心价值的领域,我们可以协议化领域服务,通过IOC的方式与核心领域解耦,可以考虑使用外部实现。
  • 易变领域是在后续的业务迭代过程中,变化概率较大的领域,比如UI、数据库、框架、平台等。通过**协议化扩展点、IOC等方式来实现开闭,**从而增强扩展性。

纵向领域内层次划分。

在横向按领域划分好架构后,在每个每个领域内,纵向需要按层次划分清晰的架构层次在维护性和扩展性上都有很大的优势。常见的分层架构模式有:

  • 分层架构

  • 六边形架构(端口适配器)

  • 整洁架构(洋葱架构)

架构坏味道

上面介绍了优秀的架构应该具备特性,这些特性为我们设计出优秀架构提供了最佳实践和架构模式。在提供了好的标准的同时,也需要“坏味道”来提醒我们不要落入坏架构的陷阱。我们都知道《重构 改善既有代码的设计》中24种“代码坏味道”,架构同样也有坏味道,总结主要有如下几点:

耦合外部实现

在“横向领域边界划分”里面我们介绍过,对于支撑域,我们在经过外部方案选型后,可以使用外部实现,从而减少开发成本,加快迭代速度,将更多的精力集中到提供核心、创新价值的核心域。

在使用外部实现时,经常犯的错误是没有构建**“防腐层”,导致外部实现扩散到架构的各个层次中。而随着业务和外部实现的不断发展,经常会出现外部实现无法满足业务的诉求,在替换外部实现时,这种“耦合外部实现”的架构坏味道会带来“霰弹枪式”(Sloggun Surgery)修改,也即需要在架构全局各层进行代码级别的修改**,在带来大量的工作量的同时,对代码的稳定性也带来了巨大的挑战。

所谓“防腐层”就是在外部实现和内部实现之间建立一道**“屏障”,保证外部实现不会扩散到内部实现中。具体可以将所需要的外部能力抽象为协议(注意是所需要能力,不是外部实现), 然后将外部实现作为协议的实现方**提供给内部使用。这样在需要替换外部实现时,只需要替换为新的实现方即可。“防腐层”带来的好处主要有:

  • 修改代码范围小。不需要全局各处做修改,只需要替换新的协议实现,代码影响范围相对小。在替换外部实现的时候遵守了**“开闭原则”。**
  • 对业务稳定影响小。如果替换的同时,能够结合单元测试进行代码保障**,对业务稳定性的影响将至最低**。

最后10%的陷阱

“最后10%的陷阱”描述了这样一种现象,在外部实现选型时,发现外部实现可以快速满足大部分需求,因此就草率的进行了方案确定。然而在后续的实际的开发过程中,发现某个“特定的”、“小的”需求无法轻而易举的实现,需要我们付出数倍的努力去实现。

为了防止“最后10%的陷阱”,我们在进行方案选型时,需要注意以下几点:

  • 对核心领域有深入的理解,对现有和未来需求有准确判断,尽量不遗漏能力需求。
  • 全面、系统性的调研方案所提供的能力。
  • 在同类型的方案中进行能力对比选型
  • 借助**“曳光弹”**式的开发方式(详见《程序员修炼之道:从小工到专家》),快速验证方案。

简历驱动开发

架构应该基于“合适原则”进行设计,但在实践过程中,架构师们经常抵不住**“诱惑”,**设计出来的架构表面十分漂亮、复杂,在个人简历中十分亮眼。实际跟现有业务并不匹配,在后续的迭代过程中增加了开发成本。因此在架构设计中一定要抵制这些诱惑,脚踏实地的设计出合适的架构。这里总结常见的诱惑有:

  • 最新的、流行的技术。
  • “大厂”使用的技术。
  • “能力过载”的技术。

过度、无架构设计

过度架构设计是超出领域需求的架构设计。过度的设计会导致系统超出所需要的复杂度,给后续的维护性、扩展性上带来的灾难性的后果。上面我们提到了架构设计中的**“演进原则”,**合理的架构设计方式应该是首先设计合适的架构,在后续的演进过中再根据业务需求进行架构重构,而不是过渡设计。

无架构设计是另一个极端,在业务开发过程中没有进行合适的架构设计,随心所欲的开发。特别是敏捷开发盛行的当下,很多开发同学拿敏捷为自己的懒惰开脱。同时采用无架构设计的团队,一般也不会在后续的迭代过程中持续演进架构。无架构设计带来的危害是随着业务的复杂度增加,越来越难向系统中加入新的功能,导致最后不得不进行全盘推翻,进行重写

总结

本文对架构解决的问题、基本原则进行了简要阐述,同时论述了优秀的、坏的架构具备的特点。由于篇幅有限,对具体的技术和应用的案例没有展开介绍,如果大家有兴趣,后续会针对个别小节进行详细的展开。希望文章的内容能够对大家架构设计带来小小的帮助~