从架构可视化入门到抽象坏味道

抽象的坏味道

上文说过,C4说穿了就是几个东西:关系-线、元素-方块和角色(角色不过是图形不同的方块)、关系表述-线上的文字、元素的描述-方块里的文字,虚线框(如前文所说,在C4里面虚线框的表达力被极大的限制了)。

这些东西一点都不新,我们自己随便找个白板,无非也是用这几个东西来表达架构,它的优点在于引进了一些分层,帮助我们理清思路、也有利于可视化给别人看。

换言之,C4不能帮你做好架构设计,但是它能暴露出你设计中的问题,以便于被自己或其他人纠正。

可视化的威力就在这里,但根据我的经验,即便你用上了C4也不见得就能表达清楚,不过好消息是,我们终于可以聊一些高级的表达问题了。

可视化之后,我们能看到自己的表达问题,大概的问题有两个:抽象层次和抽象粒度。这个是表达方面永恒的问题,也就是软件设计永恒的问题,没有万灵丹,但是用上了可视化手段之后还是有机会让生活更美好一点的。

这两个问题可能太抽象了,不容易意识到,那我们可以看图,从图上的具体表现来发现坏味道。一般会有几个迹象表明我们有可视化的坏味道:

  1. 一张图上过分密密麻麻的线
  2. 一张图上太过多元素(也就是方块)
  3. 一张图上太少的元素,比如角色特别少
  4. 每个图上文字表达不契合,有的太泛泛,有的太细节
  5. 无限制的画更多张图,基本上也就失去了使用图形化表达的意义

那么对应的手段就有:

合成更大的元素

当我们意识到有密密麻麻的线、太多的元素,闻到这个味道的时候,可以考虑是不是该把里面的一些元素合成更大的元素了。Component可以合成Container,Container可以合成System,这样就会分成更多的图,每张图就变得没那么多线和元素了。

紧接着会面临下一个问题:怎么合成一个更大的系统,Container是明确的,所以Component合成Container不是问题,问题是Container怎么合成一个系统,为什么是这些Container合成这个系统,而不是另外几个?或者多加几个、减几个?

这个问题没有标准答案,但是有一些其他的框架可以提供一些思考的维度。

比如可以结合akf扩展立方来思考。

(akf扩展立方)

X轴就比较容易,一方面从容器本身的描述来看设计上是不是支持横向复制的,另一方面则是看部署图。

Z轴相对难一些,只是比较偏技术。比如当技术上有性能瓶颈,则需要注意这一个维度,有时不得不搞出一些特殊的容器出来,有时已经存在这些容器了,他们可能单独属于一个系统(类似于大数据分析的系统),或者一个系统的某一个局部(这就是前面提到的虚线框表达力被限制的地方)。

Y轴给人的感觉是最容易操作的,但实际上却是最难做好的,Y轴的背后是业务,往往我们觉得就按业务切成多张图就好了。这种想法就表现出我们往往意识不到业务的真正难度,于是总在这里出问题。如果你能跨过这个心理障碍,决定去认真做一下,那么也有一些工具可以帮助我们做好。

(领域模型与架构设计)

最经典的工具组合就是求助于DDD,结合康威定律和步速,考虑维护的团队、使用的角色、变化的节奏,这块展开就复杂了,有机会再聊。

这里说一个最简单的做法:按照用户角色分。同一种角色,由于其在公司里的职能、职责都是已经被定好的,天然在系统上就有一种隔离性。比如招聘专员、会计、出纳,他们使用的系统肯定是不一样。

但说简单,其实也不简单。我见过一些图,上面的角色只有两个,内部用户和外部用户。而另一些图,细化到了个人的级别,或者把职级都放上去了。所以无论再简单的原则,最后都会掉进抽象的坑。

画一些共识图来忽略掉一些通用的元素

有时候合成了更大的元素,元素依然很多,线条依然很密。画多张图也不够切分的。这个时候我们可以求助于共识。

人与人交流,如果已经有一些共识存在就可以少废很多话,共识多到一定程度只需要确认一个眼神就完成交流了。所以毫无疑问,做好共识管理,可以大幅简化我们的架构图。

所以在我们做架构可视化的时候,经常会先画一个技术共识图,比如以一个能力建设的数字平台为例,我们就画了一个下面这样的技术共识图:

(技术共识图)

在后面画具体的图时,就可以省略掉一些共识的元素,像nginx和数据库就没有了,可以更关注在业务上,而不是技术上。

通过制定主题,限制文字的抽象层次

其实上面的技术共识图就是类似的做法,只是用于技术方面,如果用于业务方面,我们可以用一些抽象的名词或动词来代替一类业务,比如下图:

(数字平台系统景观图)

上图是一个系统景观图。当前这个主题是希望人们能一眼看清楚这个系统里面的相关角色都在使用什么系统,他们关注什么,职责是什么。至于具体学什么,怎么学的,都不是那么重要。所以我们就用学习一词代表了一系列的业务。

