码农戏码

新生代农民工的自我修养

0%

那一年有个程序员在github上撕心裂肺痛首疾呼

程序员呐喊

这句呐喊,喊出了多少程序员内心的苦楚,行业更新迭代太快,而年龄在不断增长,精力在下降,尤其现如今的内卷文化,怎么才能不掉队,保持竞争力?似乎唯有保持学习能力一条路。

我们是怎么学习的呢?学习的效果如何?需要去对学习有一定的元认知能力。

李笑来老师有个专栏,叫学习学习再学习。意思是讲先把学习这件事学习会了,再去学习其他知识。

我也一直在反思自己的学习能力,学习效果与速度,所以也一直在学习怎么学习,怎么学习才有效果,脱离低级学习效能。毕竟学习本身不仅对自身有帮助,也是家中无矿普通大众唯一可以传承给后代的财富,知识时代,缺什么也不能缺少学习能力。

功夫

我们学习的动机是什么?

一开始我们靠兴趣,但是兴趣多变;然后我们追新知,发现新知进化得比我们学习的速度还快;之后我们回身去读经典,却发现经典一辈子也读不完;于是我们开始寻求底层逻辑。

然而底层逻辑是高度抽象的,人类对抽象的认知都是非常困难的,幸运的是所有对抽象的认知都是源于对大量具体事物认知的抽象。

我们一般都不是骨骼清奇的天才,大多数时候只能渐修顿悟。就算是真经放在眼前,也没有识货的能力。

举个切身经历的案例,在我刚入职场时,那时我还很痴迷于阅读源码,一天我灵机一动,学生时代就倒腾了几年的web开发,都是跑在web container中的,为何不看看tomcat源码呢,下班看,上班干完手上活也看。一天领导问我在干吗,我兴致勃勃地说在看tomcat源码,写得真精妙。

领导把我叫到一边,语重心长地说,学习源码是不错,但对我们当前工作有直接帮助吗?我们开发游戏,用的是TCP协议,使用的是mina、netty网络框架,如果现在项目中要准备使用web了,再看也不迟。就算你现在看了,以后我们真用上,其他人去学习,你也不一定比他们提前多少。

“切,又忽悠我多干活,压榨”。这是我当时第一反应。你觉得这是真经吗?

领导的这番教导,我也是过了很久才慢慢醒悟。是什么底层认知逻辑,且看文末总结。

因此我们还是得踏踏实实地积累基础知识,同时,需要勤于思考,学会抽象,学会抓本质。

花功夫,事上修。否则读遍经书也枉然。

如何学习

学习一样技能,需要经过三个过程

1、编码:知道并理解,形成一些潜在的心理表征

2、巩固:强化心理表征,通过遗忘,再考试,再练习不断地被挑战

3、检索:学以致用

细化一下这个过程

大概会经过这几个步骤:获取、理解、拓展、纠错、应用

获取

不再像过去贫瘠时代,获取知识本身的难度,现今是如何面对信息大爆炸时代,过滤出精品。

获取知识的两个要点:

1、速度

2、质量

对于速度,有几个方法:

首先让自己变成一个文字型学习者,现在有视频、音频等多媒体信息,而文字是最快速的获取方式。回想微信聊天,是看文字快还是语音快?

其次学习一些快速阅读的技能

1、指读法:对的,你没看错,用手指指着文字阅读。

2、练习阅读法:刻意练习,比如手指移动更快些,又快又能最大吸收信息

3、积极阅读法:也就是带着问题阅读,或者读完问自己几个问题,这一节主要观点是什么;怎么能记住主要观点;观点拓展以及应用

比如最近有篇转来转去的文章《我在美团的八年》,作者总结了几个原则,第一次看觉得作者讲得真不错,可当在聊天群中再次看到被人转发时,只是想这文章我看过,作者写得不错。但具体内容可能都忘光了。这时不妨回忆下,作者说了哪个原则?最认同哪个?能再加减点别的原则。再阅读比对一下。

而对于质量,是要去学习第一手资料学习原理并且及时更新,尤其像程序员,不然看到的知识可能是错的。

比如,我两年前总结的一篇JVM参数文章:《百万QPS系统JVM参数》,如果你现在看到直接照本全收,估计有些参数有效,有些无效,整体效果你会失望,但也别说我乱写,因为JVM在发展,参数也在更新,而且也是在我当时的系统背景下得出的结论,我的认知也可能是不完整的。

但你掌握了JVM调优原理,并结合最新的JVM规范因地制宜,是没问题的,质量也是有保障的。

再比如你正在看的这篇文章,也是很多手信息后的产物。不是说就不要看了,至少可以激发你的思考,进而去拓展学习。

质量和速度兼并的学习技能是联机学习,我们要做知识的路由器

通过交流交换思想,再深入思考,整合归纳。相比个人学习,不管是速度还是质量都会有更大的提升。

为什么要去大厂,不就是能更方便地遇到更牛B的人,快速吸收他们的最新思考成果。

拓展

一味地获取内容和信息,并不一定能达到想要的效果。启发思考才是学习的目的,所以反思是学习真正发生的时候,这样才能去改变我们的行为和思维。

拓展就是思考与反思阶段,只有拓展得好,才会将学到的知道举一反三、触类旁通。也只有这样,后期我们才能应用好知识。

拓展有三种方式:

1、深度拓展

不仅仅理解一个结论就结束,还得挖掘知识从何而来?结论来自何处?一个发现是如何做出来的?结论之前的试验是怎么做的?怎么想起来做的?

一句话,我们要知其然还得知其所以然,多问why,以及how。

2、横向拓展

与知识周围建立联系。

知识不会孤立地存在,与此类似的结论还有哪些?是哪些地方类似?不同的地方在哪里?同一时期还有哪些其他的发现,同一个发现者还有哪些发现,在同一个领域里还有哪些发现?

3、纵向拓展

知识都遵循一定的模式,同样的模式在其他知识中也会出现,能将一个公式与一个自然事件相联系吗?

纵向拓展是有相当难度的。也是最有创造性的学习方式。

比如达芬奇看到鸟在天上飞,他就琢磨鸟在天上飞和鱼在水里游到底有什么共同之处,为什么鱼在水里游那个敏捷的程度明明有水做阻力,但看起来比鸟还快。为什么?后来他就慢慢搞出了流体力学。

这三种方式似乎有些难以理解,可以类比在《架构与架构师》中提到的架构师技术能力包含的知识深度、宽度、广度。

再如最近我写的《大明湖畔的领域模型》,领域模型因OOA被提出,让我们寻找现实中的概念类,万物皆对象,映射到具体软件实现类。而同时期Martin Fowler也提出了Domain Model,却是对OO升华,要充血模型,不要贫血模型。再到现如今大家热议DDD中的领域模型,Eric推崇的分析模型与实现模型统一融合。

一个概念被各方位拓展后,原始的软件方法论被突破,各种全新软件方法论被创造。

高效学习

赚钱只能赚到认知范围内的钱,不然凭运气赚取的钱也会被实力亏掉;同样,学习也是如此。只是知道如何学习,无法内化这些认知,学习行为的效率依然低下。

如何能高效学习,我们需要再一次反思自己对学习的元认知。

错觉

我们的元认知非常容易出现偏差,常会产生两个误区:

1、不知道自己学习中的薄弱之处,不知道要在哪里花更多精力才能提高自己的知识水平

2、爱使用那些会让自己错误地认为掌握了知识的学习方法,也就是拼命记笔记、拼命画下划线、拼命地用荧光笔、拼命地反复阅读

怎么解决这两个误区?巩固与检索。

学习越轻松,效果越不好,看来起来非常勤奋,不停地背书,一遍一遍地背,拿笔一遍一遍地画,甚至是一遍一遍地抄,看起来很勤奋耗费大量的时间,但他的学习过程是很轻松的,没有做到有挑战的事情。

通过检索、考试,不断地挑战来巩固记忆。

比如要跳槽了,怎么去准备面试呢?拿出浩瀚如烟的资料去慢慢复习吗?或者找一堆面试题来刷吗?

这些都很低效,不如自己玩一把角色扮演,扮演下面试官,给自己出一份面试题,出题需要检索知识点,解题也需要挑战记忆。

