dsl口是什么意思啊 dsl是什么意思啊

文章目录

  • 数字式用户线路
    • 什么是DSL?
    • DSL的分类
    • 如何选择不同的DSL
    • 流畅的界面
  • 状态机
    • 状态机选择
      • 开源状态机太复杂 。
      • 开源状态机的性能不佳
    • 状态机实现
      • 领域模型
在最近的一个项目中,因为涉及到很多状态流,所以我们选择使用状态机引擎来表达状态流 。因为状态机DSL(领域特定语言)带来的表达能力比if-else代码更优雅,更容易理解 。另一方面,状态机非常简单,不像流程引擎那样华而不实 。
【dsl口是什么意思啊 dsl是什么意思啊】一开始我们选择了开源的状态机引擎,但是我觉得不好用,就写了一个能满足我们要求的精简版状态机,比较KISS(保持简单愚蠢) 。
作为可乐开源的一部分,我已经开放了可乐的源码——状态机,可以通过访问https://github.com/alibaba/COLA.获得
在实现状态机的过程中,我有幸看到了Martin Fowler编写的领域特定语言 。书中的内容让我对DSL有了不一样的理解 。
这也是有这篇文章的原因 。希望看完这篇文章,你能对什么是DSL,如何使用DSL,如何使用状态机有不一样的体验 。
数字式用户线路 在介绍如何实现状态机之前,我们先来看看Martin Fowler的《领域特定语言》一书中的DSL是什么 。开头是随着状态机的引入引入DSL 。如果你有时间,我强烈建议你看这本书 。如果没有时间,可以看看下面的内容大概了解一下 。
我来提炼一下书中的内容,带你浏览一下DSL 。
什么是DSL? DSL是一种工具,它的核心价值在于它提供了一种手段,可以更清晰地传达系统某一部分的意图 。
这种清晰不仅仅是一种审美追求 。一段代码越容易理解,越容易发现错误,越容易修改系统 。因此,我们鼓励变量名要有意义,文档要写清楚,代码结构要写清楚 。出于同样的原因,我们也应该鼓励采用DSL 。
根据定义,DSL是一种对特定领域表达能力有限的计算机编程语言 。这个定义包含三个关键要素:
语言性(language nature):DSL是一种程序设计语言,因此它必须具备连贯的表达能力——不管是一个表达式还是多个表达式组合在一起 。受限的表达性(limited expressiveness):通用程序设计语言提供广泛的能力:支持各种数据、控制,以及抽象结构 。这些能力很有用,但也会让语言难于学习和使用 。DSL只支持特定领域所需要特性的最小集 。使用DSL,无法构建一个完整的系统,相反,却可以解决系统某一方面的问题 。针对领域(domain focus):只有在一个明确的小领域下,这种能力有限的语言才会有用 。这个领域才使得这种语言值得使用 。
比如正则表达式,/\\d{3}-\\d{3}-\\d{4}/就是一个典型的DSL,解决了特定字段的字符串匹配问题 。
DSL的分类 根据类型,DSL可以分为三类:内部DSL(外部DSL,和语言工作台 。
Internal DSL是一种通用语言的特定用法 。用内部DSL写成的脚本是一段合法的程序,但是它具有特定的风格,而且只用到了语言的一部分特性,用于处理整个系统一个小方面的问题 。用这种DSL写出的程序有一种自定义语言的风格,与其所使用的宿主语言有所区别 。例如我们的状态机就是Internal DSL,它不支持脚本配置,使用的时候还是Java语言,但并不妨碍它也是DSL 。builder.externalTransition() .from(States.STATE1) .to(States.STATE2) .on(Events.EVENT1) .when(checkCondition()) .perform(doAction());External DSL是一种“不同于应用系统主要使用语言”的语言 。外部DSL通常采用自定义语法,不过选择其他语言的语法也很常见(XML就是一个常见选 择) 。比如像Struts和Hibernate这样的系统所使用的XML配置文件 。Workbench是一个专用的IDE,简单点说,工作台是DSL的产品化和可视化形态 。
DSL的三个类别从前到后是一个递进的关系 。内部DSL最简单,实现成本低,但不支持“外部配置” 。Workbench不仅实现了配置,还实现了可视化,但实现成本也是最高的 。它们的关系如下图所示:
如何选择不同的DSL 几种DSL类型都有自己的使用场景 。选择的时候,可以这样做一个判断 。
Internal DSL:假如你只是为了增加代码的可理解性,不需要做外部配置,我建议使用Internal DSL,简单、方便、直观 。External DSL:如果你需要在Runtime的时候进行配置,或者配置完,不想重新部署代码,可以考虑这种方式 。比如,你有一个规则引擎,希望增加一条规则的时候,不需要重复发布代码,那么可以考虑External 。Workbench:配置也好,DSL Script也好,这东西对用户不够友好 。比如在淘宝,各种针对商品的活动和管控规则非常复杂,变化也快 。我们需要一个给运营提供一个workbench,让他们自己设置各种规则,并及时生效 。这时的workbench将会非常有用 。
总而言之,用对地方用对溶液,不能面面俱到 。就像最臭名昭著的DSL-process engine一样,是严重滥用和过度设计的典型,是把简单问题复杂化的典型 。
最好不要无缘无故增加复杂性 。但是,简单并不容易,尤其是在大公司 。我们不仅要写代码,还要沉淀“NB技术”,最好是那种能让老板说得迷迷糊糊的技术,就像尼古拉斯在《反漏洞》里说的:
在现代生活中,简单的实践总是很难实现,因为这违背了一些人试图寻求复杂性来证明其工作合理性的精神 。
流畅的界面 写软件库的时候,我们有两个选择 。一是提供命令查询API,二是流畅的接口 。比如Mockito的API When(嘲讽列表 。Get (Anyint()) 。那么Return (\"element \")就是连贯接口的典型用法 。
一致的接口是实现内部DSL的一种重要方式 。为什么这么说?
因为Fluent的连贯性提高了可读性和可理解性,其本质不仅仅是提供API,更是一种领域语言,一种内部DSL 。
比如Mockito的API When(嘲讽列表 。Get (Anyint()) 。那么Return(“元素”)非常适合Fluent的形式 。实际上,它也是单元测试这一特定领域的DSL 。
如果把这个Fluent改成命令查询API,就很难表达测试框架的领域了 。
String element = mockedList.get(anyInt());boolean isExpected = \"element\".equals(element);
这里需要注意的是,coherent接口不仅可以提供类似于方法链和构建器模式的方法级联调用,比如OkHttpClient中的构建器 。
OkHttpClient.Builder builder=new OkHttpClient.Builder(); OkHttpClient okHttpClient=builder .readTimeout(5*1000, TimeUnit.SECONDS) .writeTimeout(5*1000, TimeUnit.SECONDS) .connectTimeout(5*1000, TimeUnit.SECONDS) .build();
他更重要的作用是限制方法调用的顺序 。比如在构建状态机的时候,我们只能在调用from方法之后再调用to方法 。构建器模式没有此功能 。
怎么做?我们可以利用Builder和Fluent接口的结合来实现 。我将进一步介绍状态机实现的以下部分 。
状态机 好了,关于DSL我就告诉你这么多 。接下来,让我们看看应该如何实现内部DSL的状态机引擎 。
状态机选择 我反对滥用流程引擎,但我不排除状态机,主要有以下两个原因:
首先,状态机的实现可以非常的轻量,最简单的状态机用一个Enum就能实现,基本是零成本 。其次,使用状态机的DSL来表达状态的流转,语义会更加清晰,会增强代码的可读性和可维护性 。
但是,虽然我们的业务场景不是特别复杂,但是Enum只支持线性状态流已经超出了范围 。所以我必须先小心 。
开源状态机太复杂 。像流程引擎一样,还有许多开源的状态机引擎 。我重点介绍了两个状态机引擎的实现,一个是Spring状态机,一个是Squirrel状态机 。这是目前github上Top 2状态机的实现 。它们的优点是功能齐全,缺点也是功能齐全 。
当然,这也不能怪开源软件的作者 。你成功地开源了一个项目 。至少支持UML状态机上列出的所有功能点 。
就我们的项目来说(其实大部分都是) 。我真的不需要那么多状态机的高级游戏:比如状态嵌套,并行,fork,join,子状态机等等 。
开源状态机的性能不佳 除此之外,还有一个我不能容忍的问题 。这些开源状态机是有状态的 。从表面上看,状态机应该自然地维护状态 。但是仔细想想,这种有状态性是没有必要的,因为有了有状态性,状态机的实例就不是线程安全的了,而我们的应用服务器是分布式多线程的,所以状态机每接受一个请求就要构建一个新的状态机实例 。
以电子商务交易为例 。用户下订单后,我们调用状态机实例将状态更改为“Order Placed” 。当用户支付订单时,它可能是另一个线程或另一个服务器,因此我们必须重新创建一个状态机实例 。因为原始实例不是线程安全的 。
这种新的每请求实例方法,不考虑功耗 。如果状态机构造复杂,QPS高,肯定会遇到性能问题 。
考虑到复杂度和性能(公司电费),我们决定自己实现一个状态机引擎 。设计目标很明确,有两个要求:
简洁的仅支持状态流转的状态机,不需要支持嵌套、并行等高级玩法 。状态机本身需要是Stateless(无状态)的,这样一个Singleton Instance就能服务所有的状态流转请求了 。
状态机实现 领域模型 由于我们的诉求是实现一个只支持简单状态流的状态机,所以这个状态机的核心概念如下图所示,主要包括:
State:状态Event:事件,状态由事件触发,引起变化Transition:流转,表示从一个状态到另一个状态External Transition:外部流转,两个不同状态之间的流转Internal Transition:内部流转,同一个状态之间的流转Condition:条件,表示是否允许到达某个状态Action:动作,到达某个状态之后,可以做什么StateMachine:状态机
整个状态机的核心语义模型也很简单,如下图所示:
注意:这里之所以称之为语义模型,是因为它使用了DSL中的术语,也可以理解为状态机的域模型 。Martin Semantic这个词用来表示外部DSL脚本代表语法,内部模型代表语义 。我觉得这个比喻挺贴切的 。
好了,状态机语义模型的核心代码如下:
//StateMachinepublic class StateMachineImpl
  • 中国用12260吨钢制造世界级豪华客轮,超过日韩
  • 吃猪皮补人皮?想多了!营养专家告诉你吃什么能让你的皮肤好起来?
  • 热水龙头哪个牌子好(水龙头加热器哪里买)
  • 养老金和养老金的区别(理解退休不吃亏)
  • 年年体检,为什么发现癌症还是晚期?原因值得深思

    推荐阅读