当主题确定的时候,很多纷杂的信息就没有了。一定要克制住自己试图在一张图上表达足够多信息的冲动。

只画重要的图,剩下的交流的时候再画

除了像上面说的,不要试图在一张图上给他足够的信息。同时也不要试图把所有的信息都表达出来。

绝大多数的图可能只在交流具体业务的时候才画,推荐使用动态图。

总结

即便有了C4这么,好用的可视化工具。我们依然会看到,自己会掉进抽象的坑。所以在使用的时候一定要注意坏味道,经常自查是不是犯了抽象层次和抽象力度的错,才能做好可视化。这件事上,没有谁能幸免,所以要时常自省,与诸君共勉。


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

项目管理的三个关键

项目管理是一门抽象的学问,实践证明,能把项目带向成功的并非固定招式,也不是放之四海而皆准的标准,在项目管理这条道路上,走过的弯路、踩过的坑都有可能成为非常宝贵的经验和教训。总结了三个项目管理的关键,分享给所有项目管理者或者想成为项目管理者的伙伴。

01 管理风险,基于事实而不是感觉

风险,一直是令项目管理者头疼的问题,客户关系处理不好是风险,交付范围扩大是风险,需求变更是风险,团队合作是风险,有的时候诸多风险会一起到来,令项目管理如履薄冰,稍有不慎就会导致项目失败。

在真实风险之外,也有许多项目折戟于“想象中的风险”。换言之,很多风险并非无法解决,而是我们认为自己无法解决。

我的上一个项目,客户负责算法,交付团队负责应用程序,就在离交付只有不到三周的时候,团队提出客户的算法有bug,会导致一些我们解决能力范围之外的问题,于是报出了高风险预警,并产生了悲观情绪,认为项目必然会失败。

后来,我们做了问题整理和原因分析,结果却大大出乎我们的预料,竟然发现大部分的问题是来自应用程序,应用程序解析外界输入太脆弱,而且没有良好的容错机制,应用程序读取算法时设置了错误的参数。

也就是说,团队掉进了自己为自己挖的坑。

我们还把所有基于感觉的问题做了梳理跟踪和记录,项目结束之后大家一起做了回顾,对所有人来讲,那是一次深刻的教育。

项目交付过程中,团队可能会面临各种各样的复杂度,有时候需要突击学习,有时候需要加班赶工期,这些挑战都有可能变成团队的压力,重压之下团队很有可能变得悲观,当一个人的悲观变成一群人的悲观,团队就失去了对风险的客观判断,这时候最需要的就是事实。

所以,项目管理的第一个关键:面对风险,我们需要多做一些分析,管理风险,一定是基于事实。

02 管理客户,多一些沟通少一些猜测

很多项目管理者认为客户管理是项目管理中最有挑战的部分,而客户管理中最复杂的莫过于决策者的管理。

决策者通常来自客户的高层,有时候还是出资的那个人,他们有想法,有话语权,但其特殊的身份决定了他们一般都很忙,不是我们在项目中直接对接的那个人。

如果管理不好决策者,就经常有如下情景发生:

  • 项目开发到一半了,决策者出现,推翻了大部分做好的功能;
  • Showcase的时候决策者提出反馈,条条都是需求变更;
  • 关键业务功能找他拍板的时候,约不到时间,找不到人。
  • ……

这样的事情发生的多了,开发团队通常会这样猜测,重要的人物都很忙,我们的项目不是他的优先级,我们尽管非常需要他,但无能为力。

但当你问开发团队,决策者是否知道他很重要,是否知道他可能会是项目交付的风险和最大瓶颈,大多数时候,开发团队一脸茫然,因为很少会有人和决策者确认过这个猜测。

这是项目管理的第二个关键,有时候,我们对客户的认识基于我们的猜测,而不是事实。

我经历过一个决策者隐身的项目,也预料到他的缺席会是项目交付最大的风险,于是借助一次关键的showcase,我们暴露了所有的缺陷,也因此引起了决策者的关注,我们趁机和他做了沟通,原来他之所以和项目保持着若即若离的距离,是因为他一直以为项目一切顺利。

意识到参与的重要性之后,他和团队安排了一周两次的catch up,自那之后我们才真正将决策者引入开发的过程。

所以,在客户管理上,对客户产生正确的认识,让客户成为团队的一部分,多一些沟通,大部分的时候都会有收获

03 管理目标,不断验证并强化目标的一致性

所有的项目都是有目标的。

这个目标的设定首先来自于客户,客户想通过一个系统或者数字化手段解决什么问题,带来什么价值,对这些价值都有什么描述,成功和失败的定义是什么,除了数字化手段还有什么其它辅助方案?

绝大多数项目经理,都会有意识去收集并澄清这些信息。

团队的目标是根据客户的目标制定的,团队如何帮助客户达到目标,实现期望的价值,团队内部如何合作,团队和客户如何沟通,如何界定开发范围,根据什么进行优先级决定,这些也通常会被项目管理者纳入工作的范围。