再按知识模块与各种资料比对自己的知识掌握度,查漏补缺,事半功倍。

问题树

认知心理学认为,有三个前提要求时,学习效率最高:

1、有目标导向,俗称带着问题学习

2、有即时反馈,为什么游戏好玩?

3、最近发展区,当前水平与通过学习获得的潜力之间的差异叫最近发展区

那么怎么样的学习方式才会同时拥有这几个前提呢?使用问题树的思维方式替代知识树的思维方式。

知识树的思路,是典型的专业知识细分的学习路径。工业时代分工高度稳定,每个领域都相对独立、发展缓慢,一个人有机会学完一个细分领域的所有知识。沿着一棵长成的大树向上爬,这种学习路径效率最高。

但在一个高度变化、多领域跨界的时代,完成任何任务都需要调取多领域的知识,全部靠自己学习显然来不及。

当我们想学习某一知识时,常列个计划,搜集资料,罗列书单、阅读清单,可涉及面很广,这样难免有很大的随机时,书单虽全虽好,最后没有动力去读,读了也没有实践的动力。

而以问题为切入点,问题使目标更清晰,不会在知识树里迷路;动力也更强,一个问题解锁后,会带来更多、更大、更有趣的问题。

时代是水流,答案是河岸,而问题是船只。

在水流不快的时代,可以在河岸上慢慢走,也许跟得上水流;但在知识爆炸、洪流时代,只有登上船只,才能保持和时代同步。守在岸上,只能被远远抛下,望洋兴叹。

不要学习

相对现如今提出的终身学习者,是不是有些反人类。

不要学习,指的是在没有明确自己究竟想达到什么目的,就去不停地“学习”,实在是对宝贵时间资源的浪费。而且学习得多,未必收获得就好。

打个比方,学习就好比整个食物经过咀嚼、消化、吸收的过程,它不是表面看起来“吃”的动作。人们不可能永远吃个不停,所以学习行为不是越多越好。

学习需要挑选吃的食物(获取信息)、咀嚼(明白阶段)、消化(理解阶段)、吸收(应用阶段)。

犹如ThoughtWorks中国区CTO徐昊在他的专栏《业务建模》中所说,技术没有反哺过我的生活,反而我的人生给养了在技术上的洞见。吃喝玩乐并不耽误事,因为学习是一种状态(being),而不是一种行为(doing)。在学习的状态中,吃喝玩乐都可以让我成为更好的程序员。

找到自己的目标,不要人云亦云,刻意宣传终身学习更多时刻是商家制造的焦虑感收缴人们的智商税。

总结

世人都知道认知能力重要性时,我们更需要去认知一下如何更好地获得认知的能力。

“学习”本身不是一件容易的事,在我们长期实践“学习”的空隙,需要对“学习”保留一份反思,学习学习,加深对学习元认知的认识。

不需要刻意终身学习,更不必因终身学习而焦虑,它就是我们的血液。是这个时代赋予我们的第二呼吸。

学习行为像吃饭,学习状态像呼吸。不要为了学习而学习,贪图勤奋的假象带来的快感。

回到篇首,提到我的切身经历,问题的本质是认知效率:认知收益与时间精力之比,牛人真正的秘诀是在最精华的资源上,以高很多倍的认知资源来学习。要事第一。

当然在认知水平不够时,也不要吝啬自己的时间。毕竟我们已经与牛人有了差距,花功夫才能得到真功夫。

最后的最后,如果你对学习主题也感兴趣,可以延伸阅读《刻意练习》、《认知天性》、《如何高效学习》、《人是如何学习的》、《卡片笔记法》、《跃迁》。

《软件学报》在2021年第32卷第9期刊登了一篇论文:《领域驱动设计模式的收益与挑战:系统综述》。这篇论文是学术界在这一领域开山之作。

论文是对2003~2019年之间发表的相关文献进行识别、筛选、汇总和分析。

揭示DDDP的应用情况,即哪些DDDP被应用到了软件开发中,以及其所带来的收益、挑战及相应的缓解挑战方法。

对于长期在DDD中折腾的人,值得一读。

一是可以看看学术界的研究成果

二是对比自己实践,是否在康庄大道上

如果你对学术性论文不感兴趣,可以仔细阅读这篇文章,我对论文做些简单摘抄,对比一下自己实践感悟

基本介绍

论文先对DDD的一些概念、特性作了介绍

定义:

DDD是一种软件设计中应该遵循的思维方式,其目标在于加快具有复杂需求的软件项目的研发速度

理论:

通过构建领域模型来解决软件开发的复杂性问题。因为在大多数软件项目中,最困难的往往是解决业务领域复杂性,而非技术复杂性

特征:

设计与开发的绑定,DDD强调软件设计概念必须在开发过程中得以成功实现

DDDP:

DDD方法中,将一组设计实践、技术和原则合并为标准模式,即所谓的领域驱动设计模式(domain driven design pattern,简称DDDP)

基本原则

  • Evans强调设计概念必须在代码中成功实现,否则它们将会变成抽象的讨论。DDD通过引入模型驱动设计建模范式及其构造块,弥补模型与可运行软件之间的差距
  • DDD提倡迭代开发,领域驱动设计与具体实现过程间紧密关系,使得DDD比其他软件设计方法更具实用性
  • DDD追求领域模型需要依靠头脑风暴的创造性和对领域的深入了解,因此在实践过程中,开发人员与领域专家之间需要展开紧密协作
  • DDD是一种软件设计方法,而任何设计出来的领域模型都应该与架构无关。也就是除了领域模型,在软件开发过程中仍然需要架构设计,比如微服务架构或六边形架构

DDD设计模式概览

DDD由Evans作为一种大型模式语言引入,其由一组相互关联的模式组成。

模式语言提供了讨论问题的交流术语,它定义了特定场景、特定问题的解决方案,其主要目的是帮助开发者解决在设计和编程中遇到的通用问题。

模式语言在软件工程中被广泛地应用,比如设计模式、企业架构模式等。

DDD与DDDP的关系,正如同OOD与面向对象设计模式的关系。

对于DDD,最基本的模式是通用语言,它是一种供不同涉众(如开发人员和领域专家)共同使用的语言,主要用来辅助领域建模,,

一种通用语言只适用于单个限界上下文,后者作为一个电焊工的模型边界来维护模型的完整性。

根据Vernon的观点,除了通用语言之外的DDDP,要么属于战略设计模式,要么属于战术设计模式。

战略设计模式

旨在应对具有多个领域模型的大型软件开发项目的复杂性,其中,每个领域模型都属于单独的限界上下文。

限界上下文以外的其他战略设计模式关注如何管理不同限界上下文之间的关系

比如上下文映射负责描述不同领域模型间的通信,而职责分层则站在更高的抽象层级来组织不同领域模型间的概念依赖关系

战术设计模式

负责根据通用语言对单个限界上下文进行领域建模,并结合面向对象原则绑定领域建模和编码实现。包括实体、值对象、聚合、服务、资源库等。

实体和值对象用于对具有不同领域特征的领域对象进行建模;

聚合将一组领域对象绑定为一个整体,以控制事务;

服务则充当领域模型接口,具有无状态特点;

资源库用于封装领域对象的数据库访问操作


感悟

对于基础信息的介绍,可以说基本是符合当前DDD的理论知识,只是表述方式有所差异。但鉴于每个人认知差距,可能抓住的重点信息不同,从我个人认知角度,我来点评一下:

首先是定义,论文指出DDD是一种思维方式

这是种很新颖的定义。在DDD系列文章中,我们也去追寻过DDD定义。但这种定义是第一次听说。

软件设计中最困难的是什么?

论文指出是解决业务领域复杂性,而非技术复杂性。

这个应该会随着软件复杂性越来越高,人们的认知会越来越深。现在技术人员还是过于看重技术,甚至对技术驱动业务深信不疑。现实是怎么样?现在工具人的流行说法已经足以说明,驱动业务是少见的,而跟随业务是常态,成就业务已经了不得

为什么瀑布模型不适合软件行业,就因为隐藏的业务知识太多。不可能一次性就把所有业务知识提取出来,而且在软件开发进程中,业务可能还在变化中发展。

