设计DSL并没有想象中那么高深,它分为三个层级,难度从易到难。
从最简单的开始,逐步深入到原理层。
第一层:用通用语言“伪装”DSL(内部DSL)
这是最简单、最安全的方式,不需要自己写解析器,直接利用现有语言的特性。
方法:使用方法链(Fluent API)或Builder模式,让代码读起来像英语句子。
Java示例(模拟一个条件查询):
List<User> result = Query.select("name", "age") .from("users") .where("age").greaterThan(18) .and("city").equals("Beijing") .execute();虽然它是Java代码,但读起来已经像一门专门查数据的DSL了。这招在构建测试框架(如Mockito)和配置工具时极其常用。
优点:不用学新语法,IDE自动补全,安全可靠。
缺点:受限于宿主语言的语法规则(比如括号、分号去不掉),无法做到极致的简洁。
第二层:解析结构化配置(外部DSL的轻量版)
如果你希望DSL不是代码,而是存成纯文本的配置文件(比如.txt或.yaml),方便非开发人员修改,那就需要写一个解释器(Interpreter)。
场景:设计一个“简单规则引擎”,比如电商促销规则:
IF 订单金额 > 500 THEN 打9折。做法:不用复杂工具,直接用字符串匹配或词法分析库(如ANTLR)。
极简手写示例(Python):
rule = "price > 100 AND brand == 'Apple'" # 你写代码把这条字符串拆成: # {"left": "price", "op": ">", "right": 100, "logic": "AND", ...} # 然后判断当前商品是否满足条件。这就是DSL解析的最朴素形态。
进阶工具:当规则变复杂时,建议用ANTLR(Java)、Peg.js(JavaScript)或Parsec(Haskell)这类解析器生成器,你只需定义语法规则(类似正则的增强版),它们能自动生成解析代码。
第三层:完整的外部DSL(设计一门“小语言”)
这是最高阶玩法,你需要定义完整的语法(Syntax)和语义(Semantics),甚至自己写词法分析器(Lexer)和语法分析器(Parser)。
经典案例:Elasticsearch的查询DSL(基于JSON)和PromQL(Prometheus的监控查询语言)。
核心流程(这就是编译原理的简化版):
词法分析:把
(age > 18)拆成一堆Token(左括号、标识符age、操作符>、数字18、右括号)。语法分析:根据你定义的语法规则(比如
比较表达式 -> 标识符 操作符 数字),把这堆Token组装成一棵树——抽象语法树(AST,Abstract Syntax Tree)。执行/编译:遍历这棵树,执行具体的逻辑(比如调用数据库查询)。
这里的核心产出就是AST。你看下面这段DSL:
filter: { range: { age: { gte: 18 } } }解析后生成的AST大致是:
有了这棵树,计算机就知道“需要比较年龄字段,条件是大于等于18”。
进阶实战:在Spring Boot里实现一个极简DSL(思路)
假设你要实现一个权限校验DSL:@Permission("user:delete OR admin:all")。
定义语法:支持
AND、OR、括号。写解析器:用Java的
Pattern先分词,再通过递归下降解析(一种手写解析方式)构建AST。执行:校验当前用户权限列表是否满足这棵树的逻辑。
你会发现,整个过程中最难的不是执行,而是“优雅地处理语法错误”(比如少个括号时给出友好的报错),这是工业级DSL最考验功底的地方。
给你一个“该不该造DSL”的判断标准
虽然造DSL很酷,但别为了炫技而造。记住这条铁律:
如果某类配置或规则,需要非程序员(如运营、测试、运维)频繁修改,且修改频率远高于代码发布频率,那么DSL就是最佳方案。否则,直接用代码写死或使用现有工具即可。