WsztRush

从下向上来开发

因为在公司接触业务比较少,更多的是在想办法做一些工具来帮助快速开发,过程中感觉做业务的思路与做工具(系统)的思路差别比较大。下面分享一下在两个模块中的思考过程。

获取数据

团队做的业务复杂无比,几百张的数据表导致有非常非常多的报表,通常的做法是这样的:

  1. iBatis(SQL)
  2. DAO+DO
  3. Manager
  4. Web+VO

几十张报表开发起来非常痛苦,而且大部分的代码实现的功能都是相似的!说到相似,那么是不是把不同的部分(取数逻辑)独立出来,相同部分进行复用即可:

配置一个数据接口只需要填写一段SQL就可以了!

其他的操作(数据库、返回数据)不需要你再关心了。YY了一下大部分的需求用SQL绝对绰绰有余,这件事情就这样结束了!图样图森破啊!!马上遇到了这样的需求:

{
    title : "xxx",
    data : [1, 2, 3, 4, 5, 6, 7, 8]
}

还有这样(一些常量):

[
    {text : 'A', value : 1},
    {text : 'B', value : 2}
]

于是火速开发了更多的功能组件来解决问题:

  1. MYSQL
  2. MYSQL-XXX
  3. JSON
  4. VELOCITY
  5. XXXX

组件越来越多,“新手”进来已经不是特别了解每个的具体含义,配置的成本也越来越大。感觉系统用起来并不想当初想的那么美好!

停下来想一下,从一开始的目标:

我要一个可以干啥干啥的功能!

在实现起来没有任何的拐弯抹角,一步到位完成之后没有考虑过:

系统有多少的灵活性、可以从哪些方面扩展?

仔细想了一想,我们对系统进行了简单的改造:

利用GROOVY脚本极大的灵活性,让系统的适应能力也有所提高!虽然花了很多业务时间来研究ACE的功能来提高编辑体验,但是还是有非常多的同一种吐槽:

写脚本的学习成本太高!

更郁闷的是来吐槽的都是“资深”JAVA开发工程师(GROOVY和JAVA不是很像么)!在现在的系统上面只需要实现一个方法:

static Ojbect execute(){
    return [1, 2, 3];
}

即可通过RPC或者HTTP获取数据(在底层已经将参数传递等东西都封装完成),作为一个程序猿对这样写一小段脚本就能实现的方式挺亲切的,不明白为啥其他的同是开发对这种方式极其的排斥,以至于我几乎没有机会去讲我夹下来要做的事情。

接下来要做什么呢?

脚本再简单也只能开发来写,那像原来只会SQL的人就用不来这个系统了。

显然不能让这种情况发生,解决办法也非常简单:

  1. 写一个通用解决问题的脚本
  2. 在执行时,将SQL、数据源信息通过参数传给对应的通用脚本执行

比如一个查询数据库的脚本如下:

static Object execute(sql, datasource){
    // 1. 根据datasource获取数据源
    // 2. 指定sql
    // 3. 返回执行结果
}

而此时用户看到的配置页面就小白很多了:

保存时会将其填写的信息以及要使用的目标脚本一起保存下来,执行时将参数传给指定的脚本执行即可(是不是有点像带参数的ln)。

将该功能开放给各个业务系统,那么就可以自己去做各自需要的小白配置页面了。

组装页面

缺前端是个普遍的问题,如果能将前端的开发简化到后端也能参与,也许能在一定程度上得到解决。

首先,把页面上的各个部分抽象成组件:组件=数据+动作+展示。比如带下拉列表的选择输入框:

  1. 数据:列表中的内容以及当前展示的内容
  2. 动作:内容发生改变
  3. 展示:可以用bootstrap等前段框架实现

在使用的时候就非常简单了(一行代码相当于原来20行左右的HTML+JS):

@input(label="仓库" name="warehouse" items=[{text:"A", value:"1"},{text:"B", value:"2"}])