借助敏捷中迭代式开发,一次次与领域专家交流的深入,才能慢慢挖掘出隐藏在业务知识中的复杂性。

而DDD正是通过模型去捕获领域知识,对领域复杂性进行简化和提炼。

所以整合起来看,DDD是一种迭代改进试错法,还不知道什么时候该停止,甚至停止的时候你也不知道得到的到底是垃圾还是宝藏。

设计与开发绑定

这一点,是相当重要。是DDD与别的分析方法重要区别。

为什么DDD如此流行,可能跟很多技术大词一样,正如之前在中台文章所说,可能是应激者怕掉队,也可能是投机者想上位,很多人还是把它当成一种分析方法。

如果只是一种分析法,我们之前使用的ER分析,OOA,都已经够用了。

对于现流行说的DDD帮助微服务划分,那只是DDD中界限上下文这一个点,而且服务划分也不能只考虑领域界限,还有其它因素。

因此需要多思考,为什么需要DDD,DDD到底有什么独到之处?

我个人认为理由在Evans著作中提到的知识消化。学习者过多的精力放在了战术部分,什么是实体、领域服务、repository等等这些元素上。

在编程实践中,我们希望实实在在的用上DDD元素,才表示我们是在进行DDD,而其实没有DDD,对OO的深刻理解,充血模型是自然而然的。

然而,如果没有DDD战略部分,只有DDD战术元素,那只是DDD编码风格,而编码风格,没有高低之下,贫贱之分。

何为知识消化?我特此画张图:

业务方与技术方沟通业务需求,在交流中,对部分业务知识达到共识,这部分交流语言被标准化为统一语言;

在不断地交流中,对于双方达成共识部分,凝炼成到模型中,对模型review,以确定描述了业务实际情况,发现缺乏或不适当概念时,进行修正,指取新的统一语言;

当模型确认后,以模型为样本,使用代码实现它;

上面是正向流程,技术方在重构代码时,也会同步修改模型,进来提取出新的统一语言,反哺业务方。

通过正逆流程,达到修改统一语言就是修改模型,修改模型也就是修改代码;修改代码也是修改模型,修改模型也相继修改统一语言。

这样打破了知识壁垒,给予业务方与技术方一种更好的协同方式。这就是DDD的精髓。让分析模型与实现模型融合了,不再像以前的方法一样分裂。


研究结果

如上图所示,应用DDDP的相关活动主要分为4类:领域分析、领域设计、领域模型实现和普适性活动。

领域分析:与领域专家一起探索领域知识的过程。经过领域,将得到初始的领域模型;

领域设计:指将模型分成不同部分(每个部分对应着独立的限界上下文),然后扩展和细化每个限界上下文的过程,以此为开发实现做准备

领域模型实现:将模型转换为可运行代码,这一过程还通过检查模型为模型设计提供反馈

普适性活动:在应用DDDP时,可能在领域分析,领域设计,领域模型实现这3个阶段都会发生的模切活动,比如构建和更新通用语言

应用情况

在基础研究集合中,出现频次到达3次以上的DDDP,以及对应的描述和提及这些模式的研究文献

这些模式出现的频次并不平衡,战术设计模式被提及的频次明显高于战略设计,表明开发人员更容易注意到领域驱动设计的战术设计模式。

总体而言,目前只有31.1%的DDDP在基础研究中得到探讨,这也表明当前学术界对DDDP的研究存在不足。

应用DDDP的收益

下图展示了应用DDDP所带来的收益在领域分析、领域模型实现以及普适性活动中的体现情况

对每个阶段带来的收益细节详述一下

领域设计

应用DDDP收益在于使各个领域之间依赖关系更加明确,上下文映射和职责分层用于组织系统的不同部分:

前者表示不同限界上下文之间的关系,每个上下文表示一个特定的领域;

后者根据领域对象职责,将它们组织成具有清晰依赖关系的层次结构

帮助开发人员更加深入地了解系统,降低认知复杂性,有助于分析系统架构

领域模型实现

应用DDDP有助于领域模型的落地实现。

普适性活动

可以提升软件架构的质量属性,如可维护性、可扩展性、可重用性和可测试性等

应用DDDP的挑战

下图列出了在领域分析、领域设计、领域模型实现和普适性活动中,应用DDDP可能面临的挑战以及相应的缓解方法


感悟

可以看出论文对DDDP应用的收益和挑战阐述得很详细。在现实实践中,挑战远比论文中提到的还要多。我觉得主要是两方面:

一是对理论研究的不够深入,没有系统性学习

二是过于关注战术问题,不深入战略部分而不知所以然,又对OO的理解不深入使其难以落地,从而抱怨DDD

对于第一条

1、从个人学习者角度,只能自己多看看市面上已经出版的书籍,碎片化时间系统性学习,而不是随便看看几篇文章,就以为理解DDD了,现在DDD就像个筐,什么东西都在往里装,就算是Evans开山之作,也有很多时代局限性;

2、从团队角度讲,不要奢求团队每个人都去理解DDD,毕竟它的本身门槛就高,作为TL,需要自身内化DDD,制定规范,团队执行就可以,向有兴趣的同学传输背后理论。

对于第二条

3、除了需要系统性学习外,要明白DDD精髓与DDD编码风格是两码事,在实践DDD过程中,也带有个人经验特征,不仅要有自我理论作为背后支撑,也要认识到不是使用了战术元素,我们就是DDD了,而是要有DDD的知识消化过程。

总结

软件工程中是“没有银弹”的,任何理论方法都会存在一定的局限性和缺点,DDDP也是如此。因此应用各种DDDP带来的局限或者挑战,仍需要未来进一步探索和反思。

也希望我的感悟能帮助到你,如果有兴趣可以再看看论文原稿。对于高阶程序员来讲,困难的不是能不能干出来,而是怎么干才舒坦。

最近又看了几本关于架构的书籍,不禁回到原点:架构是什么?架构师职责是什么?

架构

《架构与架构师2》中引用了1995年David Garlan和Dewayne Perry给出的定义:

系统的组件结构,组件的相互关系,以及管控组件设计和长期演进的原则和指导方针

十几年前,软件架构师只处理架构中的纯技术问题,像上面定义中的组件,可能是类,是包。而现在架构师承担着大量、宽泛的责任,并且范围还在不断扩大。尤其云时代,IT基础设施包括网络、数据中心、计算基础设施、存储,以及其他子系统都得考虑

贴一张思维导图来说明软件架构涵盖的范围

从图中可以看出,架构师的职责包含技术能力、软技能、运营意识及其他很多方面

所以定义架构不是件轻松的事,Martin Fowler也都拒绝尝试对架构做出定义,退而引用名言:

“架构是那些重要的东西…………无论它具体是什么”
—- Ralph Johnson

所以在行业内共识:对软件架构本身并没有一个好的定义。

虽然架构很难定义,但我们总得尝试着描述它,分解它,进而更好地运用它指导软件开发。如果说软件世界更迭速度过快,组件化定义显得太陈旧,那我们需要一种与时俱进的思考软件架构的方式

Mark Richards与Neal Ford展示了这样的思考方式

如图中所示:软件架构包含系统的结构、系统必须支持的架构特征、架构决策以及设计原则

系统结构

实现该系统的一种或多种架构风格(比如微服务、分层和微内核)

仅仅描述结构并不能完整地诠释架构,还需要了解架构特征、架构决策和设计原则

架构特征

架构特征定义了系统的成功标准,这些标准往往与系统的功能正交

《架构与架构师》中,指出应用系统需要考虑两方面内容:一是功能性需求,二是非功能性需求。

但从语言角度来看,将一个东西命名为非功能会带来负面影响:如何说服团队充分注意“非功能性”的东西

另一个流行术语是质量属性,但它暗示的是事后质量评估而不是设计。

架构特征描述了对架构以及整个系统的成功至关重要的关注点,同时又不影响其重要性。

架构特征满足三个标准:

  1. 明确非领域设计的某个注意事项
  2. 影响设计的某些结构项
  3. 是否对应用的成功至关重要

构架决策

架构决策定义了一组关于如何构建系统的规则,构成了系统约束,并指导团队哪些可以做,哪些不可以做