理想情况下一旦客户的目标明确,团队的目标也会变得非常清楚。

但现实往往是,每过一段时间就会有人质疑团队是否有目标,或者抛出一个对目标的错误认知,甚至认为团队不可能达到目标。

项目经理可能会疑惑,目标不是很明确吗?团队不是一起讨论过目标吗?而且所有人都达成了共识?项目经理甚至记得这种事情发生了什么地点什么时间,他自己或者有上下文的同事说过什么话,在白板上写过什么内容。

但是,这些都无济于事。

人的认知是个很奇怪的事情,信息被植入人的大脑,但随着时间的推移,它会被迭代很多次,于是不同的人就有了不同的认识。

此时,如果项目经理还是基于以前的假设,认为所有人都已经充分了解上下文并拥有一致的认识,那就大错特错了。如果不反复强化目标,并确认所有人拥有一致的认识,就会出现这样的一些情况:

  • 团队花时间开发了无用的功能,造成浪费,但是要开发重要功能的时候却没有时间了;
  • 系统出现了问题,有人认为应该修复,有人认为不需要,对优先级的认识不一致导致了很多无效的讨论;
  • 客户也会对开发团队产生怀疑,认为团队自始至终是没有理解业务;

最终,在客户的资源耗尽之时,团队没有办法交付一个可以实现价值的可用的版本。

这也是项目管理的一个关键问题。

要解决这个问题,项目管理者需要不断验证假设,弄清楚团队是否都理解目标并且是否对目标达成一致,尤其去找中间加入项目的成员确认他们对目标的认识。

即使团队暂时性的对目标明确且达成了一致,项目管理者也需要不断的强化目标,这样才能尽可能的帮助团队统一方向,提高效率。

导致项目失败的原因有很多,遇到如上原因的话,有可能会使一个看起来成功概率很大的项目走向失败。

在《有效管理的5大兵法》中有这样一句话:解决问题,就是把可能让我们失败的因素清除了,让我们达成预期目标。

作为项目管理者,把项目带向成功,就是不断识别可能会导致项目失败的关键因素并解决问题的过程。


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

在一家技术公司做媒体

ThoughtWorks是一家专业服务公司,但更是一家特别的技术公司。你或许从不同渠道看到过来自“ThoughtWorks洞见”的内容,主题横跨ThoughtWorks的业务能力、技术专业能力、文化、职业。自第一篇文章发出至今,已有五年时间,累计641篇文章,不能说这是一个多么大的成绩,但我们在背后的坚持,乃至产生的持续影响,还是会让我自己觉得意外,毕竟我们是一家技术厂商,而不是媒体。

初衷

当年,我的初衷仅仅是为ThoughtWorks打造一个相对完善、固定的知识库,在此之前,同事们散落在网络各处(个人博客、邮件、媒体)的材料、作品,存在着巨大重复和浪费,而且极容易被时间遗忘。而在我看来,这对于一个还要教导自己和客户如何精益求精,减少浪费,交付真正价值的公司,是莫大的讽刺。

而一旦开了头,和同事一起顺势做起了内容(运营),我才越发觉得内容是如此一件做得正确的事情。但更重要的,它是一件需要正确做的事情。

在内容运营的过程中,我发现它需要牵扯的方面比我预期的多,而绝不仅仅是拿到一篇内容,发表到线上这么简单。作为一家技术厂商,ThoughtWorks有自己的标签(不管是不是外界给贴的),有内在的诉求(发展、壮大),有特别的文化价值观,有多元的业务能力,我们需要向外部呈现怎样的内容,来换一个立体、丰满而真实的ThoughtWorks形象?在这样的技术公司如何做媒体?

在做媒体的上下文里,“公司”似乎是个约束,但好像又是个特色,毕竟这里有特别的文化和思想,而“技术”则更像是机遇,是可以加持“媒体”的特色,因为这里的技术更特别。

下面我想简单聊一聊我所理解的内容运营思路,一是分享,二为交流。

受众的联结

受众,或者说读者,是我们希望那些看到我们内容的人,是我们希望能够影响到的人。“影响”,在这里似乎是个偏贬义的词——“小样儿,原来你们让我看这些东西是颇有用心。”但其实,当任何人在网络上阅读(消费)内容,影响总是会在潜移默化中产生、发酵,对此我们并不讳言。

我们的内容希望影响到你,不管是因为我们特有的文化和价值取向,还是因为我们特有的技术卓越能力,我们希望有志同道合的人能一起成长,或者加入我们。但首先在当下(自)媒体如此发达的现状中,我们遇到了一些掣肘。

由于不可言状的原因,(技术圈)人们默认对厂商的内容是抵触和不信任的,毕竟大多数厂商的内容是单调枯燥无聊的,要不就是浓烈的销售味道,比起这些,人们更愿意相信那些个自媒体背后“看起来”有血有肉,幽默风趣,又不失品味和真性情的个体,由长期建立的“信任”而自然产生的消费(转化)显得水到渠成,义无反顾。

