1. 组件化的悠久历史
组件化从来都不是新的概念,认识这个图标的同学,应该都是资深程序员了:
正如MS设计的C++的产品标识图,++的作用不仅仅是C的增强,同时也具备了更好的模块化特性,甚至希望程序如同积木可以快速的拼装。看来组件化从C++时代以来自有之,只不过以前更多的叫做模块化。可以说,面向过程的C语言统治时代,模块化也是追求的目标。一个function就是一个模块(功能模块),一个类就是一个模块,而多个模块组成的头文件/lib文件,也即是C/C++时代的组件了。
组件化和模块化,追求的终极目标是一样的:重用。但我们在追求如何更好地重用的路上,却探索出了相当多的思路、技术。
组件化,可组装,仍然是这个古老的目标的延续:重用。
需要注意的是,重用并不只是“节约”的需要,重用隐含了“标准化”和“适应变化的能力“,这也正是构建复杂系统的关键。
从以上”悠久“的历史来看,组件化的思想,已经是软件开发中的常客。
2. 软件开发的问题思考
复杂系统设计的挑战和组件化
既然重用,就需要思考,需要设计,因为真正的软件系统,总是充满各种挑战,其中最难对付的”魔鬼“就是系统的复杂性。
说起复杂系统,不得不提Brooks这个大牛。当年IBM花掉50亿美元(相当于现在的约400亿美元,超过当时IBM一年的总收入)打造S/360的传奇故事的主角就是他。
提S/360,是因为这个项目作为软件历史上第一个超级复杂系统,在让IBM获得巨大的成功的同时,也催生了Brooks的那片名作:《没有银弹:软件工程的本质性与附属性工作》。正是这篇名作的观点”由于软件的复杂性本质,而使真正的银弹并不存在“,引发了热烈的、广泛的讨论。而面向对象、C++等技术的诞生,相信也和这些广泛的讨论有着必然的联系。
今天暂且放一放仍值得争论的”有没有银弹“的问题,我们来一个走马观花,用我们已经熟悉的只是来看一看,重用的探索路上,有什么值得再思考下。
笔者是C/C++开发出身,但大部分工作经验都是基于Java技术体系的。而Java的成功,也是OOP伴随95后互联网兴起的成功。学习Java的同学都需要深入理解面向对象编程和设计(OOP/OOD),那么面向对象编程又有哪些解决复杂问题或者重用问题的机制呢?我们从面向对象编程的第一步动作开始思考:抽象。
笔者认为抽象是OOP过程中最重要最基础最深刻的一环。抽象并不是软件科学的专利,就好比我们用数学研究一个物理系统,我们会有质点,我们需要抓住文本最本质的那一部分来研究。
抓住了本质,就能迎接变化,而适应变化是构建复杂系统过程中必须面对的问题。
然后我们从面向对象编程的三个再来看一看OOP基本概念:”封装、继承、多态“。
封装这个特性,其实更贴近组件化思想,这也是解决复杂系统构建的得力措施。封装正词如其意,就是希望一个组件(或者一个类),隐藏细节,暴露接口。换句话说,封装其实就是该暴露的暴露,该隐藏的隐藏。
继承本身就是重用,只是Java语言设计上考虑了如何”更好“地重用,加上引号是因为更好并不是一个单纯地动机,对于不同场景重用本身地目标就有所不同。
多态的基础目标是实现适应变化,比如一个接口用来获取用户数据,那么当数据源从RDB切换到NOSQL再到服务,怎样才能最小程度地避免代码修改,就是多态要解决地问题。这也正是组件化的重要思想。
封装、继承、多态,可以作为组件化设计的基础标准,尤其是封装和多态。
做Java开发和设计工作的同学,都熟悉OOD的一些准则,这些准则也能给我们带来很多灵感,这些笔者认为也是思考组件化的有用根基。比如高内聚低耦合。
高内聚低耦合是面向对象程序设计的精髓,笔者认为在实际开发中可以奉为圭臬。
真正落地高内聚低耦合并不容易,实际代码中泛滥的@Autowired并不少见,基础的依赖管理混乱也不少见。甚至,大家可能对高内聚低耦合的热爱远远抵不上加班进度的压力。
但这不意味着我们应该放弃对高内聚低耦合的追求,恰恰相反,无论在现在或者是将来,我们都可考虑使用不同手段、不同思路来更好的实现代码或者模块的高内聚低耦合。
所以,组件化也是和一直以来的软件工程一脉相传的目标,秉承良好的封装抽象的、高内聚低耦合的意图设计和实现的组件,我想就已经满足了组件化的大部分特性。
而实现这些目标的组件,也是复杂系统构建的良好基石和精良的材料。如果说我们实现组件化的进度条是100%,那么笔者认为,做到这些你已经实现了70%。
顺道一提,软件行业的故事,很多都非常精彩。比如微软的观止,包括S/360的相当精彩的故事,如果您没有读过,一定要去看看。除了声名远播的成就,它也差点毁掉IBM;同时它不仅仅是第一次超复杂系统的探索,也是缓存技术、虚拟机技术的最早探索。从这些书籍中,能读到的精彩,不仅仅是精彩,也是大型软件系统践行中的策略和战术的最好学习素材。
人的理解困境、不完备性,概念分层
为什么说就算是良好的设计,仍然仅仅实现了70%?我们不得不承认,每个人能理解、记住的概念总是有限的,一个人不可能是所有领域的专家,这就意味着任何软件设计或者开发人员,其实都是某种程度的“一知半解”。
解决任何复杂问题的基本策略都会用到分治,我想软件工程也是一样。如果我不知道全部的信息,那我是否可以在高层概念上提出策略目标?如果我擅长的是某个业务领域,那我是不是可以从高层概念中专攻一个模块?
我想答案是正向的,而且大家都在这么做,这也就是大型项目从Roadmap,到产品设计,到基础架构,到业务架构,到数据,到架构,到具体研发,复杂的项目都会分工协作。
那么使用组件化思想实现复杂系统,应该也可以采取分层策略,分工策略。在DDD中,我们聚焦业务模型,而不是存储、通讯,也是因为要做好高层概念的统一。
再谈组件化的封装和内聚
前文对抽象的讨论,浅尝而已,这里我们可以稍微再深入一点:如何更好的抽象?
我之前的某个朋友,典型的艺术派,很有高斯林(如下图)的气质,他本人照片就暂时不放了,大家可以脑补下:
这大神很好地向我们展示了极限情况下怎么更好地抽象:成为业务地专家。 09年地时候他参与了一个法院相关系统的建设,因为牵扯了很多专业知识,这位大神不仅仅作为技术大神,也潜心研究专业知识,做到了项目交付之时也是他律师证到手之日。这在当时着实让我们几位小伙伴颇为崇拜,毕竟就算在没有专业产品设计岗位分工的年代,这也是非常深入的沉入到业务当中了(顺便提一句,这兄弟当时也是微软MVP)。
大神用来膜拜,膜拜之时是不是也可以给我们一些启示?那么在当今,有了专业的产品设计人员,我们应该怎么看待呢?我仍然坚定地认为:”对业务背景地专业知识的深入、系统了解,是更好的使用技术手段抽象和设计对应行业系统的基础“
但这里仍存在一个悖论,互联网告诉发展的这些年,我们手上的开发任务设计任务都需要996来支持,我们哪有时间来学习另外一个专业的知识呢?这个问题也许小伙伴们有答案了,欢迎留言交流。
3. 再看组件,怎么创作组件以及怎么才能算是一个组件
写到这里,小伙伴们大概已经能读出一二,在笔者心中,良好的动机(组件化)+ 基础的技术概念 + 敬业专业,就基本具备了设计良好组件化系统的功底,也具备了解决复杂技术系统建设的能力。
那么我们从另外一个角度看看,阿里的大神们是怎么看待组件化的呢?
云巧定义的组件思考
云巧中提到了可装配组件的几个原则,这些原则的和我们上述的组件化是什么关系?又有什么新的发现?
云巧对组件的定义来自于Gartner提出的组装的单元PBC(Packaged Business Capability),技术上来说,PBC是一个有边界的集合,这个集合的组成元素是数据schema,一堆的服务(注意并没有说一定得是微服务),API和事件通道。
- 模块化
模块化的概念,我们在前面有所描述,信息隐藏(封装)和分离关注点正是良好的封装设计的基本目标,同时功能完整性则是高内聚低诉求。所以模块的概念,从最早的C语言,到后来的C++/Java,到现代的Python/Rust,本质仍然坚如磐石。 - 自治
对自治的理解,和基础的OOD/OOP相对,增加了落地和实际系统特点的考虑,增加了边界的概念。这样在实际创建组件时,能创建能落地集成的系统。组件关注的是能力,比如接口协议之类应该是能力的形式,组件应该负责支持主流的协议形式。 - 可发现
可发现和后面的可编排,更多来自于实践。我们可以想到,当我们真正使用组件”组装业务“时,组装组件本身采用硬编码方式往往不够灵活。之前我们可以基于插件机制,基于SPI等方式,实现业务的扩展。但当组件扩展到通用概念时,自然就诞生了可发现可编排的思想。 - 可编排
可编排主要是保障业务足够稳健,保障不过多依赖环境,不会因为重复执行而产生错误等。
首先到此我们应该有个重大的变化,在之前讨论OOP/OOD时,我们讨论的组件更多的是数据结构和逻辑,但组件实际上如果要自治,也应该包括数据存储、配置能力,甚至应该包括UI。只要能适应组件化的需求,甚至还可以包括资源,比如服务器。这在云计算时代也不难实现,也能更好的切合组件的思想。
组件本身都携带了可组装的诉求,云巧作为组件平台,更会强调生态,所以强调了可装配的特性。
以上这些特点,无外乎都是为了更好的集成、更稳健的组装,从而迎接复杂性的挑战和变更的挑战。那么这些特点有没有明确的参考标准呢。首先模块化也好、自治也好,这些问题都是并没有完全明确的标准,需要我们在日常工作中根据业务优化和挑战,做出最佳选择。第二编排服务,实际要根据业务情况进行详细的考虑,也经常需要对业务的上下游进行细致的分析,在实际开发中也需要根据成本综合考虑,但方向总是OK的。
从云巧的技术文档来看,云巧沉淀了很多实践的经验,基于云巧市场也提供了基于目标的完整解决方案,包括组件发布、组装服务等。这既可以作为上手的平台,也可以作为学习研究的对象。
所有云巧对组件化的讨论,结尾也提到”没有最可组装,只有更可组装“。我等程序员大可放心,还是大量的业务场景需要你我根据情况决策和优化。😄
从组件使用思考组件的创作的策略和技巧
我们不妨结合到实际的简单案例,利用前面的回顾,看一看可能怎么更好的设计一个可装配的组件,同时还能照顾到日常开发实际的需求。
我最近的项目中有一个国家标准的行业的相关诉求,需要提供标准行业的查询能力给各个服务。
我们首先来看如果要设计一个国家标准行业组件的边界:提供查询服务,支持行业code精确查询,父行业精确查询,行业名称模糊查询,行业树。
然后我们来看看,应该这么设计这么一个”行业模块组件“。首先我们需要提供的组件资源包括服务、数据、UI,没有事件。为什么提供UI,是因为选择行业的Tree是通用场景,而且在大部分环境中都可以基于同样的交互实现,只是需要考虑支持单选和多选两种模式。所以这个UI是有价值的。
虽然说我们应该为该组件同时提供基于Restful和GraphQL服务能力,但是基于公司业务背景、现实情况和成本,我们目前不会支持GraphQL(因为收益不足)。所以模块的定义不是绝对的标准。
然后自治这块,我们需要该模块不依赖任何非基础设施。但是到底什么算是基础设施呢?RDS和Redis算不算,我们是否应该使用JSON文件代替RDS,使用Map或者其他结构代替Redis?笔者的答案是未尝不可,因为自治理论上依赖越少越好,包括中间件。但如果这个组件本身基于一个微服务发布,而服务中RDS和Redis本身就是必备基础措施,那依赖也未尝不可,而且好处是将来提供了编辑数据的能力,就看是否需要这些能力罢了。根据笔者的实际情况,我会选择后者,因为后期的灵活性更好。
至于可发现,如果是基于微服务,那么我们可以将行业组件作为独立服务发布或者作为基础服务的一分部发布,这样本身就具备了一定的服务可发现。但是如果要接入云巧市场,也可以通过云巧的相关后台接入。
但如果是一个单体应用去组装这个组件呢?那我们是不是也可以回到SPI的路线,或者自己编写简单的服务发现和注册机制也可以(基于Spring容器)。我们要的是可发现的结果,而不是形式嘛。
可编排方面,因为都是查询接口,目前是完全支持可编排的,只是如果将来支持某些信息的修改(如果真要支持,此时一定要回到模块化和自治思考,是不是应该包含这些信息了),就需要把服务放到上下游业务中一起来看看修改的结果了。
这里举另外一个例子:某系统中地址信息作为基础服务,是需要维护的。而地址信息又是人员信息、房屋信息的基础服务,所以就会遇到一个问题,当我们编辑了地址信息的时候,比如名称变更、地址上下级变更,就不能简单的处理了。这个时候一定需要拿出来放到业务中,去看更改名称对其他上下游尤其是下游业务的影响,去看变更上下级对上下游业务的影响了。
4. 组件化的进一步思考:有没有终极目标?
软件开发究竟有没有银弹,这个当年brooks大神抛出的问题,会不会正如当年的费马大定律,在很长时间之后要给出一个答案了呢?虽然说brooks大神已经有了结论,但我们就无需探索了吗?非也!
首先我们就需要思考一个问题:我们要对付的一定是狼人吗?一定是整个狼人种群吗?
而且我们也可以想一想,是所有地方都必须使用银弹吗?是否可以考虑使用铜弹呢?
毫无疑问,无论是开发人员还是架构设计人员,也包括产品设计,基于组件化、云巧和云巧市场的一些理念和策略,都是值得学习和借鉴的。软件工程、软件开发中,有太多的思想、策略、技巧、经验值得我们去学习和掌握,但人总是精力有限,如何把握住核心目标,找到抽象的本质,不断根据本质挑战战术,也许是个人职业生涯的主旋律。