比如在一个分层架构中,架构师可能会规定只有业务层和服务层可以访问数据库,限制表现层直接调用数据库。

设计原则

设计原则与架构决策的不同之处在于,设计原则是指导原则,而不是必须遵守的规则。

在微服务架构中,开发团队应该使用服务间的异步消息传递来提升性能。

架构定律

虽然架构范围已经大到难以置信,但统一元素仍然存在。

架构第一定律:

软件架构中的一切都是在做权衡

当架构师若认为自己发现了不需要做权衡的东西,很有可能他们只是还没有发现需要舍弃的东西而已

通过结合架构的原则、特征等,我们对软件架构的定义超越了软件结构脚手架。架构不仅仅是各种要素的组合,还体现了架构第二定律:

原因比方法更重要

架构师面对不了解的系统时,可以探明这个架构是如何工作的,但会很难解释某个选择背后的原因。

因此架构师需要去详细记录架构决策以及背后权衡的逻辑。

架构师

在之前的两篇文章中指出架构师必须要有屠龙刀还得有绣花针,需要技术+业务+管理三条腿。

总之一句话,架构师是最牛的人。可一个团队不能人人都是架构师,况且还有资深工程师,技术专家。作为架构师与他们的区别是什么呢?能力模型有什么不同呢?

决定一个人的强弱,是他的认知水平。对应技术人员就是对技术的认知。架构师的认知有四个阶段:

愚昧之巅(不知道自己不知道)、绝望之谷(知道自己不知道)、开悟之坡(知道自己知道)和持续平衡的高原(不知道自己知道),这也是架构师的认知逐步提升的过程。

如果人们对开发工程师的期望是把功能需求开发完成,那对架构师的期望就多样了

一、制定架构决策

架构师需要制定架构决策和设计原则,以指导团队、部门或者整个企业进行技术决策

二、持续分析架构

架构师需要持续分析架构和当前技术环境,然后给出改进建议。从整体上分析技术和问题域的变化,以确定架构的稳健性

三、掌握最新趋势

开发人员必须时刻关注技术更新,从而保证与这些技术与时俱进。对架构师来说,掌握最新的技术和行业趋势更为关键

四、确保决策被遵守

架构师需要确保架构决策和设计原则被遵守

五、丰富的经历和经验

架构师需要涉猎各种各样的技术、框架、平台和环境

六、具备业务领域知识

一个称职的软件架构师不仅要了解技术,还要了解问题背后的业务领域。没有业务领域知识,就无法理解业务的问题、目标和需求,也就不可能设计出有效的架构

七、具备人际交往能力

架构师需要具备出色的人际交往能力,其中包括团队合作、引导和领导力

八、了解并驾驭政治

架构师所做的几乎每个决策都会受到挑战。由于成本或工作量(时间)的增加,架构性决策将受到产品负责人、项目经理和业务利益相关者的挑战


针对以上八点,以及技术+业务+管理三项技术人普实能力,可以更简洁地概述架构师自身定制的三条腿:技能+影响力+领导力

1.技能是实践架构的基础。它需要知识以及应用知识的能力

2.影响力用来衡量架构师在项目中应用技能后给项目或公司带来多大的效益

3.领导力确保了架构实践的状态能稳步向前推进,同时培养更多的架构师

能力模型

论能力模型,与开发人员之间对技术方向的侧重有所不同。开发人员必须拥有很深的技术深度,但软件架构师必须具有非常广的技术广度才能像架构师般思考,并以架构的角度看待事物。

以知识金字塔展示世界上所有技术知识的类别,事实证明,技术人员应重视的信息类型随职业阶段的不同而不同

“已知”指代技术人员日常工作用到的技术、框架、编程语言和工具

“已知的未知”指代技术人员稍微了解或听说过,但没有掌握的技术

“未知的未知”是金字塔中面积最大的部分,指代能够完美解决技术人员面临的问题的技术、工具、框架和编程语言,但是技术人员甚至都不知道它们的存在

开发人员的早期职业生涯专注于扩展金字塔的顶端,积累经验和专业知识。金字塔顶端的知识需要投入时间才能保持专业。最终,金字塔顶的大小就是个人的技术深度。

而随着开发人员过渡到架构师角色,知识的性质也发生变化。架构师的价值很大一部分是广泛地理解技术,并且知道如何利用技术解决特定的问题。对架构师来说,最重要的部分是金字塔的顶部和中间部分

中间部分与底部交汇处的长度代表了架构师的技术广度

作为架构师,技术广度比技术深度更重要。因为架构师的职责就是根据功能做出与技术限制相匹配的决策,所以广泛了解了解各种解决方案是非常有价值的

架构师需要“博而不专”,牺牲技术深度来提高技术广度,虽然技术人都想在多个领域保持专业深度,但结果往往事与愿违,甚至无一成功。

对于能力模型为啥有区别,简单总结一下:

广度决定能找到的方法+深度决定选择方法的正确性+经验决定找到正确方法的速度

平衡编码

虽然架构师的能力模型与开发工程师有所不同,但还是需要保持动手编写代码,并保持一定水平的技术深度。

正好之前所说,不仅要有屠龙术,还要会瓷器活。

因此架构师面临的困难任务之一是如何在动手编码和软件架构之间取得平衡。

如果参与过多的编码工作,可能会陷入瓶颈陷阱。也就是当架构师在项目的关键部分(通常是基础框架代码)中拥有代码所有权并成为团队的瓶颈时,就会发生瓶颈陷阱。

避免瓶颈陷阱方法之一是将关键路径和框架代码委托给开发团队其他人员,然后着重于实现业务功能(一个服务),并且在1~3个迭代中完成。

如何保持编码能力和一定水平的技术深度呢?

一、频繁进行概念验证(POC),这种做法不仅要求架构师编写源代码,还要求架构师能够通过考虑细节来帮助验证架构决策

二、处理一些技术债或架构相关的故事问题,使开发团队腾出精力来处理关键的功能性的用户故事;或者在迭代中修复bug,能使架构师识别出存在于代码甚至架构中的问题和弱点

三、创建简单的命令行工具和分析器进行自动化来帮助开发团队完成日常任务

四、进行频繁的代码审查,通过代码审查能确保代码符合架构规则,并在团队中进行指导和辅导

总结

架构定义是件不容易的事,软件发展日异月新,架构的范围也日益扩大,进一步加据了定义架构的难度。但无论如何,我们还是有必要通过结构化思维去分析架构,进化古老的组件化定义,从架构结构、架构决策、架构特征以及设计原则四方面描述架构,继而明确架构师的职责,区别与开发工工程师的能力模型,加强“技能+影响力+领导力”三条腿能力成长,更好地服务架构。

康威定律随着微服务架构兴起的步伐慢慢复苏,重新进入人们的视线,但他的威力远远不仅限于简单的指导如何拆分微服务,不管是整个团队的战力,还是架构方案能否顺利落地都起着重要的作用。

康威定律

先回顾一下什么是康威定律:1968年,计算机系统研究院的梅尔康威在Datamation杂志上发表了一篇论文How Do Committees Invent?

这篇论文中有一句话被总结为康威定律:“设计系统的组织由于受到约束,这些设计往往是组织内部沟通结构的副本。”

下面先通过一次切身经历来阐述定律如何发挥威力,以及如何通过逆康威定律得到我们想要的架构方案

起初我带领一支团队负责一个业务,先称它为APP1,经过一段时间,老板找我谈话,说:“APP1在你的带领下,运行得不错,应该承担更大的责任,后面APP2团队也由你负责”。

经过一段时间的迭代,APP2需要一个配置服务,支撑差异化运营

APP2架构师根据最新业务需求,提出了给APP2增加一个配置服务,对于APP2来讲,架构师都无需赘述,此架构方案无疑是合理的

但从整体看APP1已经有配置服务

此时就有了两个方案:

  1. 按架构师规划,APP2构建新的配置服务
  2. 增强APP1的配置服务,让它同时支撑APP1和APP2

怎么决择呢?

从架构角度,方案一似乎更有优势,独立,两个APP之间也不会相互干扰,原先的配置服务也无需改动,相对去改造一个旧系统,构建新系统负担还小一些