同时,即使不被具有鲜活体征的自媒体吸引,人们也会受困于信息爆炸时代下的信息过载和知识焦虑。多数人无意识下的浅阅读,以知道代替知识,是我以为当下最悲凉的事情之一,人们被知识付费和煞有其事的推介来回拉扯,摇摆于自以为是的短暂满足感和焦虑感之间。那我们再做内容,做传播,是不是可能又加剧了这种残酷。信息过载,过度推送,是缓解了知识焦虑,还是恶化了知识焦虑?

在这样的态势下,企业的媒体化还做吗?如果答案是肯定的,那怎么做?对此,我个人一直在怀疑和坚持间徘徊。

受众很重要,如果说内容和信息是产品,那受众(读者)就是第一现场的客户,内容是来帮助他们,可以解决他们疑惑的。除了帮助解决问题,还有没有可能缓解他们的焦虑,在情绪上、经历上、情感上产生联结,用故事和经验来触动他们。

我们有两位同事的文章,就是这方面典型的例子:

我们需要和受众建立信任,共同成长,甚至共赢,这才是做内容的正确方式,不管是专业性内容,还是故事性内容,我们希望能产生与读者的联结。

优雅的运营

出于信息过载,我们不愿意在已经不堪重负的受众面前,又堆上重重的阅读包袱,不仅面临被弃读,对我们失望甚至取消关注的风险,更是置作者们辛苦的写作努力于不顾,这是对我们的内容和品牌最大的忽视和毁灭。所以在运营上,我们一直在尝试更加精细化,寻找节奏感,试图以合适的频率,合适的主题,在合适的时间段,推送到最合适的受众面前。这里存在不断磨合和尝试。

转化是在媒体运营上绕开不过的话题。我们目前的内容更倾向于承载思想、传播影响力,但这并不意味着我们不该针对特定受众做内容转化上的努力。从内容的主题策划,受众的阅读路径设置,到数据追踪,受众交互,都是我们可以努力的方向。在广泛的受众群体范围内来做转化的经营,似乎目前的国内媒体和受众阅读习惯并不提供这样的土壤,简单粗暴的阅读收费形成了短暂又粗犷的闭环,很难说这是不是培养了短视的内容消费习惯,但我个人认为用心经营媒体内容的阅读路径、读者交互、基于信任的转化,仍然是有价值而美妙的事情。

爆款,是现在坊间让人向往的事件。自己制造爆点,或者蹭上热点,内容传播效果一日千里,这样的例子的确常有发生。但我观察到的是,爆款出来或者热点蹭上以后,内容导致并不清晰的目标受众群体的反应多是肤浅和单薄的,引来哈哈一笑,或者只是沦为茶余饭后的谈资。而当热点蹭完,再度爆款已是相当不易,内容凸显疲软和苍白。在爆款上盲目跟从,会给内容运营者强烈的心理落差,反而更容易对应该做好什么做对什么,失去基本的判断。

在选择内容的推送渠道方面,因为国内的自媒体/社交媒体的极度发达,我们会很难像国外那样,仅仅依靠一个平台(网站)加三个社交媒体(Twitter、Facebook、LinkedIn)来深刻耕耘自己的内容和受众的关系。在国内,我们会不得不面对庞杂种类的内容平台、社交媒体,以及各具特色甚至排他的运营策略,所以我更相信以不变应万变,充分理解和遵守第三方平台和渠道的运营品位、策略,特别内容按需投递,在共生中求成长,毕竟到达我们的目标受众,以及建立长期而稳定的信任关系,才是我们的最终目的。

以上这些,是我一直在坚持的一些思路。过多的细节,因为篇幅所以不在讨论之列。简单地总结,在内容/媒体运营中,内容的到达方式、形式非常重要,用数据和专业性拱卫的运营思路之下,内容的持续真诚和可信赖才是根本。


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

可视化架构设计——C4介绍

好多年前,同事徐昊说过的一句话给了我很大启发,他说“纸上的不是架构,每个人脑子里的才是”。这句话告诉我们,即便是天天工作在一个团队里的人,对架构的认识也可能是不一样的。每个人嘴上说的是类似的话,但心里想象的画面仍然是不一样的。在多年的工作中,我越来越认可这句话所揭示出的道理。软件开发是一个团队协作的工作,混乱的理解会造成架构的无意义腐化、技术债的无意识积累、维护成本的无价值上升。

最近听到一句话,“那些精妙的方案之所以落不了地,是因为没有在设计上兼容人类的愚蠢”。话糙理不糙,虽然最终人们选择的方案的思想都是在十年前甚至几十年前就已经存在的,然而在技术升级到足以“兼容”人类的愚蠢之前,这些思想只能在学术的故纸堆里睡大觉。当然话糙确实也会有一个问题,将一个思想性问题转化成了情绪性问题。人们容易把一些糟心的事情归因到人类的愚蠢,在宣泄完不满情绪后就停止思考了。作为知识工作者,我们的思维不能停步,我们需要思考到底人类有哪些愚蠢,分别用什么方法去避免或者“兼容”。

