上篇文章《DDD探险——基于GraphQL+Dgraph实践》中提到了一种假设
如果前端能通过领域模型进行数据操作,能否通过
GraphQL Schema
同时描述领域模型、API接口以及数据库结构?
基于此假设提供了Arc
框架,同时采用Dgraph作为数据存储。
既然我们已经有了Schema,能否基于这个描述自动生成系统实现?通过降低开发成本、提升效率,继而达成快速迭代、试错领域模型的目的。
尝试通过代码生成器来完成这个假设。
1. GraphQL Schema -> Dgraphql Schema
1.1 概念:
Dgraph Schema: Dgraph数据库结构化语句,类似mysql中的DDL
predicate: Dgraph的数据库字段,一个predicate可以被多个type使用
domainClass:
Arc
框架中定义javaBean类型,用于反序列化
1.2 方案
解析GraphQL Schema
后,根据转换逻辑生成Dgraph Schema
注意Arc
框架限制
DB结构中拥有框架依赖通用predicate,如 domainClass
为了解决predicate跨type定义问题,dgraph中的predicate命名增加type为前缀
1.3 注意
需要框架提供根据
Dgraph Schema
自动初始化数据库的能力
2. GraphQL Schema -> JavaCode
2.1 JavaPoet
JavaPoet is a Java API for generating .java source files.
通过JavaPoet可以方便、详细的描述Java源文件并生成。
看官方issue发现很多人希望提供快速生成getter、setter方法的方式,官方并未采纳,给出的回复是
Basically we're a tool for generating exactly what you tell us and not a tool for inferring code to generate. You can write a static method or helper class which can produce a field, getter, and setting in one shot onto a TypeSpec.Builder.
这个回复也明确了JavaPoet的定位。其实生成相关方法的方式非常简单
提供一个生成Builder的示例,基本涵盖了全部定义
2.2 实现
基于 graphql-java 解析 Graphql Schema
,通过 javapoet 按照Arc
约束生成相关java文件
Graphql Enum -> dictionary
Graphql input -> input
Graphql type -> type、api、repository
2.3 注意
通过只提供interface方式,避免修改生成的代码。保障每次schema变更后都可以重新生成。
如果存在需要修改生成代码的场景,通过配置方式跳过相关Java源文件生成。
3. Maven Plugin
生成逻辑通过上述定义完成后,如何触发生成的动作?Maven插件是一个不错的选择。
只需要继承AbstractMojo类并在Override execute()中调用相关生成方法即可。这里暂不展开如何开发Maven插件。
然后再命令行执行
mvn arc:generate
即可按照配置及规则生成相关代码。也可以通过参数决定只生成java代码,不生成
Dgraph Schema
mvn arc:generate -Dtarget=java
4. 效果
4.1 创建schema. 默认路径为 resources:graphql/schema.graphqls
4.2 新建配置文件, 默认路径为 resources:arc-generator.json
4.3 执行命令行
4.4 代码生成
在聊清楚schema后,只需要执行一行命令,然后编写一个实现类即可完成接口服务的开发。在schema修改后,重复执行这个过程。
5. 缺陷
5.1 GraphQL描述力不够。只描述来数据结构与类型,没有描述数据存储可能会用到的主键、索引、缓存等信息,以及DDD中的限界上下文(BoundedContext)、聚合根(AggregateRoot)等概念
可以通过自定义directive实现相关扩展定义,比如:
但是这个时候QL
已经变成了DSL
,需要考虑额外的学习、推广成本
5.2 生成的代码和实现代码混合,如何进行code review?
schema和生成的代码先提交一次PR,对schema进行review。之后的实现代码再单独提交PR
5.3 Dgraph更新时数据如何处理?
已经上线后无法自动迭代。数据版本迁移不应属于开发过程
5.4 仍需编写实现类(生成的代码是否允许编辑)?
理想情况是完全通过代码生成完成相关实现。但是实际业务并不是简单的crud,如果通过schema描述复杂业务,就会发现本质上是通过schema来编写另一种java代码。所以只生成interface,需要开发者补全相关实现。让schema专注于描述DDD。不排除提供生成简单crud实现的功能,在修改了相关实现类后,再通过配置SKIP_IF_EXISTED跳过相关实现
5.5 自动化生成后发现无事可做,不可替代性如何保障?
-_-#