但从组织结构讲,组织效能角度也更高效,大局出发,也不需要两个相似的配置服务,组织结构与架构结构也更有同态性

此时,康威定律就发挥了至关重要的作用:“如果系统的架构和组织结构不一致,那么组织结构将成为赢家”


当我在计划着进一步整合两个团队时,事情发生了变化,老板又找我谈话了,“经过这段时间,发现你带两个团队有些吃力,这样吧,以后你就只负责APP2团队”

随着业务的继续开展,发现了个问题,当APP2团队需求需要变更配置服务时,为难了

APP1使用配置服务深度和广度都高于APP2,所以在划分时,配置服务归于APP1了,之前都是同一个大团队,资源协调很简单,内部沟通很容易

此时怎么办?

原先团队内部的沟通,需要跨团队沟通了,再简单的一次变更,都需要提前沟通,协调排期,制约了高效迭代交付能力

所以APP2团队不得不剥离APP1的配置服务,另起炉灶,回到当初架构师的方案一

这其实还是康威定律发挥着威力:组织内部的沟通路径(无论是否和正式汇报线一致)严重制约了组织所能设计的解决方案的类型

逆康威定律

这是ThoughtWorks技术总监James Lewis突发奇想地提出的,组织要根据他们想得到的软件架构来设计团队结构,而不是期望团队盲从一个被设计好的团队结构

其核心观点在于,将软件架构看作一个独立的概念,认为它可以被孤立设计,然后交由任何团队来实现,这从根本上就是错误的

我们把上面的示例,顺序倒置过来,就是逆康威定律。

我详细阐述下:

刚开始,APP1和APP2是两个独立完整的团队,都有各自的配置服务,也就是

虽然他们功能相似,但由于在两个团队里面,与组织结构和沟通路径都是匹配的

从公司全局架构看,发现配置服务只需要一个就够了,推倒烟囱式架构,整合资源,配置服务中台化,这也是近些年各公司崇拜的中台文化

怎么办呢?简单啊,提取共性,抽象整合呗。

现实没那么轻松,如果两大APP团队,是支撑公司盈利的两大业务,营收压力也很大,基本上整合是句空话,看看有多少公司的BU都是各自为战,烟囱式系统一个比一个强悍,谁能动得了?

此时怎么办?整合组织结构,让两个团队组合成更大的团队,拥有独立的架构团队,团队内部自己就会催生出整合的力量

再看一个示例,假设有四个独立团队,每个都由前后端开发工程师组成,他们分别负责系统的不同部分,然后对DBA提出数据库变更请求。

如果这四支团队独立的前端和后端开工程师推动了UI和应用层的前后端分离,而有一个共享的DBA团队,那么很可能会带来这样一个单一的共享数据库。

因为康威定律的同态力会强烈地牵引软件架构“自然而然”地体现出当前的组织设计和沟通路径。

当我们在使用微服务架构时,每个独立服务都需要有属于自己的数据存储。

通过应用逆康威定律,可以在各个独立的客户端应用和API开发团队里面增加一名数据库开发人员,那架构结构自然就体现出来了。

总结

想想架构风格千千万万,为什么分层架构却是最流行的,也是最容易实践成功的,因为有独立的前端团队,后端团队,基础服务团队,对于业务数据流向,正好也是从UI发起,逻辑层处理,数据库存储,组织结构与架构结构是匹配的。这就是康威定律的威力。

组织结构和团队间真实的沟通路径会直接影响所构建系统的架构。如果有四个小组合作开发一个编译器,那么你将得到一款具有四个步骤的编辑器。对于一家软件产品公司来说,组织结构预示着产品架构。

过去很多组织结构调整的潜在目标都是为了减少员工或者围绕管理者和领导者的权势建立山头。可对于一家软件公司,势必慎重,必须要考虑架构,更可以应用逆康威定律:设计团队满足理想的软件架构

简而言之,在设计软件架构或进行组织结构调整时,将康威定律纳入考虑因素之中,就能够受益于兼顾软件架构和团队设计的同态力。

Eric在DDD第一章节就介绍了模型,可见模型的作用不言而喻,说DDD是一种模型驱动设计方法,绝对没有问题

那是不是我们在拿到业务需求时,就急呼呼的跟业务方来一起构造模型呢?毕竟模型是万事之首嘛

《DDD开篇》提过DDD是一种基于面向对象的设计方法,我们既然已经有了面向对象,而且OOAD也很强大,为什么还需要DDD呢?

要想弄清楚这两个问题,首先我们需要拿个示例来仔细比对一下

OOP小示例

《面向对象是什么》一文中提到的游戏小示例

有个游戏,基本规则就是玩家装备武器去攻击怪物

  • 玩家(Player)可以是战士(Fighter)、法师(Mage)、龙骑(Dragoon)
  • 怪物(Monster)可以是兽人(Orc)、精灵(Elf)、龙(Dragon),怪物有血量
  • 武器(Weapon)可以是剑(Sword)、法杖(Staff),武器有攻击力
  • 玩家可以装备一个武器,武器攻击可以是物理类型(0),火(1),冰(2)等,武器类型决定伤害类型

作为一名受过OO熏陶的程序员,借助OO的继承特性把类结构设计成:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Player {
Weapon weapon
}
public class Fighter extends Player {}
public class Mage extends Player {}
public class Dragoon extends Player {}

public abstract class Weapon {
int damage;
int damageType; // 0 - physical, 1 - fire, 2 - ice etc.
}
public Sword extends Weapon {}
public Staff extends Weapon {}

攻击规则如下:

  • 兽人对物理攻击伤害减半
  • 精灵对魔法攻击伤害减半
  • 龙对物理和魔法攻击免疫,除非玩家是龙骑,则伤害加倍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Player {
public void attack(Monster monster) {
monster.receiveDamageBy(weapon, this);
}
}

public class Monster {
public void receiveDamageBy(Weapon weapon, Player player) {
this.health -= weapon.getDamage(); // 基础规则
}
}

public class Orc extends Monster {
@Override
public void receiveDamageBy(Weapon weapon, Player player) {
if (weapon.getDamageType() == 0) {
this.setHealth(this.getHealth() - weapon.getDamage() / 2); // Orc的物理防御规则
} else {
super.receiveDamageBy(weapon, player);
}
}
}

public class Dragon extends Monster {
@Override
public void receiveDamageBy(Weapon weapon, Player player) {
if (player instanceof Dragoon) {
this.setHealth(this.getHealth() - weapon.getDamage() * 2); // 龙骑伤害规则
}
// else no damage, 龙免疫力规则
}
}

如果此时,要增加一个武器类型:狙击枪,能够无视一切防御,此时需要修改

  1. Weapon,扩展狙击枪Gun
  2. Player和所有子类(是否能装备某个武器)
  3. Monster和所有子类(伤害计算逻辑)

除了伤害逻辑有各种规则,还有装备武器也会有各种规则

比如,战士只能装备剑,法师只能装备法杖,但他们都可以装备匕首

再比如,当我们有不同的对象,但又有相同或类似的行为时,OOP会不可避免的导致代码的重复

在这个例子里,如果我们去增加一个“可移动”的行为,需要在Player和Monster类中都增加类似的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Player {
int x;
int y;
void move(int targetX, int targetY) {
// logic
}
}

public abstract class Monster {
int x;
int y;
void move(int targetX, int targetY) {
// logic
}
}

一个可能的解法是有个通用的父类:

1
2
3
4
5
6
7
8
9
10
public abstract class Movable {
int x;
int y;
void move(int targetX, int targetY) {
// logic
}
}

public abstract class Player extends Movable;
public abstract class Monster extends Movable;

但如果再增加一个跳跃能力Jumpable呢?一个跑步能力Runnable呢?如果Player可以Move和Jump,Monster可以Move和Run,怎么处理继承关系?要知道Java(以及绝大部分语言)是不支持多父类继承的,所以只能通过重复代码来实现


原生OOP力不从心

从OO角度看待,逻辑简单,代码也算过得去,也基本符合充血模型需要的数据与行为结合性要求

