分类:计算机 / 架构
- oa-backend-framework项目架构设计 - 总体架构
总体思路上,我们要做一个相对独立的后端应用程序。 这个应用具有独立的分层设计,主要分层如下: 业务请求入口: 控制器层:接收来自Web API的业务请求。控制器类会注册到Web容器中。虽然API主要都写在我们的Spring Boot项目中,而不是写在这里(因为很麻烦),但仍然有部分涉及OA系统的API必须写在这里。 流程动作层:接收来自OA系统的执行流程动作的业务请求。流程动作类会注册到OA系统中。 服务层:实现业务服务,是业务逻辑的核心。 数据库层:实现对数据库的读写,支持事务管理。 应用与OA系统运行在同一个JVM中,可以在代码层面与OA系统进行交互。 应用将实现一个开发框架,这个开发框架将做到: 本地运行,方便调试:所有与OA系统非强相关的业务代码,都支持在本地运行,可以在Web容器上运行API,可以通过JUnit运行流程动作。本地运行时,可以方便地通过IDE断点做调试。 打包部署:整个项目可以打成一个jar包,然后整体上传、部署到OA系统中,方便更新和版本控制。 良好封装:框架对与OA系统的交互做良好封装,业务代码完全透明,或只需使用封装后的简单概念和接口,无需自己处理OA系统的复杂细节。 事务控制:一个业务请求(API或流程动作)的所有代码都运行在一个数据库事务中,支持自动提交和回滚。 开发规范:制定合理的开发规范,包括包结构、实体划分、服务粒度等,使业务代码更清晰有序、更可复用、更可维护。 由于本质上仍然是嵌入到OA系统中运行,应用面临如下限制: 部署后仍然要重启OA:必须重启JVM,才能使新代码生效。 三方库有限:应用只能使用OA系统自带的三方库,且必须同版本。为了避免影响OA系统,不允许向OA系统中加入其他三方库或已有三方库的其他版本。
- oa-backend-framework项目架构设计 - 起步
我们的OA系统是泛微的Ecology 9。OA在走流程审批时,经常需要在到达或离开某个流程节点时,将流程中的数据保存到某个位置,或执行其他的业务操作。如果操作很复杂,就需要用代码来实现。OA提供了机制,让我们能写Java代码,部署到OA系统中,然后在流程的指定时机,调用我们的代码。 泛微给了我们这样一个开发框架:一个Java项目,依赖Maven库com.github.liuzhenghui:weaver-ecology-core:9.00.2102.17,这个库提供了Ecology 9的类结构,能编译,但不能本地运行。这个项目主要用来写流程动作和Web API。我们需要按规定在本地写好Java类,编译成class文件,上传到OA服务器中的指定目录,重启OA,然后执行它进行测试。如果要修改,则必须重复上述过程。 这个开发框架存在以下主要问题: 调试困难:不能本地运行,只能在服务器上运行,调试只能靠打印日志。 部署困难:只能逐个上传class文件,不能打包部署。多人、长期各自上传,使得class文件的版本难以控制。更新class文件后,要重启OA才能生效,重启时间很长。这种模式,使得我们不敢多写类,大量的代码挤在一个类中。 缺少封装:业务代码必须直面OA的底层机制,必须手动处理OA的元数据、权限等机制细节,缺少机制文档的情况下很容易出错。每个业务都要重复做这样的处理。 缺少事务控制:多个步骤中间出错时,会导致数据不一致。 缺少开发规范:只提供了与OA交互的基础机制,没有给出合理的程序结构,使得业务代码的组织相当混乱。 近期,我被调入公司新成立的信息部门,负责整个公司内部信息化的技术工作。同时,一个中等规模的独立业务系统需求被提到我们部门,与OA系统强相关。因此,我决定从这里入手,正式开始着手解决已经越来越明显的软件架构问题。 我将发起一个新的软件项目oa-backend-framework,从零开始对其进行架构设计,解决上述问题,积累架构经验,并用这个需求进行实战检验。 为什么不在上述开发框架上修改?已经尝试过了,但该框架与我的设计相差很大,修改势必带来很多冲突,导致两头都做不好。 OA系统本身就有成熟的架构和规范,为什么不遵循之?① 该架构是面向OA系统本身的问题域的,与我们的问题域并不相同;② 我们缺乏足够的文档信息和支持,对其架构和规范知之甚少;③ 基于已经了解到的信息,我们认为,即使遵循之,也无法完全解决上述问题,反而会带来新的麻烦。
- oa-codeblock项目架构设计 - JS文件包含
最近在开发一个非常复杂的需求,代码量巨大,为了清晰起见,必须拆分成多个JS文件,然后有序地组合起来。开发框架目前只支持将代码写在单个JS文件script.js中,因此需要修改。 考虑历史代码兼容性和开发周期,我决定采用一种简单的实现方案:文件包含。类似于C语言中的#include,我们在JS文件中做特殊标记,表明要将另一个JS文件的内容包含进来,然后在构建时用指定JS文件的内容替换这个特殊标记,这样就实现了分开编写、组合运行。 文件包含标记:在JS文件中用如下标记表明要包含哪个JS文件: //#include: A/B/C/xxx.js 解析文件包含: 迭代解析:A包含B,B包含C,通过迭代算法可以实现正确的链式包含。算法:循环检查A中是否还有包含标记,有则对第一个包含标记(B)执行替换,此时,如果替换的内容中带进来了新的包含标记(C),则会在下一次循环中被解析。 防止循环包含:迭代解析的过程中,要记住走过的文件有哪些,一旦发现要包含的文件之前已经走过,就说明出现了循环包含,报错。 动态监听:一个脚本文件现在可以拆分为多个文件,这多个文件又随时可能修改、增加、删除,则对于持续构建模式来说,要监听变化的文件范围是动态的。每次构建一个脚本文件时,都要重新计算并监听涉及的文件。 有了文件包含机制,我们就可以获得以下好处: 将单个复杂的任务分解为多个简单的任务,提高了清晰度、可维护性。 可以实现公共代码,免于复制粘贴,提高了代码复用度。 在OA系统的代码块机制中,多个<script src="...">之间的执行顺序是随机的,通过在同一个文件中控制文件包含的顺序,我们就可以严格控制多个脚本间的执行顺序。 文件包含机制也有其缺点: IDE不识别:标记是我们自己定的,IDE并不知道要包含哪个文件、文件间的顺序等信息,也就无法提供文件跳转、准确的代码提示等功能。 作用域不隔离:文件包含本质上是将代码拼在一起,是同一个作用域,并没有实现真正的JS模块化。编写代码时需要对这一点有清晰的认识,如果需要模块化,则必须自己用IIFE等手段实现。 不太容易理解:我开发的这个非常复杂的需求,其他同事在阅读代码时,不太容易跟踪和理解同一作用域、多层包含下的执行过程。 这些缺点,未来可以通过用Webpack实现真正的JS模块化来解决。
- oa-codeblock项目架构设计 - 支持JSX和SCSS
使用代码块开发复杂的功能时,经常需要写React组件和复杂的CSS来实现界面,目前的开发框架只支持原生JS和CSS,因此书写很繁琐,需要修改。 支持编译JSX:在JS业务代码被替换到template.js之前,先用Babel库进行编译(presets为@babel/preset-react)。 支持编译SCSS:如果样式文件的文件名为style.scss,则先用Sass库将SCSS代码编译为CSS代码。style.css则无需编译。 支持JSX和SCSS语法,显著提高了React组件和样式的开发效率。
- oa-codeblock项目架构设计 - 两套环境
我们的OA系统有两套环境:生产环境,开发环境。我们需要先在开发环境上编写代码,测试通过后,再部署到生产环境。同一个功能,在两套环境上的业务逻辑部分完全相同,但是部分配置值会有差异。之前的开发框架实现只支持单个环境,因此需要修改。 环境配置文件:在script.js所在目录中,约定config_prod.js为生产环境的配置文件,config_dev.js为开发环境的配置文件。两种配置文件中通常只定义常量,有哪些常量是完全相同的,只是常量的值不同。如果常量值在环境之间无差异,则直接写在script.js中。 分环境构建并输出:构建时,使用config_prod.js+script.js+style.css构建生产环境版本的脚本文件,并输出到dist/prod/目录下;使用config_dev.js+script.js+style.css构建开发环境版本的脚本文件,并输出到dist/dev/目录下。 代码压缩与格式化:对于生产环境的脚本文件,构建时我们使用uglify-js和clean-css库对JS和CSS代码压缩,提高传输性能;对于开发环境的脚本文件,构建时我们使用prettier库对JS和CSS代码格式化,以便在浏览器中阅读和调试。
- oa-codeblock项目架构设计 - 基本构建
问题 公司使用泛微的Ecology 9作为OA系统,OA提供了代码块功能,能让我们在页面中嵌入自定义的HTML、CSS和JavaScript代码,实现自定义功能。 目前看到同事的开发模式:在后台管理中打开代码块编辑界面,编写代码(主要是JS代码和CSS代码),保存,然后刷新页面查看效果。如果需要修改,就再打开编辑界面,编写,保存,刷新。如此往复。 这种模式显然非常繁琐,除了要编写代码,还要在后台管理中做很多步骤。通常代码要改很多次才能写好,因此这种模式的效率非常低。 我决定做一个开发框架,命名为oa-codeblock项目,简化整个开发流程,提高效率。 思路 代码块中不直接写JS和CSS代码,而是只引用JS脚本文件<script src="...">,CSS代码包含在脚本文件中,也就是说,最终一个脚本文件就对应一块功能,其中同时封装了行为和样式。 开发时,引用的脚本文件在本地,可直接修改其中的代码,然后刷新页面查看效果即可,不必重复保存代码块,也就是说,开发流程被简化为“编写→刷新”2步即可。 部署时,将脚本文件上传到服务器,然后将引用的脚本文件地址改为服务器即可。 实现 基于Node.js来实现开发框架,使用NPM管理依赖。 项目目录结构: package.json:NPM配置文件。 index.js:框架核心代码,执行构建。 template.js:用于构建脚本文件的模板。 src/:源文件目录,存放业务代码。 流程布局/:存放用于流程布局代码块的业务代码。 功能名称/:存放指定功能的业务代码。 script.js:业务代码的JS部分。 style.css:业务代码的CSS部分。 建模布局/:存放用于建模布局代码块的业务代码。 ... 建模查询/:存放用于建模查询代码块的业务代码。 ... dist/:构建输出目录,Git忽略。 脚本文件模板template.js:用于构建脚本文件成品的模板,其中写好了在documentready时才执行JS代码,以及将CSS代码应用到<head>中,并留有占位符,以便将具体的JS和CSS代码替换进来。 框架核心代码index.js: 构建过程:遍历src/目录,如果发现某个目录中有script.js,则说明该目录要构建成一个脚本文件,并输出到dist/目录,src/A/B/C/script.js将输出到dist/A/B/C.js。构建时,从script.js读取JS代码,从style.css读取CSS代码,替换template.js中的占位符,然后输出。 持续构建:支持命令行参数指定一次性构建或持续构建。持续构建时,监听扫描到的script.js和style.css的变化,并在有变化时重新构建对应的脚本文件。这样,“编写”和“刷新”之间就无需手动的“构建”步骤。 实践表明,本开发框架显著提高了开发效率。