旧文:DSL 初步的设计和探索 1-3

(2018.7.18 7.31 最近半年,关于 DSL 的研究已经有了我们认为的重大变革和场景突破,我们的 DSL 已经可以直接支持 Python 脚本,并通过生成中间语言 IC 的方式来最终通过运行 IC 来生成终极产出物。等这几个月写作的债还了,好好思辨和分享一下。下面的文章作为之前探索的记录。)

你知道 DSL 技术么?我认为 DSL 是适应目前多变的业务需求和需要稳定的底层系统的一座桥梁。

DSL,Domain Specific Language,领域特定语言。

DSL,是否可以简化常规开发的复杂度?到底是否存在终极武器?从 dos 年代到现在的云,开发越来越复杂。

开发一个可视化的 web 系统,来完成一些业务场景的定制,其实是非常耗费资源的。简单的例子如报表中心,每一份报表需要定义数据来源、呈现格式和计算规则、查看权限等,通过程序来实现这些功能,提供给报表制作人员的确是一个很好的注意,很多的报表类产品也是这样做的,但是我觉得这样系统的复杂度以及本身要考虑的细节非常之多。我们之前使用过 Oracle 的 BIEE,功能强大,但是用户体验很不好,造成的结果是制作一个报表的时间很长,如果需要修改的话,大部分的业务逻辑需要使用报表的人员向制作报表的人员提出,周而复始。面向大众的软件,即便是 Microsoft Office,也是需要学习曲线的,并且微软基本上也不会为用户来做功能定制,因为 Office 软件有强大的 VBA 来完成这些。

我们要做一个报表系统,但我们不是提供一个可视化的编辑系统,也不提供复杂的数据库连接工具,当然也不需要用户有太多 SQL 的知识。用 DSL,提供一种简单的接近自然语言的描述,然后通过解析,生成用户所需要的报表。我们开发这个系统的工作量主要是设计 DSL 以及解析引擎,当然一个包含主要功能的外壳还是需要的。实际上,我们已经在一些报表呈现,传统上所说的 Dashboard 上进行了实践,从概念到如今,也有四年之久,在最近的一个项目中,通过还略显粗糙的 DSL,从数据库连接到界面呈现,都自动化完成,传统意义上制作页面、切页面、写 SQL 都通过 DSL 的原理基本半自动化了。很感谢这些聪明的同事们。接下来的挑战自然越来越大。如何构造一套更加容易使用的 DSL、用在更加复杂的场景、提高解析效率等。只是用 DSL 来构造用户界面、固定的用户操作以及数据库连接,我们不太满足。

说说容易,以上这些是对于业务系统设计多年的反思,除了耳熟能详的那些中间件和越来越成熟也越来越复杂的云技术以外,系统业务逻辑的复杂和多变是事实,人工智能除了人脸识别以外可以做的事情太多了,而大数据不光是 hadoop 这六个字母,他们是真正的自动化、推理、演绎的基础。

为什么我们会想到要用 DSL 呢,最早是在制作很多产品的控台的时候,一个产品需要一个给商户使用的控台,一个给内部人员使用的控台。

然后带来以下的问题:

相似的页面的重复制作,虽然用了 MVC,但是不同项目的定制依然存在,一个新项目的流程时间还是长,对于一些查询条件和显示的改变的修改和测试耗用了很多资源。
和数据库的连接部分,需要对数据表有比较清楚的了解,数据库部分发生变化,会导致从后至前的不少修改。
和传统开发项目一样,从产品经理-项目经理-程序员-测试,整个过程耗费时间,且修改总是容易引入新的错误。
我们将控台系统分为:

web 界面
接口层
数据库
web 界面层通过调用接口层来完成业务逻辑,接口层读取数据库来进行数据库操作。

整个基于 DSL 的设计流程如下:

使用 DSL 描述 web 界面,能够描述界面包含的元素。实际上,界面模板是固定的,另外制作好,我们需要填充的是左边栏的菜单,右边栏需要哪些标签、文本框、日期选择等,以及显示查询结果的表格。
我们有一个通用的 DSL 的语法检查器和解析器。根据我们预定义的 web 规则检查语法,然后将其再解析为 html 代码片段,便于填充到界面模板中。除了 html+css+js 以外,同时生成调用接口获得数据的 php 代码。
目前的 DSL 除了 for web 以外,就是 for sql 了,通过 for sql 的 DSL 来生成 sql 语句,然后通过另外一个通用查询接口,获得数据。
这个通用查询接口输入为 sql,输出为 json 的数据集,怎么和数据库连接,具体数据库位于哪里等细节都被隐藏起来,通过配置文件来设置。
应该说,开发这样一套系统,其实工作量并不小,有以下的好处:

一旦开发完成后,用其生成的系统,因为将业务逻辑和数据库逻辑做了彻底的分离,因此很多的修改变得非常容易,真正的通过 DSL 就可以修改业务逻辑。业务程序通过 DSL 来调用 DSL 的解析器,这些都有 Restful 接口实现,也算是一种专注于自己业务逻辑的准微服务架构。
复用性。在开发第二、第三套系统的时候,我们就会享受到这样设计的便利性了,特别是前面说到,类似于控台这样基本框架一致的系统。只要后台提供接口,通过 DSL 、html 模板等,访问数据库或者访问接口。有了这样的引擎,大大减少重复的工作量。
这算是我们对 DSL 来简化业务系统流程的初步思考,之后会再举例。而我们对于 DSL 的思考这还是第一步,我们也不满足这类技术只能做相对简单的控台。

DSL 是一个好东西,之前几年零零星星的只是探索,虽然也用在了一些项目中,但是总觉得有些不完美。

首先我们定义了一个 DSL 的语言,称之为 r2,r2的解析是通过一个配置文件来定义语法,r2从某种程度上更像一个命令行的定义工具。

然后我们将 r2的解析器和编译器分别定义为 c1和c2,c1是负责解析语法,根据配置文件,配置文件定义了所有参数的名称、允许值类型、允许值范围等;c1会将解析通过的 r2命令生成 json 格式对象,然后 c2会将这个 json 根据业务场景来生成sql 语句或者 html 等。我们目前的应用场景就是 sql 的生成和 html 网站的生成。

r2 作为 dsl 的问题在于:

r2的问题在于虽然形式上和一般的命令行没有差别,但是还是需要学习的。
r2的配置文件虽然可以定义各类需要的参数和取值范围,很灵活,但是其定义还是有不低的学习曲线。
因为 r2是类似命令行的方式,所以缺乏基本语言的判断、循环、分支等,更不要说面向对象继承等这些了。
c1的问题:

配置文件随着语法表现力的需要的增强,解析复杂度陡然上升。对于 sql 和 html 这样的互相关联性不强的场景还可以应付,但是如果要把 dsl 用在业务逻辑的描述,c1已经显得有点力不从心。
c2的问题:

目前 c2在实现 sql 和 html 的生成时候,用的是基于模板的配置方式,实现上还是比较复杂的,从中间状态 json 到这些最终文件,这里是借用了一些编译的基本原理,问题是 json 描述 key-value 的确不错,但是对于语法的描述就比较弱了。
综上所述,我们继续探索新的 dsl 的实现方式和更广阔的应用场景。

ps

2015年用了半年业余时间,开发了 r2的定义和 r2c1,然后其他同事继续开发了 r2c2等,2017年,同事们将 r2这个 dsl 模式应用到一些业务场景中,这三年,对我们来说,还是走出了艰难的第一步。从2015-2016年中间,还是浪费了一些时间,当时一些同事对于 dsl 整体的理解或许还有所欠缺。所以不管之后怎么继续,先用这些文字来将思想统一。

还是选择放弃使用 Eve 作为 RESTful 框架的选择