但如果业务比较复杂,未来会有大量的业务规则变更时,简单的OOP代码会在后期变成复杂的一团浆糊,逻辑分散在各地,缺少全局视角,各种规则的叠加会触发bug。

在这个小示例中,可以看到新增加一次规则几乎重写很多类,改造成本相当高,这还写得不够OO吗?

总体而言,上面的代码没有处理好这三个问题:

  • 业务规则的归属到底是对象的“行为”还是独立的”规则对象“?
  • 业务规则之间的关系如何处理?
  • 通用“行为”应该如何复用和维护?

DDD应对

示例和单纯使用面向对象的问题已经很明晰了,DDD如何应对呢?

当然,可以申辩

虽然示例代码已经很OO,但却没有遵守OO原则SOLID,至少没有达到OCP目标

尤其开始就掉进OOP的陷阱,使用继承来实现看似是继承关系的逻辑,没有遵循组合优先于继承的原则

尤其没有提取出业务规则,并理清业务规则的归属,不应该与实体对象混合

建模

示例本身很简单,如果我们建模,大概是这样:

但很怪,模型则偏重于数据角度,描述了在不同业务维度下,数据将会如何改变,以及如何支撑对应的计算与统计,也就是说模型上看,会有实体以及实体间的关系,隐藏了业务维度,可以我们这个模型上却包含了动词,来描述业务行为

当然这个模型可以再充实一下,比如把业务规则标识上去,这也说明了传统模型的缺点,如果你对其他模型感兴趣,请关注我,后期会详情介绍模型系列文章

我们回到有问题的本质原点,为什么要建模呢,为了抽象复杂业务逻辑,降低理解业务的成本,挖掘更多的业务隐藏知识

可上面的示例太清楚了,一览无余。一句话可以概述出整个业务需求:

玩家使用武器攻击怪物,对怪物造成伤害,直至怪物死亡

把规则加进去:

玩家按规则使用武器按规则攻击怪物,对怪物、玩家、武器造成一定规则的影响(怪物受到伤害,玩家可能会有反弹伤害,武器持久属性会下降直到武器消失),直至怪物死亡

这其实是任何一款ARGP游戏的核心业务

软件开发的核心难度在于处理隐藏在业务知识中的复杂度,模型就是对这种复杂度的简化与精练,DDD改进版还使用事件风暴方式挖掘业务知识,而像这种业务知识没有隐藏的简明型业务系统,我们已经把核心问题描述得很清楚,无需再去知识消化,事件风暴,为了DDD而DDD,所以建模价值不高,甚至毫无必要

DDD应对

在上面的申辩中,我们已经发现了并不是OO不行,而是使用OO方式不对,虽说要把OO原则深入骨髓,可有没有一种方法能直接上升一层次,就像我们在使用面向过程语言时,也要有面向对象思维,实践没那么容易,直接使用面向对象语言,会让我们更容易使用面向对象思维,领略OO精髓

DDD正好就是这样一种方法,基于OO的升华,主要看看领域层的规范

实体,充血的实体

这一点与原生OO一样,数据与行为相结合

1
2
3
4
5
6
7
8
9
public class Player {
private String name;
private long health;
private WeaponId weaponId;

public void equip(Weapon weapon) {
// ...
}
}
  • 任何实体的行为只能直接影响到本实体(和其子实体)
  • 因为 Weapon 是实体类,但是Weapon能独立存在,Player不是聚合根,所以Player只能保存WeaponId,而不能直接指向Weapon
  • 实体需要依赖其他服务时,也不能直接依赖,使用Double Dispatch
1
2
3
4
5
6
7
8
9
10
public class Player {

public void equip(Weapon weapon, EquipmentService equipmentService) {
if (equipmentService.canEquip(this, weapon)) {
this.weaponId = weapon.getId();
} else {
throw new IllegalArgumentException("Cannot Equip: " + weapon);
}
}
}

领域服务(Domain Service)

单对象

这种领域对象主要面向的是单个实体对象的变更,但涉及到多个领域对象或外部依赖的一些规则

跨对象领域服务

当一个行为会直接修改多个实体时,不能再通过单一实体的方法作处理,而必须直接使用领域服务的方法来做操作。

在这里,领域服务更多的起到了跨对象事务的作用,确保多个实体的变更之间是有一致性的

不能学习实体的Double Dispatch

1
2
3
4
5
public class Player {
void attack(Monster, CombatService) {
CombatService.performAttack(this, Monster); // ❌,不要这么写,会导致副作用
}
}

这个原则也映射了“任何实体的行为只能直接影响到本实体(和其子实体)”的原则,即Player.attack会直接影响到Monster,但这个调用Monster又没有感知

通用组件型

像Movalbe、Runnable通用能力,提供组件化的行为,但本身又不直接绑死在一种实体类上

策略对象(Domain Policy)

Policy或者Strategy设计模式是一个通用的设计模式,但是在DDD架构中会经常出现,其核心就是封装领域规则。

一个Policy是一个无状态的单例对象,通常需要至少2个方法:canApply 和 一个业务方法。其中,canApply方法用来判断一个Policy是否适用于当前的上下文,如果适用则调用方会去触发业务方法。通常,为了降低一个Policy的可测试性和复杂度,Policy不应该直接操作对象,而是通过返回计算后的值,在Domain Service里对对象进行操作。

总结

DDD是一种模型驱动设计方法,但使用DDD也并不是一定要按固定方式方法一步步执行,建模是为了对复杂问题的简化和精炼,挖掘隐藏的业务知识。

如果能通过简明方式就能把业务核心问题描述清楚,比其他一切手段都有用,也都重要。那我们就没必要再去为了DDD而DDD,去进行事件风暴,知识消化慢迭代方式

本文中虽然提取了一些DDD领域层规范直接升华OO,但你有没有注意到一个问题,Player如果拥有很多能力,比如Moveable,Runnable,Jumpable,Fireable,那这个实体如何实现?

首先我们肯定会面向接口编程,提取出interface Moveable,interface Runnable,interface Jumpable,interface Fireable,可Player呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Player implements Moveable,Jumpable,Fireable {

void move(int targetX, int targetY) {
// logic
}

void jump() {
// logic
}

void fire() {
// logic
}

}

可以想象,随着能力越来越强大,player类会越来越臃肿,发展成超大类,充满坏味道,可我们这次也没有违反什么原则?难道达到了原生面向对象的能力极限?

如果你有好的想法,欢迎留言交流。如果你觉得文章有帮助,多转发,点击右下角『看一看』

前一段时间,脑子短路了,出现点思维障碍,突然特别想推广一下公众号,增长一下粉丝数量。

所以呢,就去各大技术网站,同步一些文章,想引流到公众号,现在写作技术网站真不少,也可能因为各个能写的技术人都要打造自己的个人品牌,从这些大型平台上搬迁到个人网站了,造成了一些流量流失,所以现在这些平台也在大力推广,吸引作者回归平台,比如搞更文活动,有赏写文,用户体检也上去了,可以很方便地从别的平台把文章搬迁过来

时间巧得很,7月底去看了各大平台,发现掘金在8月搞更文活动,连续更文多少天,就有相应的奖品。虽然奖品不是很值钱,但我借此机会,把文章搬过去,即能引些流量,又能得到奖品,两全其美。要得到最大奖项的要求是连续31天,心里想,这应该很难有多少人做到,每天写一篇,真是神人,除非像我这种人,已经有一大堆文章,只是手动搬迁一下

虽然就每天机械地选篇文章复制过去,但坚持了一周,还是有些烦,一是每天都有这么一件事在心里挂着,二是越来越发现意义不大,不应该把时间浪费在此,哪怕只有2分钟。 同时也发现能挑选的系列文章不多,好文章也不多,几年的积累,战不过一个月的考验,悲哉

一整月,终于过去了,平台发布了中奖名单,我根据人数,做了个统计

横轴是更文天数,纵轴是人数

个人感觉应该是金字塔结构,结果是头尾两波人数量差不多,中间人数反而比较少,这有些违背常识,难道跟我类似的人很多?

连续更文31天的人,如此之多,单单是为了这些奖品,我是不太相信,更多的应该是内驱力的驱动。莫道君行早,更有早行人。这个世界勤奋的人远超出想象,这估计也是为什么各行各业都充斥着内卷的气息