直观上干了两件事情:

  1. 使用组件输入框(input)
  2. 设置一些属性(label、name、items)

那展示呢?这个显然不应该交给用户来操心,比如:

  1. 需要引入哪些CSS、JS?
  2. 对应的DOM结构应该是什么样的?

为了达到这种效果,一个组件的定义可以是这样的:

@import(
    'bootstrap.css' // 依赖的CSS文件(需要的时候也可以加JS)
)
@component
    this.width = '10px'; // 一些默认的数据

    @render  // 在render下定义渲染(有点像JSX吧)
    <ul>
        for(i in items){
            <li>${items[i]}</li>
        }
    </ul>

在做的过程中发现一些好玩的问题:

JS的资源不一定适合SEAJS来加载,因为JS之间可能有依赖关系。

渲染DOM的部分类似JSX,但更暴力:支持JavaScript的各种控制结构,但这一点是有代价的。仔细想一下:

JavaScript和HTML的语法之间天然就是彼此隔离的,除了‘<’之外。

那做到上面这种模板语法也就比较简单自然了。到这里组件就变为一个方便定制高度聚合的东西了!但是光聚合没用啊,需要把他们拼装成一个页面,需要解决:

  1. 布局(把组件拼装成一个完整的区块甚至页面)
  2. 数据交互(组件之间建立关联)

简单想了一种方案:

  1. 利用缩进来控制层次关系
  2. 利用@on来进行组件关联

举个例子:

@layout// 布局
    @input(name="a")
    @input(name="b")
    @input(name="c")
    @button(label='查询')// 查询
        @on(click)
            // 1. 获取a、b、c的数据
            // 2. 请求数据
            // 3. 更新table、page的展示
@layout
    @table // 表格
    @page// 分页

当点击查询按钮时,根据三个输入框(a、b、c)的内容查询数据并更新表格的展示。最后我们需要将该页面放到业务系统中,做法也非常的‘土’:

<script>
engine.init("page", document.body);// 将page对应的页面渲染到body下面。
</script>

渲染的位置你可以随意指定。到这里用法就说完了,下面来看如何实现:

模块很简单,我用SEAJS的方法,但是作了一些简化和定制。组件的操作包括:

  1. 使用组件(添加到父组件的children列表中)
  2. 设置属性
  3. 绑定事件

HTML的操作包括:

  1. 创建标签
  2. 设置属性
  3. 绑定事件

既然想为以后留好扩展性,那么JavaScript作为中间层应该是一个不错的选择,将对应的HTML操作可以翻译成下面几个方法(组件的也类似):

  1. create // 创建HTML标签
  2. attr // 设置属性
  3. listen // 绑定事件

为了处理层次还需要pushpop方法。为了实现这些我们需要一个翻译器:

将生成的JS文件保存到CDN上面,再结合HTTP缓存,性能应该还是可以的!我们继续,在此基础上可以扩展出更多的玩法:

现在是将结果渲染到某个DOM节点下面,那是否可以直接在Velocity中使用某个组件,像这样:

<div>
    @input(label="xxx" name="xxx")
</div>

上面是对后端Javaer敞开大门,那前端工程师是否可以得到好处,像这样:

<script type="text/engine">
@input(label="xxx" name="xxx")
</script>

因为每一步前后都没有耦合,你完全可以去扩展自己想要的东西。

总结

远古时期的程序员在实现业务时可能把:取数据、业务逻辑、渲染都放在一个代码中完成,后来逐渐分成几层来做:

  1. Model
  2. View
  3. Controller

不仅结构更清晰,而且在任何一层你都可以选择不同的方案来实现。我们在做工具时也可以参考这种思路,多分几步来做。

那什么是从下到上呢?和上面的分层的角度不一样:

  1. 越下越灵活,但门槛越高
  2. 越上越好用,但扩展起来不是很容易

在对一些场景实现工具,根据灵活度和易用性来分成几步来做,这样不同的技术背景的人都可以使用,而且最关键的是可以扩展出更多的使用场景!:-)