前端架构演进 - 从单体到微前端(理论篇)

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们首先需要认识到每一个系统的架构都不应该是一成不变的,为了应对业务的变化,我们不应该只有重写这一个选项。但往往架构的迁移业务方不会给开发人员预留充足的时间,在短时间内平滑地将旧的架构向新的架构演进就成为了一个需要解决的问题。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文将从一个我最近经历的项目出发,讲解我们是如何在两周时间内将一个单体前端应用演化为一个","attrs":{}},{"type":"link","attrs":{"href":"https://teobler.com/posts/20201125-micro-frontends","title":"","type":null},"content":[{"type":"text","text":"微前端应用","attrs":{}}]},{"type":"text","text":"的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"为什么有这次演进","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ce/ce16264583e2c9c2959c30a4216f82fe.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"不是为了解决问题胡乱上莫名其妙的解决方案就是耍流氓","attrs":{}},{"type":"text","text":"。微前端和微服务一样都是为了解决问题而诞生的解决方案,先看看你的项目是不是也遇到了这些问题,再决定做不做吧。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我们项目是一个 To B 的交付项目,是某组织为其多个部门协调合作为愿景而设想的一个系统。各个部门的工作人员为了完成各自的业务需要访问该系统下的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一个或多个","attrs":{}},{"type":"text","text":"子系统。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这样的业务场景下在项目开始之初后端很自然地选择了微服务作为业务解耦和降低系统复杂度的解决方案,但前端因为考虑不周加上客户比较保守并没有采取微前端的解决方案,而是以分文件目录的方式尝试区分各个子系统。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在第一个子系统顺利完成交付后我们意识到了一些问题:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一期项目上线后转由公司内另外的组维护,我们在做后面的项目时难免会修改到一期或者公用的代码,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"两个团队","attrs":{}},{"type":"text","text":"势必会造成","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"代码冲突","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整个系统过于庞大,我们的体量没办法吃下整个合同,可预见","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"未来会有第三方甚至第四方公司加入交付","attrs":{}},{"type":"text","text":",现在的单体应用不但会造成大量的团队代码冲突,而且","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"限定了整个项目的技术栈","attrs":{}},{"type":"text","text":",不利于后期的跨团队合作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然我们的应用通过 AWS EKS 部署,没有宕机的烦恼,但是现在的架构","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"无法实现独立部署","attrs":{}},{"type":"text","text":",每一次子系统的部署","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"需要对整个应用进行打包","attrs":{}},{"type":"text","text":",同时如果一个应用挂了,将会影响整个系统,微前端可以在这件事上做得更好","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"演进发生的时机","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a5/a56afc3a48a187450d25bb44be6950ee.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"架构需要发生改变往往是因为开发人员发现当前的架构没办法应对业务的发展和变化,需要改变架构来适应新的业务。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也有可能是当前业务已经复杂到一定程度,需要对架构做一些改变来对业务做一些解耦降低整个系统的复杂度,使系统更易维护。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而不管是什么原因,在真正开始改变架构时都需要在交付的过程中花费额外的时间精力。但前面我们也提到过,往往业务方不会给足够的时间来让开发人员完成架构的演进。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"选择一个恰当的时机","attrs":{}},{"type":"text","text":"也就成为了一个重要的点。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就我们的情况而言,时机在一期项目上线后,二期项目准备阶段,于是我们在新项目的第 0 个迭代启动了前端架构演进。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而如果我们就是连两个周的时间都没有,那么就真的只能在交付的过程中加入一些 tech 卡,在别的分支上边交付边改进。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"目标","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/99/99514c24b5d2f6902277642c4f034b0f.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先既然是架构的演进,那么就不会有完成的那一天,但是应该有一个","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最小的目标","attrs":{}},{"type":"text","text":",只要达成了这个最小的目标,已经能解决开发过程中的主要问题,这次演进就算是达到目的了,基于此我们在演进开始前规划了相应目标:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"不动基础设施,尽最大可能节省工作量,将所有应用打包后部署到同一个 nginx,将不同的应用放在不同的 folder 下,后续项目稳定后再推动客户拆分基础设施","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"先做最坏的打算,假如我们两个星期内完不成拆分该怎么办","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"保持 master 代码不动,计划后续如何在 master 代码分支上也能继续开发,同时新建分支完成代码拆分工作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"保持现有 pipeline 不动,用于支持现有 master 分支代码,新建一条全新的 pipeline 适配新的应用","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"先只拆分整个应用的代码部分,时刻与 BA 和 后端小伙伴保持沟通,以业务形式和权限设计来指导前端如何拆分","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"技术选型","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9abdef4a10325ff41fd5d1a467449f36.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个部分不一定每一次演进都会有,在我们的这个案例中,因为我们需要将一个单体应用拆分成微前端,为了减少拆分的工作量,增加项目的可维护性,我们需要挑选一个合适的微前端框架来解决这个问题。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"说来也巧,在做出这个决定不久,公司发布了","attrs":{}},{"type":"link","attrs":{"href":"https://insights.thoughtworks.cn/thoughtworks-techradar-vol-23/","title":"","type":null},"content":[{"type":"text","text":"第23期技术雷达","attrs":{}}]},{"type":"text","text":",我们关注到了 single-spa 做为一个微前端框架已经进入到了”实验“象限。同时进入我们视野的还有以 single-spa 为基础的 qiankun。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用 single-spa 完成一个小demo后我甚至都没有了解 qiankun 就已经决定使用 single-spa 了,原因有以下几点:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生态完备,官网的文档很详细,并且有社区和官方的一系列代码库例子,同时还有上传在油管和B站的各种科普视频","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"已经能解决我们想要解决的所有问题,并且从各个渠道搜索来看没有致命缺陷","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寻求帮助响应极快,我在写 demo 时遇到了一个没法实现的需求,当晚我在官方 slack 提问,第二天一早就收到解决回复","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"任务拆解","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/11/11d85378c1fb6224c5b791204cd86350.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遇到了合适的时机,明确了需要达成的目标,完成了选型后要做的就是开动了,但是不得不再次提醒的是,我们需要做的是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"平滑演进","attrs":{}},{"type":"text","text":",所以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最重要的步骤","attrs":{}},{"type":"text","text":"其实是区分各类任务的优先级,通常我们会将任务划分成以下几类:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"必须要在短时间内完成的任务 - 这些任务如果不在这段时间内完成可能会 block 接下来的业务开发,可能会对未来的交付产生风险","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以晚点做的任务 - 这些任务不会 block 业务的开发,但是从业务和技术上来说都是应该完成的,而且越往后做这些任务所花费的资源将越多","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可做可不做的任务 - 这些任务往往是为了提升开发体验,不会直接影响整个应用或业务","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以不用做的任务 - 这些任务可以做,但是由于各种原因不在此次计划中,可以推迟到未来时机成熟后完成","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在日常开发过程中,我们需要站在一个高位往前看,确认现在的架构是否能支撑未来的业务形式和变化,及时计划架构调整和演进。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最重要的是,大多数架构的演进都是在时间不允许的情况下开始的,这时候我们需要对整个演进有一个计划,对所有的任务排列优先级,先做最重要的那一部分,不重要的延迟决定,然后舍弃一些东西。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一件重要的事情是千万不要在这个过程中自己给自己加戏,作为开发人员,大家都想把每一个技术改进做到最好,但是给自己加戏的后果往往就是啥都想做好但是最后啥也没做好。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"成功交付的前提是平滑演进。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章