可以肯定彼此明明对自己开发的软件有不一样的认识却天天在一起讨论问题并试图把软件做好是一件愚蠢的事情,为了兼容这种愚蠢我们需要采用可视化的方法。

为什么需要可视化呢,主要还是语言不靠谱。人类语言真的是太随意了,只要你想,你可以说你见过一个方形的圆,并为此与别人辩论。但是无论如何你也画不出来一个方形的圆,这就是我们需要可视化的原因。

今天我们介绍一个工具,叫做C4 model,这是我近几年见到的一个比较难得跟我的认知有大量共鸣的工具。

该工具的作者在多年的咨询中经常发现,很多个人画出来的架构图都是不一样的,但也不是说谁画错了,而是每个人的抽象层次不一样。抽象层次这种东西,说起来好像存在,但真要说清楚还挺难,于是作者类比地图,提出了缩放的概念。(两年前我在教学生的时候提过同样的概念)如下图:

上面的四张地图就是想说明,当我们看待真实世界的“架构图”时候,也是要不停的缩放,在每一个层次刻意忽略一些细节才能表达好当前抽象层次的信息。所以他类比着把架构也提出了四个抽象层次:

从上到下依次是系统System、容器Container、组件Component和代码Code。(咦,那为什么叫C4呢,因为系统的图叫System Context,系统上下文图。为了凑四个C也是够拼的。)

基于这四个层次的抽象,C4模型由4张核心图和3张附属图组成,分别用于描述不同的场景,下面我们一一介绍一下。

四张核心图

系统上下文图

如上图所示,这个图表达的是你所开发的系统和它的用户以及它所依赖的系统之间的关系。从这个图上我们已经看出来C4图形的几个关键图形:

C4说穿了就是几个要素:关系——带箭头的线、元素——方块和角色、关系描述——线上的文字、元素的描述——方块和角色里的文字、元素的标记——方块和角色的颜色、虚线框(在C4里面虚线框的表达力被极大的限制了,我觉得可以给虚线框更大的扩展空间)。

通过在不同的抽象层次上,重新定义方块和虚线框的含义来将我们的表达限制在一个抽象层次上,从而避免在表达的时候产生抽象层次混乱的问题。

那么在系统上下文图里,方块指代的是软件系统,蓝色表示我们聚焦的系统,也就是我开发的系统(也可能是我分析的系统,取决于我是谁),灰色表示我们直接依赖的系统,虚线框表示的是企业的边界。通过这些图形化的元素表达我们可以看出来各个系统彼此之间的关系。

容器图

当我们放大一个系统,就会看到容器,如上图所示,C4模型认为系统是由容器组成的。我个人认为,容器是C4模型最大的创举,尤其是在这个单体架构快速崩塌的时代。所谓容器,既不是Docker的容器,也不是JavaEE里的容器,而是借用了进程模型,代指有自己独立进程空间的一种存在。不管是在服务器上的单独进程空间,还是在浏览器里的单独进程空间,只要是单独的进程空间就可以看作一个容器。当然如果你容器化做得好,Docker的Container和这个Container可以一一对应。有了这个概念的存在我们就可以更清晰的去表达我们的架构,而不是总是用一些模糊的东西。

组件图

当我们放大一个容器,我们就会看到组件,如上图所示。组件在这里面很好的把接口和它的实现类打包成一个概念来表达关系。我个人觉得有时候一些存在于代码中,但又不是接口的某些东西,比如Service、Controller、Repository之类也可以用组件图来表达,如果你学了一些没有明确抽象层次的架构知识或者一些单体时代的遗留经验的时候,你可以画出来一些组件图,来印证自己的理解,如下图,是我画的自己对DDD战术设计里面的一些概念的理解:

比起模糊的堆砌在一起的文字,这种表达要清晰的很多,哪怕我的理解是不对的,也容易指出和讨论。

代码图

代码图没什么可说的,就是UML里的类图之类很细节的图。一般是不画的,都是代码生成出来。除非非常重要的且还没有写出代码的组件才画代码图。

以上就是C4的核心图,我们可以看到四种不同的抽象层次的定义会让我们更容易固定住我们讨论的层次,这点上我觉得C4是非常有价值的。

三张扩展图

架构设计设计要考虑的维度很多,仅四张核心图是不够的,所以作者又提供了三张扩展图,可以让我们关注更多的维度。

系统景观图

看得出来,系统景观图是比上下文图更丰富的系统级别的表达。不像上下文图只关注聚焦系统和它的直接关系,连一些间接相关的系统都会标示出来,那些系统的用户以及用户之间的关系也会标示出来,只是内部的用户会用灰色标记。