这也说明在当下时代,相对过去想要成功,不仅需要付出更多的勤奋,还需要有智慧的勤奋,也就是战术勤奋,战略更不能不努力。需要更加剖析自我,找出个人特色,有的放矢。

努力决定下限,运气决定上限;除了单单个人努力,在这样一个时代,还需要其他因素,而且因他因素的占比会越来越重,也越来越多元。

在此环境下,我们首先需要提高自己的底层认知水平

最近听好几位大佬提到一本书《跃迁》,摘抄几句:

获得百倍收益的关键,并不是百倍努力。每个时代的高手都在利用社会和科技的底层逻辑撬动自己,实现跨越式的成长。

长江商学院的校训是“取势、明道、优术”,个人方法论被放到了第三位,更重要的是把握趋势(取势)、理解系统运行之道(明道)

没有一个人是仅凭努力、天赋、机遇而获得巨大成功的,跃迁式成功都是利用了更底层规律,激发了个体的跨越式成长。

暂时还在消化中,有些认识值得多看几遍,希望你也有所收获

中台,在过去两年是个流量顶级词汇,谈什么都得带上中台,干什么都得扯点中台,不说中台,那绝对不是个合格的技术人

但经过了谈中台、建中台、拆中台,潮起潮落,不管是看好,还是贬低;可以看出在技术大词的浪潮里,不管是应激者怕掉队,还是投机者想上位,真正懂它的人不多,或者大多都还停留在以以往经验来判定的新事物

在历史遗忘之际,我来重温一下它

起源

在中台的发展进程中,首先得回到它的起源,至少有两个版本:

一是正史,马云一行,参观了芬兰一家游戏公司supercell,大受震撼,回来就提出了“中台战略”

一是野史,张勇的挟中台以令诸侯

从这两史中至少可以看出一些东西:

  1. 中台是由企业掌舵者提出以及使用的,它不是一个技术人员提出的,甚至说是CTO级别提出的,提出的起源与技术占不上边
  2. 中台的战略地位,不管是愿景实现还是战略落地,都发挥着巨大作用

所以很多技术人不解的地方,为什么谈到中台,需要谈企业战略,需要企业级组织变更,而不仅仅在于研发内部,不是使用的技术多牛,而是因为中台本身就有战略属性

因此在讨论中台时,需要从业务环境,组织结构,人力构成和技术架构各方面统筹考虑,抛开这些单从技术角度,是没有全局视角衡量中台的优劣,也是没有意义的,所以中台得结合企业本身综合情况,而不是技术迁移就能完成的

定义

现在提到中台,一连串的词汇会涌现出现,比如共享、复用、积木化

那么什么才叫中台呢?至少指出者没有给出定义,但它有特性,就是在高速发展环境之下,企业需要具备相应的响应速度去支撑企业运营的需求,而依靠小而灵活的前台团队,频繁而低成本的试错是一种应对此商业环境具有竞争力的模式

“小而灵活”是关键:小意味着人员少,成本低;灵活意味着对外快速响应市场,对内流程敏捷,快速失败

而能支撑这种小而灵活前台团队的系统就称为中台,这是从中台的作用来描述它,经过这几年的发展,有各式各样的定义

我比较认同王健老师的定义:企业级能力复用平台

企业级:

定义了中台的范围。它不是单业务级,是从企业全局出发,考虑多条业务线;一个企业也不是只有一个中台,可以有多个中台。也就是企业与中台的关系是多对多的

企业级这表明了中台不单是技术问题,而是上升到企业架构的问题

能力:

定义了中台主要承载的对象。能力的抽象解释了为什么有那么多种类的中台,也解释了为什么每家中台都是不一样的,因为每家企业的核心能力是不一样的

复用:

定义了中台的核心价值,建设中台的过程就是推倒烟囱系统的过程,也是去重复用的过程;“去重”讲的更多是向后看,是技术驱动;“复用”讲的更多是向前看,是业务驱动和用户驱动的

中台需要从“去重”到“复用”的视角转变

“复用”是中台更加关注的目标

“可复用性”和“易复用性”是衡量中台建设好坏的重要指标

“业务响应力”和“业务满意度”是考核中台建设进度的重要标准

平台:

定义了中台的主要形式。区别于传统的应用系统拼凑的方式,通过对于更细粒度能力的识别与平台化沉淀,实现企业能力的柔性复用,更好地支撑前台业务

种类

自从中台概念流行,各个词都与中台组词了,研发中台、技术中台、组织中台、业务中台…只要把以前谈的词语带上中台,就是高大上的

经过过去几年的喧嚣,沉寂。人们对中台品种达成了一定的共识:业务数据双中台

网易副总裁汪源曾在网易云创峰会上提到:“所有中台都是业务中台”。从中台起源出发,的确,中台就是为业务,为企业更好地以更低成本、更高质量、更快响应速度售出产品、换取利润服务的

而数据中台,更多的是大数据时代到来,大势所趋,业务中台是产生数据,数据中台是做数据二次加工,并将结果再服务于业务,为业务进行数据和智能的赋能

创新

这两年,拆中台的声音呼啸而起。尤其以当年带起中台的阿里等一系列巨头,都在拆。人们又开始跟风中台不行了

戏称,我们作业才抄到一半,你说写错了

为什么要拆呢?想那盒马不就是中台成功的典范,但在犀牛制造却提出不拿中台一针一线了,自己从零开始

这其实就是任何软件平台的特性

平台的能力越丰富,上层应用可以利用的越多,去完成某类功能的成本就越低,因而平台能力通常被看作效能下限

应用利用平台能力获得效能,是通过放弃一部分自主性获取,而低自主性就影响创新的可能,所以应用自主度被看作创新上限

“效能下限”与“创新上限”就像翘翘板,产生了哑铃效应,而中台则是追求效能的极致,同时却也降低了创新上限

对于像巨头在中台已经沉淀多年,有了相当应对当前市场的能力,但想要争取更多的市场份额,创新需求日益剧增,尤其需要颠覆式创新

因此,别人拆的时候,你能拆吗?建中台需要综合考虑,拆中台同样需要考虑

总结

中台曾经的顶级流量热词,不管当初的是应激怕掉队,还是投机想上位,浪潮退去之时,我们才能静下心来思考它是什么,它能干什么

虽然现在已经冷却,但威力不减,提升企业竞争力一把好手,它的出发点不是技术基建,而是寻找更好的组织结构和技术架构,以支持业务的快速增长和发展

最近学到一个词“耦合创伤应激障碍”,讲的是程序员对耦合条件反射式恐惧,对于这个新词,我再重新理解一篇

对于一名程序员,从入行开始,就听到前辈们对“高内聚低耦合”的谆谆教诲,所以对于低耦合的意识深入骨髓。知行合一,看看是怎么践行的,打开任何一个项目工程,可以看到,每一个service都有一个interface和impl,代码看起来整整齐齐,所有变化点都考虑到了,但其实没有降低问题复杂度,只是自己看着舒服

《SOLID总结》中提到过面向接口编程中接口到底是什么含义,并不是所有实现类都得需要一个接口,才是面向接口编程

而现在实践中对实现依赖心理恐惧,成了一种行业通病,见不得对实现的依赖,这是典型的耦合创伤应激障碍,像“猴子实验”

五只猴子被关进笼子里,笼子一角挂着一串香蕉,如果有猴子试图摘取香蕉,就会被开水泼到。猴子们吃了几次苦头之后,就再也不想摘香蕉了。
此时用一只新猴子替换老猴子,新猴子看到有香蕉刚想去摘,就被老猴子们拉住一顿暴打。新猴子挨了几次打之后,也不再去摘香蕉了。
此时再换进一只新猴子,它也看到香蕉想去摘,也被老猴子们一顿暴打,下手最狠的恰恰是那一只没被开水烫到过的。
最后老猴子们都被换干净了,仍然没有猴子去碰那串香蕉,因为它们知道——碰香蕉意味着被打,而为什么会被打,没有猴子知道。

当参与一个新项目,不再创建interface时,肯定会变成那只被打的“猴子”