虽然,Eve 框架很强大,也是基于 Flask,但是我还是选择放弃使用 Eve 了。几点原因:

  1. Eve 内置直接支持的数据库是 Mongo,我们已经不准备在 Mongo 上投入资源了。
  2. Eve 对额外的数据库的支持需要通过 eve-sqlalchemy,来调用 sqlalchemy 和 flask 等,这样就把问题搞的有点复杂化了。等于 flask、sqlalchemy、flask-sqlalchemy、eve、eve-sqllchemy,组合在一起,才能通过 orm 来支持各类数据库。
  3. Eve 的使用还是非常复杂的。
  4. 比较下来 flask-restful 框架就足够了。

flask-restful 框架通过将 class 直接转换为 RESTful 接口:

# Todo
# shows a single todo item and lets you delete a todo item
class Todo(Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201

用类似下面的方法就直接 ok 了

##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')

对于上面 Todo 这个 class 中数据怎么来,这不是 flask-restful 关心的,我们可以使用 flask-sqlalchemy 来完成这些和数据库操作的真正业务逻辑。

我一直觉得选择 flask 是正确的,它在很多理念的设计上也像微服务框架,每个人做好自己的事情,互相有一个约定来达到协同。

从 git 仓库看,flask-restful 的更新也还不错。

当然用了这个的话,Eve 框架很多强大的比如根据配置文件就能生成接口的能力,就要自己想办法了,初步考虑因为整个这些探索和设计都是为了自动根据数据库来生成 RESTful 接口程序,所以可以使用 DSL 来描述接口要求,将 DSL 转换为基于 flask 体系的 python 程序,来达到这个目的。

DSL,是否可以简化常规开发的复杂度?(1)

你知道 DSL 技术么?我认为 DSL 是适应目前多变的业务需求和需要稳定的底层系统的一座桥梁。

DSL,Domain Specific Language,领域特定语言。

DSL,是否可以简化常规开发的复杂度?到底是否存在终极武器?从 dos 年代到现在的云,开发越来越复杂。

开发一个可视化的 web 系统,来完成一些业务场景的定制,其实是非常耗费资源的。简单的例子如报表中心,每一份报表需要定义数据来源、呈现格式和计算规则、查看权限等,通过程序来实现这些功能,提供给报表制作人员的确是一个很好的注意,很多的报表类产品也是这样做的,但是我觉得这样系统的复杂度以及本身要考虑的细节非常之多。我们之前使用过 Oracle 的 BIEE,功能强大,但是用户体验很不好,造成的结果是制作一个报表的时间很长,如果需要修改的话,大部分的业务逻辑需要使用报表的人员向制作报表的人员提出,周而复始。面向大众的软件,即便是 Microsoft Office,也是需要学习曲线的,并且微软基本上也不会为用户来做功能定制,因为 Office 软件有强大的 VBA 来完成这些。

我们要做一个报表系统,但我们不是提供一个可视化的编辑系统,也不提供复杂的数据库连接工具,当然也不需要用户有太多 SQL 的知识。用 DSL,提供一种简单的接近自然语言的描述,然后通过解析,生成用户所需要的报表。我们开发这个系统的工作量主要是设计 DSL 以及解析引擎,当然一个包含主要功能的外壳还是需要的。实际上,我们已经在一些报表呈现,传统上所说的 Dashboard 上进行了实践,从概念到如今,也有四年之久,在最近的一个项目中,通过还略显粗糙的 DSL,从数据库连接到界面呈现,都自动化完成,传统意义上制作页面、切页面、写 SQL 都通过 DSL 的原理基本半自动化了。很感谢这些聪明的同事们。接下来的挑战自然越来越大。如何构造一套更加容易使用的 DSL、用在更加复杂的场景、提高解析效率等。只是用 DSL 来构造用户界面、固定的用户操作以及数据库连接,我们不太满足。

说说容易,以上这些是对于业务系统设计多年的反思,除了耳熟能详的那些中间件和越来越成熟也越来越复杂的云技术以外,系统业务逻辑的复杂和多变是事实,人工智能除了人脸识别以外可以做的事情太多了,而大数据不光是 hadoop 这六个字母,他们是真正的自动化、推理、演绎的基础。

下次再聊!