这个图有什么用呢?在我们分析一个企业的时候,我们需要一个工具帮助我们把一家公司给挖个底掉,做到完全穷尽,才能看到企业的全景图,从而理解局部的正确定位以做好局部设计为全局优化服务。之前我试过以四色建模的红卡、事件风暴的事件两种工具来教人掌握这种能力,一般来说,程序员学员都无法快速掌握这种顺藤摸瓜的分析技巧,毕竟跟程序员的思维还是有些差异的。但是用了系统景观图之后,学员就毫不费力的掌握了这种分析能力,所以我后来都是用这个图来教程序员探索企业的数字化全景图,效果极好,推荐给大家。

动态图

动态图不同于其他表达静态关系的图,它是用来表达动态关系的,也就是不同的元素之间是如何调用来完成一个业务的。所以动态图不仅仅适用于一个层面上,它在系统级、容器级和组件级都可以画,表达的目标是不一样的。

我之前曾经写过名为《像机器一样思考》的一系列文章,在文中也发明了类似的图,不同于本文中关系线上标注的是调用的方法、函数,我更关注的是数据,使用效果也很好。

什么时候用动态图呢?举个小例子,我之前做一个内部的小系统,团队中只有一个有经验的工程师带着十多个毕业生,我便要求他们在开始工作之前都画出动态图来,交由有经验的工程师去评估他们的思路是否正确,如果有问题,就在开始之前就扼杀掉烂设计。不管是毕业生还是初级工程师,改代码的能力都比写代码的能力要差很多,所以将烂设计扼杀在实现之前还是有帮助的。

部署图

前面的几张图都是站在开发的角度思考,但是一个没有充分思考过部署的架构很容易变成一个运维的灾难。所以作者提供了一个部署图。考虑到DevOps运动如火如荼,这个图可以变成很好的Dev和Ops之间沟通的桥梁。我们在实操中发现,Dev和Ops关注点的不同、语言的不一致,在这张图上表现得非常清楚。

图上最大的的实线框不同于虚线框,它表达的是数据中心,当你开始考虑异地载备的时候它就有了意义。数据的同步、实例的数量都会影响部署图的内容。部署图基本都是容器级的,它能很好的表达出来容器到底部署了几个实例,部署在什么样的操作系统上,一个节点部署了几个容器之类,我们在实际使用中,发现需要考虑的信息太多,自己就抽象出了类似于亚马逊上实例规格的Small、Large之类的术语来表达机器配置,增进了开发和运维之间的交流准确性。

为什么C4值得推荐

够直观,对于程序员来说容易理解,容易使用。

我们在开头的时候说过,只有每个人脑子里的才是架构图,如果我们使用一个本身就很难达成一致理解的工具,那成员就会陷入理解的死循环。经过尝试教授不同工具,发现C4模型是最容易理解、最容易使用的工具。可能它的概念是复用了程序员已有的一些认知模型,程序员在学习后都可以迅速的使用起来,并问出一些高质量的问题。

总结

在思维的世界里,我们都是盲人,很多东西我们以为自己知道,实际上画出来之后,才发现很多东西没想到,或者想的是乱的,同时别人也才可以给我们反馈。

有了上面的这个工具,我们就可以开始可视化的架构设计之路了,但路上还有一个心魔需要战胜。在我们的文化里,出错是一件很丢人的事情,所以我们喜欢用一些模糊的描述避免被别人挑战,而可视化是让我们精确的描述出自己的理解,来欢迎别人的挑战。这一个坎不太容易跨过去,但是一旦跨过去、大家形成正向的互动之后,我们的进步速度会变得很快,从而把封闭的人远远的甩在后面,获得组织级的成长推力。我自己就在跟别人的交流之后获得了更深入的洞见,本文已经分享了一些,还有一些内容后续再跟大家分享。


注:文中图片均来自:https://c4model.com/

更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

细说API – 重新认识RESTful

如果你是一个客户端、前端开发者,你可能会在某个时间吐槽过后端工程师的API设计,原因可能是文档不完善、返回数据丢字段、错误码不清晰等。如果你是一个后端API开发者,你一定在某些时候感到困惑,怎么让接口URL设计的合理,数据格式怎么定,错误码怎么处理,然后怎么才能合适的描述我的API,API怎么认证用户的请求。

在前后端分离和微服务成为现代软件开发的大趋势下,API设计也应该变得越来越规范和高效。本篇希望把API相关的概念最朴素的方式梳理,对API设计有一个更全面和细致的认识,构建出更规范、设计清晰和文档完善的API。

重新认识API

广义的API(Application Programming Interface)是指应用程序编程接口,包括在操作系统中的动态链接库文件例如dll\so,或者基于TCP层的socket连接,用来提供预定义的方法和函数,调用者无需访问源码和理解内部原理便可实现相应功能。而当前通常指通过HTTP协议传输的web service技术。