然而现实并不是这样的,真的加个interface就减少耦合了吗?耦合少得了吗?比如,需要使用支付宝或微信支付,那么这就是业务需求,与支付宝和微信就必然会耦合,才能达到业务要求。不管怎么组织代码结构,是明显直接调用,还是隐晦地抛出支付事件,终将需要调用支付宝微信的支付接口;再比如现在很多应用需要推送消息,短信、邮件亦或微信,那么与支付类似,不管如何,必将调用第三方接口才能实现这些功能,这也就是耦合的必然性

代码大致是这样的:

先来一个接口:

1
2
3
4
5
6
7
package com.zhuxingsheng.infrastructure.port

public interface AlipayService {

public PayResult pay(AliInfo aliInfo,decimal payAmount);
}

再来对接口的具体实现,调用支付宝SDK

1
2
3
4
5
6
7
8
package com.zhuxingsheng.infrastructure.apapter
public class AlipayServiceImpl implements AlipayService {

public PayResult pay(AliInfo aliInfo,decimal payAmount) {
AlipaySdk.pay();
return result;
}
}

微信支付代码结构类似,对于这些代码味道是不是相当熟悉,有service必有interface和impl,但看看接口的意义在哪儿?

机智的你,肯定发现这样不对,我们需要的应该是在线支付能力,而支付宝支付或微信支付只是具体的实现而已,也就是与支付宝和微信耦合其实不是必然的,必然的是在线支付能力

这儿其实有两种演变过程:

第一种:先实现了支付宝支付功能,当再实现微信支付时,此时发现要抽象出在线支付接口,策略模式

第二种:从业务需求用户故事:作为用户需要完成订单线上支付,完成订单的全流程

第一种从技术入手,而第二种从最原始的业务入手,这两种演变虽然第二种方是大道,可技术人却喜欢第一种,这也就回到篇首所说,代码看起来整整齐齐,却没有简化问题,甚至只因为技术风格的强统一,带来了业务语义的更加隐晦

技术其实是解决业务的工具,需要从业务本源着手,挖掘业务背后隐藏的业务能力,构建出对应的技术模型,达到模型即代码的统一,也就知道了必然性耦合,怎么降低耦合

所以再看上面的接口,package com.zhuxingsheng.infrastructure.port,是在基础设施层,只是技术上单纯的一个接口,在《DDD》专栏中,提到过的接口在领域层,实现在基础设施层,因为maven模块循环依赖或者DIP的需要,所以必需要把接口放在领域层,从业务角度分析,线上支付能力是构建模型的必要能力,是领域模型必不可少的部分,需要在线支付能力,也是业务必然性耦合的体现,应该在领域层

可从代码形式上看,你是不是觉得不管是对业务的深刻理解,还是单纯技术抽象,不都是一个接口吗?无非是叫接口还是叫业务能力而已吗?

再看一个接口

1
2
3
4
5
6
package com.zhuxingsheng.infrastructure.port

public interface UserService {

public User getUser(long userId);
}

从命名就知道,获取用户信息,不管是业务系统自身处理用户信息,还是从用户中心式外部服务获取用户信息,这是整个系统的基本能力,而不会再需要去抽象一个深奥的业务能力,当业务需求故事阐述用户下订单业务行为时,业务方已然抽象了整个用户体系,只有研究用户上下文子域问题时,才去深入用户领域模型,但从当前业务上下文看,业务系统与这些基础服务是深度绑定,并在系统架构初始已经确定了整体架构

因此在业务系统中去定义一个userservice接口,是没啥意义的,除非系统大动,否则是不会变动的

延伸一下,此时领域层如何依赖基础设施层呢?怎么DIP呢?有没有丝毫感受到分层有问题呢。对此下会分解

总结一下,我们需要正确看待耦合必然性,不是从技术实现的角度去硬生抽象,而需要从业务角度,挖掘出业务真正耦合的能力,坦然接受这样的耦合,清晰化表达业务语义

资本家主要目标是赚钱、赚很多很多的钱;他们给提出的要求是降本增效

那么作为架构师,目标是什么呢?

在《整洁架构》书中作者写到架构的主要目的是支持系统的生命周期。良好的架构使系统易于理解,易于开发,易于维护和易于部署。
最终目标是最小化系统的寿命成本并最大化程序员的生产力

大多数程序员心里觉得应该是展示最牛B的技术才对,可现实却只是资本家的工具而已,是不是有些惊讶

软件的核心是它为用户解决领域相关问题的能力,保持业务价值的持续交付

可在软件行业,交付能力的持续性是相当有挑战性的,也许前期交付很快,但慢慢交付就很慢,质量也会下降,甚至哪怕一次小小的改动都要经历很久,更可怕的是无法交付,为什么呢?

在之前的相关文章中也提过,有两张图:

《架构师》中提到软件需求并不只是功能需求:

软件复杂度并不仅仅是业务复杂度:

在一起起看似快速交付背后,不合理的设计或者实现积累了过多的技术债,造成无法交付

所以架构师最重要的事就是解决软件中的复杂性

在软件项目中,任何方法论如果最终不能落在“减少代码复杂度”,都是有待商榷的

软件架构设计的实质,是让系统能够更快地响应外界业务变化,并且使得系统能够持续演进

架构设计的主要目的是为了解决软件复杂度带来的问题

《DDD应对复杂》中也提到复杂的来源,对于软件复杂性以及应对方案,特定总结画了一幅图

在之前《应对变化》中提到模块之间合的策略:缩小依赖范围,API是两个模块间唯一的联结点

怎么才是一个好的API设计呢?最近项目中正好碰到一件关于一个API引起的相爱相恨的事件

数据来源于外部系统,外部系统通过回调把数据传输过来,内部系统通过系统A进行接受,接受完之后,转发给系统B

接受回调api大概是:

1
systemA.callback(long userId,Object data);

整体两个参数,一个userId表示这个数据是谁的、一个data表示数据本身

对于系统B来讲,他的业务要求,这数据必须要知道用户名字,必须在数据上打标签,所以跟系统A商量,要不你把username也随便转给我吧

系统A一想,那也是两秒的事,因为本身在接受数据时也得对userId校验,取个username也不麻烦,不废话了,你要就给你

因此系统B接受数据api设计成:

1
systemB.receive(long userId,String username,Object data);

一切都是行云流水,大家都很happy,如期发布上线

爱情总是在转角处遇到,上线完,QA同学一顿操作,却发现系统B没有如期显示出数据,系统B觉得是系统A没传来数据;咋办呢?心虚的系统A只能查查日志

1
log:systemB return error,username is empty 

原来原来是因为这个用户的username是空,系统B拒绝接受了,怎么username会为空呢?username怎么能为空呢?

找到用户系统,用户系统解释了,一个用户在注册时并不一定有username,有username,email,usercode三个值中的任何一个值就可以了

这时该怎么办呢?相爱相杀时刻到了

系统B:要不你把这三个值都传给我?

系统A:我还得再改下代码,测试后发版本,要不你自己从用户系统取吧

系统B:传一个可以,怎么三个就不可以了,不都一样吗?

系统A:太麻烦了,你自己取了,想怎么控制就怎么控制

系统B:你是不爱我了

系统A:你怎么就不理解我呢


温习一下一个好的API设计要求:

缩小依赖范围,就是要精简API;API要稳定得站在需求角度,而不是how角度

  1. API包含尽可能小的知识。因为任何一项知识的变化都会导致双方变化
  2. API也要高内聚,不应强迫API的客户依赖不需要的东西
  3. 站在what角度;而不是how,怎么技术实现的角度

上面示例的问题就在系统B接受数据api:

1
systemB.receive(long userId,String username,Object data);

关照上面的要求:

问题一:API中包含的知识有重复:userid,username

问题二:客户端也就是systemA并不需要username,但被强迫要知晓并要按规则赋值

问题三:站在设术实现角度,api中增加参数username,而不是需求角度

总结

示例虽小,日常工作中常常碰到这类问题,如果这个例子上线成功,每个人都觉得这是一次成功的交付,但回头复盘,发现了很多理论缺乏,惯性思维使然造成的不合理,难维护,难扩展的设计

由此看出,日常的CRUD并不是没有技术含量,而是我们有没有深刻认知