API在概念上和语言无关,理论上具有网络操作能力的所有编程语言都可以提供API服务。Java、PHP、Node甚至C都可以实现web API,都是通过响应HTTP请求并构造HTTP包来完成的,但是内部实现原理不同。例如QQ邮箱就是通过使用了C构建CGI服务器实现的。

API在概念上和JSON和XML等媒体类型无关,JSON和XML只是一种传输或媒体格式,便于计算机解析和读取数据,因此都有一个共同特点就是具有几个基本数据类型,同时提供了嵌套和列表的数据表达方式。JSON因为更加轻量、容易解析、和JavaScript天生集成,因此成为现在主流传输格式。在特殊的场景下可以构造自己的传输格式,例如JSONP传输的实际上是一段JavaScript代码来实现跨域。

基于以上,API设计的目的是为了让程序可读,应当遵从简单、易用、无状态等特性,这也是为什么Restful风格流行的原因。

RESTful

REST(英文:Representational State Transfer,简称REST),RESTful是一种对基于HTTP的应用设计风格,只是提供了一组设计原则和约束条件,而不是一种标准。网络上有大量对RESTful风格的解读,简单来说Restful定义URI和HTTP状态码,让你的API设计变得更简洁、清晰和富有层次,对缓存等实现更有帮助。RESTful不是灵丹妙药,也不是银弹。

RESTful第一次被提出是在2000Roy Fielding的博士论文中,他也是HTTP协议标准制定者之一。从本质上理解RESTful,它其实是尽可能复用HTTP特性来规范软件设计,甚至提高传输效率。HTTP包处于网络应用层,因此HTTP包为平台无关的字符串表示,如果尽可能的使用HTTP的包特征而不是大量在body定义自己的规则,可以用更简洁、清晰、高效的方式实现同样的需求。

用我几年前一个真实的例子,我们为了提供一个订单信息API,为了更方便传递信息全部使用了POST请求,使用了定义了method表明调用方法:

返回定义了自己的状态:

大家现在来看例子会觉得设计上很糟糕,但是在当时大量的API是这样设计的。操作资源的动作全部在数据体里面重新定义了一遍,URL上不能体现出任何有价值的信息,为缓存机制带来麻烦。对前端来说,在组装请求的时候显得麻烦不说,另外返回到数据的时候需要检查HTTP的状态是不是200,还需要检查status字段。

那么使用RESTful的例子是什么样呢:

例子中使用路径参数构建URL和HTTP动词来区分我们需要对服务所做出的操作,而不是使用URL上的接口名称,例如 getProducts等;使用HTTP状态码,而不是在body中自定义一个状态码字段;URL需要有层次的设计,例如/catetory/{category_id}/products 便于获取path参数,在以后例如负载均衡和缓存的路由非常有好处。

RESTful的本质是基于HTTP协议对资源的增删改查操作做出定义。理解HTTP协议非常简单,HTTP是通过网络socket发送一段字符串,这个字符串由键值对组成的header部分和纯文本的body部分组成。Url、Cookie、Method都在header中。

几个典型的RESTful API场景:

虽然HTTP协议定义了其他的Method,但是就普通场景来说,用好上面的几项已经足够了

RESTful的几个注意点:

  • URL只是表达被操作的资源位置,因此不应该使用动词,且注意单复数区分
  • 除了POST和DELETE之外,其他的操作需要冥等的,例如对数据多次更新应该返回同样的内容
  • 设计风格没有对错之分,RESTful一种设计风格,与此对应的还有RPC甚至自定义的风格
  • RESTful和语言、传输格式无关
  • 无状态,HTTP设计本来就是没有状态的,之所以看起来有状态因为我们浏览器使用了Cookies,每次请求都会把Session ID(可以看做身份标识)传递到headers中。关于RESTful风格下怎么做用户身份认证我们会在后面讲到。
  • RESTful没有定义body中内容传输的格式,有另外的规范来描述怎么设计body的数据结构,网络上有些文章对RESTful的范围理解有差异

JSON API

因为RESTful风格仅仅规定了URL和HTTP Method的使用,并没有定义body中数据格式的。我们怎么定义请求或者返回对象的结构,以及该如何针对不同的情况返回不同的HTTP 状态码?

同样的,这个世界上已经有人注意到这个问题,有一份叫做JSON API开源规范文档描述了如何传递数据的格式,JSON API最早来源于Ember Data(Ember是一个JavaScript前端框架,在框架中定义了一个通用的数据格式,后来被广泛认可)。

JSON已经是最主流的网络传输格式,因此本文默认JSON作为传输格式来讨论后面的话题。JSONAPI尝试去提供一个非常通用的描述数据资源的格式,关于记录的创建、更新和删除,因此要求在前后端均容易实现,并包含了基本的关系类型。个人理解,它的设计非常接近数据库ORM输出的数据类型,和一些Nosql(例如MongoDB)的数据结构也很像,从而对前端开发者来说拥有操作数据库或数据集合的体验。另外一个使用这个规范的好处是,已经有大量的库和框架做了相关实现,例如,backbone-jsonapi ,json-patch。

没有必要把JSON API文档全部搬过来,这里重点介绍常用部分内容。

MIME 类型

JSON API数据格式已经被IANA机构接受了注册,因此必须使用application/vnd.api+json类型。客户端请求头中Content-Type应该为application/vnd.api+json,并且在Accept中也必须包含application/vnd.api+json。如果指定错误服务器应该返回415或406状态码。

JSON文档结构

在顶级节点使用data、errors、meta,来描述数据、错误信息、元信息,注意data和errors应该互斥,不能再一个文档中同时存在,meta在项目实际上用的很少,只有特别情况才需要用到,比如返回服务器的一些信息。

data属性

一个典型的data的对象格式,我们的有效信息一般都放在attributes中。

  • id显而易见为唯一标识,可以为数字也可以为hash字符串,取决于后端实现
  • type 描述数据的类型,可以对应为数据模型的类名
  • attributes 代表资源的具体数据
  • relationships、links为可选属性,用来放置关联数据和资源地址等数据

errors属性

这里的errors和data有一点不同,一般来说返回值中errors作为列表存在,因为针对每个资源可能出现多个错误信息。最典型的例子为,我们请求的对象中某些字段不符合验证要求,这里需要返回验证信息,但是HTTP状态码会使用一个通用的401,然后把具体的验证信息在errors给出来。

在title字段中给出错误信息,如果我们在本地或者开发环境想打出更多的调试堆栈信息,我们可以增加一个detail字段让调试更加方便。需要注意的一点是,我们应该在生产环境屏蔽部分敏感信息,detail字段最好在生产环境不可见。

常用的返回码

返回码这部分是我开始设计API最感到迷惑的地方,如果你去查看HTTP协议文档,文档上有几十个状态码让你无从下手。实际上我们能在真实环境中用到的并不多,这里会介绍几个典型的场景。

200 OK

200是一个最常用的状态码用来表示请求成功,例如GET请求到某一个资源,或者更新、删除某资源。 需要注意的是使用POST创建资源应该返回201表示数据被创建。

201 Created

如果客户端发起一个POST请求,在RESTful部分我们提到,POST为创建资源,如果服务器处理成功应该返回一个创建成功的标志,在HTTP协议中,201为新建成功的状态。文档规定,服务器必须在data中返回id和type。 下面是一个HTTP的返回例子:

在HTTP协议中,2XX的状态码都表示成功,还有202、204等用的较少,就不做过多介绍了,4XX返回客户端错误,会重点介绍。

401 Unauthorized

如果服务器在检查用户输入的时候,需要传入的参数不能满足条件,服务器可以给出401错误,标记客户端错误,需要客户端自查。

415 Unsupported Media Type

当服务器媒体类型Content-Type和Accept指定错误的时候,应该返回415。

403 Forbidden

当客户端访问未授权的资源时,服务器应该返回403要求用户授权信息。

404 Not Found

这个太常见了,当指定资源找不到时服务器应当返回404。

500 Internal Server Error

当服务器发生任何内部错误时,应当返回500,并给出errors字段,必要的时候需要返回错误的code,便于查错。一般来说,500错误是为了区分4XX错误,包括任何服务器内部技术或者业务异常都应该返回500。

HATEOAS

这个时候有些了解过HATEOAS同学会觉得上面的links和HATEOAS思想很像,那么HATEOAS是个什么呢,为什么又有一个陌生的名词要学。 实际上HATEOAS算作被JSON API定义了的一部分,HATEOAS思想是既然Restful是利用HTTP协议来进行增删改查,那我们怎么在没有文档的情况下找到这些资源的地址呢,一种可行的办法就是在API的返回体里面加入导航信息,也就是links。这样就像HTML中的A标签实现了超文本文档一样,实现了超链接JSON文档。

超链接JSON文档是我造的一个词,它的真是名字是Hypermedia As The Engine Of Application State,中文叫做超媒体应用程序状态的引擎,网上很多讲它。但是它并不是一个很高大上的概念,在RESTful和JSONAPI部分我们都贯穿了HATEOAS思想。下面给出一个典型的例子进一步说明:

如果在某个系统中产品和订单是一对多的关系,那我们给产品的返回值可以定义为:

从返回中我们能得到links中product的的资源地址,同时也能得到orders的地址,这样我们不需要客户端自己拼装地址,就能够得到请求orders的地址。如果我们严格按照HATEOAS开发,客户端只需要在配置文件中定义一个入口地址就能够完成所有操作,在资源地址发生变化的时候也能自动适配。

当然,在实际项目中要使用HATEOAS也要付出额外的工作量(包括开发和前后端联调),HATEOAS只是一种思想,怎么在项目中使用也需要灵活应对了。

参考链接

在文档中还定义了分页、过滤、包含等更多内容,请移步文档:

英文版:http://jsonapi.org/format/

中文版:http://jsonapi.org.cn/format/ (PS:中文版更新不及时,请以英文文档为准)


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share