GithubHelp home page GithubHelp logo

arc's Introduction

Java CI Task - master Maven Package Coverage Status Quality Gate Status Bugs Maintainability Rating

通过GraphQL Schema描述业务领域,并提供一套基于GraphQL+Dgraph的开发框架,快速落地DDD

注: 基于 SpringBoot 2.x

Module

  • core: 通用代码
  • dgraph: 封装 Dgraph 数据库操作
  • graphql: 为 Spring 项目封装 Graphql 接口
  • graphql-client: 提供调用Graphql Server Api 的能力,只依赖spring web 可独立使用
  • mq: 简易内嵌消息队列
  • generator: 代码生成器。根据GraphQL Schema生成Java代码及DgraphSchema

arc-dependent

arc-generator

如何使用

可根据需求单独使用各组件. 配置文件,可以参考 full-example#application-default.properties

通用配置

# 开启zipkin监控,默认开启
spring.zipkin.enabled=true
# 设置zipkin上报地址
spring.zipkin.base-url=http://localhost:9411

1. 提供Graphql接口

1.1 添加依赖

<dependency>
    <groupId>io.github.wangyuheng</groupId>
    <artifactId>arc-graphql</artifactId>
    <version>1.6.0-SNAPSHOT</version>
</dependency>

1.2 创建schema. 默认路径为 resources:graphql/schema.graphqls 可以通过配置文件修改

schema{
    query: Query,
    mutation: Mutation
}

type Query{
    projects(name: String): [Project]
}

type Mutation{
    createProject(payload: ProjectInput): Project
}

type Project{
    id: String!
    name: String!
    description: String!
    createTime: DateTime!
}

input ProjectInput{
    name: String!
    description: String!
}

1.3 编写java代码

如果有 Consumer 订阅,graphql调用之后会发送对应的领域事件给 Consumer 进行消费

服务启动后可以

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @GraphqlQuery
    public DataFetcher<List<Project>> projects() {
        return dataFetchingEnvironment -> {
            String name = dataFetchingEnvironment.getArgument("name")
            return Arrays.asList(new Project());
        };
    }

    @GraphqlMutation
    public DataFetcher<Project> createProject() {
        return dataFetchingEnvironment -> {
            ProjectInput input = GraphqlPayloadUtil.resolveArguments(dataFetchingEnvironment.getArguments(), ProjectInput.class);
            OffsetDateTime now = OffsetDateTime.now();
            Project project = new Project();
            return project;
        };
    }

    @Consumer(topic = "projects")
    public void listener(Message<DomainEvent> record){}

    static class Project {
        public String id;
        public String name;
        public String description;
        public OffsetDateTime createTime;
    }
    static class ProjectInput {
        private String name;
        private String description;
    }
}

2. 调用dgraph数据库

可以通过docker命令在本地启动dgraph数据库

docker run --rm -it -p 8080:8080 -p 9080:9080 -p 8000:8000 -v ~/dgraph:/dgraph dgraph/standalone:v20.03.0

2.1 添加依赖

    <dependency>
        <groupId>io.github.wangyuheng</groupId>
        <artifactId>arc-dgraph</artifactId>
        <version>1.6.0-SNAPSHOT</version>
    </dependency>

2.2 配置文件

# 配置dgraph数据库地址
arc.dgraph.urls=localhost:9080

2.3 编写查询语句和java代码

SimpleDgraphRepository 提供了 save()getOne() 等常用方法,如果要进行复杂操作需要另外编写操作语句

查询语句

<dgraph>
    <var id="type">
        PROJECT
    </var>
    <var id="common">
        uid
        expand(PROJECT)
    </var>
    <query id="listByProjectId">
        query listByName($name: string) {
            listByName(func: eq(name, $name)) {
                $common
            }
        }
    </query>
    <mutation id="updateName">
        <![CDATA[
            <$id> <PROJECT.name> "$name" .
        ]]>
    </mutation>
</dgraph>

java代码

@Repository
public class ProjectRepository extends SimpleDgraphRepository<Project> {

    public List<Project> listByName(String name) {
        Map<String, String> vars = new HashMap<>();
        vars.put("name", name);
        return this.queryForList("project.listByName", vars);
    }

    public void updateName(String name) {
        Map<String, String> vars = new HashMap<>();
        vars.put("name", name);
        this.mutation("project.updateName", vars);
    }

}

3. 通过graphql-client调用graphql服务

3.1 添加依赖

    <dependency>
        <groupId>io.github.wangyuheng</groupId>
        <artifactId>arc-graphql-client</artifactId>
        <version>1.6.0-SNAPSHOT</version>
    </dependency>

3.2 编写调用语句

新建文件 resources:ql/echo.graphql

query hello($echoText: String!) {
    echo(text:$echoText)
}

3.3 编写java代码

@EnableGraphqlClients(basePackages = "io.github.wangyuheng.arcgraphqlclientexample")
@RestController
@SpringBootApplication
public class ArcGraphqlClientExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ArcGraphqlClientExampleApplication.class, args);
    }

    @Autowired
    private GitlabGraphqlClient gitlabGraphqlClient;

    @RequestMapping("rest")
    public String rest(@RequestParam(required = false, defaultValue = "Arc") String name) {
        return gitlabGraphqlClient.echo("Hello " + name).getData();
    }

    @GraphqlClient(value = "gitlabGraphqlClient", url = "https://gitlab.com/api/graphql")
    interface GitlabGraphqlClient {
        @GraphqlMapping(path = "ql/echo.graphql")
        GraphqlResponse<String> echo(@GraphqlParam("echoText") String text);
    }
}

4. 通过Generator生成代码

4.1 依赖中添加Maven插件

<build>
    <plugins>
        <plugin>
            <groupId>io.github.wangyuheng</groupId>
            <artifactId>arc-maven-plugin</artifactId>
            <version>1.6.0-SNAPSHOT</version>
        </plugin>
    </plugins>
</build>

4.2 新建配置文件, 默认路径为 resources:arc-generator.json 可以通过plugin配置修改

{
  "basePackage": "io.github.wangyuheng.arcgeneratorexample",
  "dropAll": false,
  "genStrategies": [
    {
      "codeGenOperation": "SKIP",
      "codeGenType": "REPO"
    },
    {
      "codeGenOperation": "OVERRIDE",
      "codeGenType": "API"
    }
  ],
  "ignoreJavaFileNames": [
    "User"
  ],
  "dgraphPath": "dgraph/schema.dgraph"
}

4.3 执行maven命令

mvn arc:generate

Manual

Example

背景知识

示例

voyager

playground

zipkin

整体开发流程

workflow

  1. 定义GraphQL schema,产生GraphQL.schema文件
  2. 定义dgraph schema,修改类型并定义type。
  3. 创建javaBean并指定@DgraphType、@UidField、@RelationshipField
  4. 创建 SimpleDgraphRepository 的子类声明为@Repository
  5. 创建 @Graphql类及@GraphQLQuery、@GraphQLMutation 方法
  6. 通过 http://localhost:${port}/playground 直接create方法
  7. 编写xxDgraph.xml实现query方法

Support

We would like to thank JetBrains for sharing free open-source licences of amazing tools.

JetBrains

arc's People

Contributors

b-five avatar wangyuheng avatar zhongl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

arc's Issues

graphql-client发布后无法读取ql文件

现象

打包jar发布后,调用graphql-client请求时报错

Caused by: java.io.FileNotFoundException: class path resource [ql/xxx.graphql] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/opt/target/app.jar!/BOOT-INF/classes!/ql/xxx.graphql

期望

可以正确读取并执行graphql请求

为什么不自动生成client代码?

swagger等工具可以根据服务端schema生成相应的client代码,通过jar包形式分享并调用。

graphql schema 提供了完整的schema,为什么不自动生成client端调用代码呢?

完善统一的异常处理

背景

.Graphql提供了异常输出的入口,但没有提供针对于自定义异常的处理。

现象

对于程序内的报错全部都会以500内部错误返回至客户端

期望

业务中定义的BizException中的code和message可以正常返回给客户端

在schema中增加数据绑定or事件驱动

背景

type 之间存在依赖关系时,目前只能根据方法自己在方法中定义listener。能否通过schema中描述这个管理?

方案

通过内置directive完成。目前倾向于数据驱动的方式,监听字段变更。

代码生成器生成的schema.dgraph属性不正确

现象

代码生成器生成的Dgraph表结构不正确,某个Type下的所有属性都会生成为DgraphType下的属性。name是属性,b是行为
schema如下,

type A{
    name:String
    b(String:bId):B
}

生成的表结构如下

<A.id>: string .
<A.b>: uid .

期望

schema中需要显性的区分属性和行为,这样才能更准确的生成DgraphType

<A.id>: string .

代码生成时区分属性和行为方法

背景

在graphql定义中,不需要关心属性 or 行为。 但是在实现过程中,需要考虑是返回当前对象的属性,还是触发另一个处理方法,获取返回值。所以在代码生成时,需要区分并按照模版生成对应的方法

方案

  1. 增加directive 标识出行为方法
  2. 代码生成时,识别并生成相应方法

其他

  1. dgraph type 需要ignore相应行为方法

应用启动时支持根据配置及schema.dgraph结构文件自动初始化Dgraph数据库结构

背景

首次使用或者数据库结构发生变化后需要手动在数据库执行变更语句,在代码生成及开发调试阶段,为了快速迭代,希望可以在应用启动时自动执行相关变更。

针对生产环境是否可以自动执行,理论上是可以的。Dgraph重复执行schema变更是幂等操作,不会影响数据。
但是

  1. 大数据量时的执行速度未经过测试。
  2. 从DB管理角度,数据库变更应该被严格控制,记录操作留痕,方便审计、回滚等操作

方案

Spring容器初始化时根据配置判断是否运行dgraph schema初始化脚本、是否清空已有数据。

验收

  1. 根据配置实现DB结构根据schema.dgraph文件自动变更

Graphql获取父链路中的各节点数据

现状

举例为3层实体结构 Project 1->N Milestone 1->N Task。

在Task的操作中,可以通过env.getSource()获取 Milestone,但是无法获取Project的信息。

在约定中,认为任意子节点可以获取其与聚合根的路径节点中的数据。

期望

可以在当前节点获取父节点&父节点的父节点的数据

Dgraph结果解析不支持一个List内包含多种类型

背景

在最近的设计中出现了List<Object>的结构,List内的元素类型为多种,Dgraph查询结果解析成JavaBean时将List内的对象类型错误

现象

Dgraph查询结果解析成JavaBean时将List内的对象类型解析错误

期望

Dgraph查询结果时将List内的对象类型解析正确

原因

Dgraph查询结果解析成JavaBean时将List内的对象去全部按照第一个元素的类型进行转换

增加测试覆盖率统计

背景

单测是代码质量的保障,通过展示测试覆盖率证明代码质量

方案

通过自动化CI集成jacoco运行,在readme中展示 Badge

验收

master发生变动时自动更新测试覆盖率

DgraphMapper封装Upsert方法

背景

DgraphMapper提供了querymutation方法,但是涉及到var的使用,需要同时提供querymutation。比如: 某个记录的字段+1, 需要执行

upsert {
  query {
            q(func: uid($id)) {
                v as uid
                usageCount as PROJECT.usageCount
                u as math(usageCount+1)
            }
  }
  mutation {
    set {
      uid(v) <PROJECT.usageCount> val(u) .
    }
  }
}

方案

通过扩展xml中标签实现

<upsert id="testUpsert">
    <query id="testQuery">
        query {
            q(func: uid($id)) {
                v as uid
                usageCount as PROJECT.usageCount
                u as math(usageCount+1)
            }
        }
    </query>
    <mutation id="updateStatus">
        <![CDATA[
            uid(v) <PROJECT.usageCount> val(u) .
        ]]>
    </mutation>
</upsert>

验收

通过xml完成upsert

根据GraphQL Schema生成Java代码

背景

  1. 代码中有部分胶水POJO代码,如xxInput
  2. GraphQL Schema已经详细描述了数据及类型

通过 Schema 自动生成相关代码,减少重复工作量

@GraphqlMethod 支持自定义方法名称

背景

通过 @GraphqlMethod@GraphqlQuery等annotation声明Graphql 接口方法时通过方法名称映射schema 方法名。如果schema方法名相同,无法在同一个类中声明多个方法

方案

允许自定义方法名称,默认为java方法名

验证

自定义方法名称

在单体应用中,聚合根与代码访问控制

举例为3层实体结构 Project 1->N Milestone 1->N Task

为实现高内聚的目的,操作实体时,通过对应的聚合根进行访问操作。

  1. 如何通过访问控制在代码编译层面实现相关控制?比如放在不同的package下,聚合根为public 实体对象为default。如果这样限制,无法访问内部实体的行为操作。比如更新Task的name。 如果放在同一个class中?通过内部类?
  2. 如果存在多层级时,如何控制?如何体现层级、兄弟节点的关系?
  3. 如果Project作为聚合根,会不会变成定义的全部实体全部被划分到一个边界上下文内?

Nest-Based Access Control?

代码生成器无参数方法不会生成graphqlmethod

现象

schema中设置无入参的类型,如 members: [User]。生成的代码中作为属性被处理

期望

生成graphqlmethod。

其他

如果存储实现时在一张表中,可以通过env 获取 source 再 getXxx() 方式减少查询

支持graphql中type的方法

一个实体在graphql中对应一个type,而实体的无参方法在graphql中会被当成属性,如:

type Project {
    delete: Boolean
}

deleteProject的一个方法,对应代码生成器也无法正确生成handleDelete 方法

支持代码定义GraphqlSchema

背景

GraphqlSchema通过 schema.graphql 定义。在使用过程中,希望在编译阶段动态生成schema。

方案

通过 GraphQLCodeRegistry 实现

增加GraphqlClient调用方法

背景

模块提供了graphql接口作为服务端接口,不同模块如果独立部署,在调用时需要发送graphql请求

方案

  1. 封装发送graphql请求的方法
  2. 考虑通过模块提供的api jar包进行序列化及封装

验收

不同模块间通过client方法调用

项目中使用@EnableFeignClients后Graphql方法注册失败,无法调用

背景

项目引用arc-graphql并通过feign调用3方httpapi

现象

项目中增加 @EnableFeignClients 后 graphql无法调用

期望

能够同时使用 arc-graphql 及 feign

原因

feignClient 在注册过程中 FeignContext 会 调用父类NamedContextFactorycontext.refresh(); 方法,并发送事件 ContextRefreshedEvent

代码生成器同时生成GraphqlMethod和字段

背景

通过Generator生成代码时,如果是GraphqlMethod(返回值是另一个type)则不会生成field & setter & getter。
为了存储方便,有时会把type作为json字段冗余存储。此时期望在GraphqlMethod中通过source.getXxx()获取相关数据

方案

  1. GraphqlMethod 仍生成 field & setter & getter

其他

考虑是否通过 direction 区分graphqlMethod & dataType

提供参数值校验directive

背景

graphql 提供了类型及非空进行校验,但是实际业务中存在对参数值校验。如 Int类型 min=0 max=100

目标

通过directive对参数值进行校验并返回指定异常格式

代码生成时支持增量生成

现状

只能覆盖生成的java文件,如果有生成的文件发生了变更,只能覆盖or跳过

目的

  1. 增量生成,可以减少钩子类,让结构更清晰。
  2. 结合TDD,可以更清楚的感知自己需要完成的工作。

其他

只需增量覆盖handle方法

GraphqlMethod参数自动序列化

背景

GraphqlMethod Function 基于 DataFetcher,统一接收参数 DataFetchingEnvironment后序列化为相关Input Bean。可以统一完成此过程,类似 Spring Web 中的 @RequestBody

提供自定义directive方式

背景

graphql允许directive。目前arc自动启动graphql api

方案

TODO

其他

编写自定义directive 应用启动后生效

多请求时拆分zipkin监控

背景

  1. GraphQL可以合并多次请求 or 存在层级关系的请求
  2. zipkin目前只有整个http请求的耗时监控

方案

  • 细分每一个dataFetcher的耗时监控
  • 作为一种特殊的interceptor?

方案

一次请求可以查看多个dataFetcher的耗时

maven插件封装代码生成功能

背景

目前代码生成通过单元测试进行调用,使用方必须依赖generator模块,进而依赖所有模块

方案

  • 通过 maven 插件 封装代码生成功能

验证

使用插件生成代码,无需依赖generator模块

schema migration

如何描述schema的变化,以及支持schema migration?

Dgraph的SimpleDgraphRepository提供的getOne无法使用

背景

Arc提供了SimpleDgraphRepository,用于一些简单的查询,目前getOne无法使用

现象

查询报错
生成的查询语句
image
结果
image

期望

能够正常使用getOne查询出结果

原因

expand(_all_)会查询当前层级下所有的属性,在同层级内出现了多句expand(_all_),导致同一字段查询多次然后报错

MQ中Message<DomainEvent>增加入参获取方式

背景

MQ通过Message传递参数,但是无法快速获取入参。

方案

通过ExecutionInput解析参数较为复杂

    private Optional<Argument> getArgumentByName(ExecutionInput input, String name) {
        return parser.parseDocument(input.getQuery())
                .getDefinitionsOfType(OperationDefinition.class).stream()
                .map(OperationDefinition::getSelectionSet)
                .map(it -> it.getSelectionsOfType(Field.class))
                .filter(Objects::nonNull)
                .flatMap(Collection::stream)
                .map(Field::getArguments)
                .filter(Objects::nonNull)
                .flatMap(Collection::stream)
                .filter(it -> it.getName().equals(name))
                .findAny();
    }

考虑通过新属性传递,或者提供相关工具扩展

验收

MQ consumer 可以快速获取入参

SimpleRepository提供通用删除函数

背景

Arc 通过 SimpleDgraphRepository 提供了基于DomainEntity的Dgraph数据库的基本操作

希望扩展实现删除功能,简化相关操作,包括 删除 & 软删除(数据状态不可查询,但是仍存储于数据库中)

方案

注: 软删除如果涉及到通用字段,需要考虑 代码生成(#5

// TODO

验收

  1. 通过调用通用函数实现删除功能

关于graphql schema与子域的思考

graphql官方的推荐实现是共用一张schema视图。
但是如果通过schema来描述领域模型,需要考虑如何描述限界上下文子域
如果为了解耦,会考虑每个领域内维护自己的schema。但是在单体应用中,不同领域如何分开管理?可以拆分schema,然后在运行时聚合。但是,是否需要命名空间,避免名称重复?
反之,如果采用微服务的方案,按照领域(甚至子域)划分不同服务,可以保障每个服务通过schema维护自己的领域模型。但是损失来组合查询的能力。比如为在一个页面同时查询项目列表与当前登陆用户信息,分属不同服务,如果通过一次查询获取?是损失一次查询的能力,还是通过代理层区分不同领域,再去不同服务查询数据后聚合?
此外,如果是单体应用,代码生成器如何保障不同自域采用不同的生成配置及策略?是否需要 Query & Mutation 作为根?

根据GraphQL Schema生成Dgraph Schema

概念:

  1. Dgraph Schema: Dgraph数据库结构化语句,类似mysql中的DDL
  2. predicate: Dgraph的数据库字段,一个predicate可以被多个type使用
  3. domainClass: Arc框架中定义javaBean类型,用于反序列化

背景

为了提升开发效率,加快schema领域模型验证。
希望在根据GraphQL Schema自动生成Java代码(#5) 的同时,可以自动生成数据库结构语句,并可根据需要自动执行数据库初始化(#7)

方案

解析GraphQL Schema后,根据转换逻辑生成Dgraph Schema

注意Arc框架限制

  1. DB结构中拥有框架依赖通用predicate,如 domainClass
  2. 为了解决predicate跨type定义问题,dgraph中的predicate命名增加type为前缀

验收

  1. 根据 https://github.com/YituHealthcare/Arc/blob/master/sample/full-sample/src/main/resources/graphql/schema.graphqls 自动生成Dgraph Schema
<domainClass>: string .
<dgraph.graphql.schema>: string .
<PROJECT.name>: string .
<PROJECT.description>: string .
<PROJECT.category>: [string] .
<PROJECT.createTime>: dateTime .
<PROJECT.milestones>: [uid] .
<MILESTONE.name>: string .
<MILESTONE.status>: string .
<USER.name>: string .
type Project {
domainClass
dgraph.graphql.schema
PROJECT.name
PROJECT.description
PROJECT.category
PROJECT.createTime
PROJECT.milestones
}
type Milestone {
domainClass
dgraph.graphql.schema
MILESTONE.name
MILESTONE.status
}
type User {
domainClass
dgraph.graphql.schema
USER.name
}

生成的schema.dgraph允许增加变更

背景

通过 generator 生成schema.dgraph 同时希望增加一些通用字段 如 <is_delete>: bool @index(bool) . 如果再次生成会覆盖手动新增的字段

方案

// TODO

验证

重新生成schema.dgraph 不影响手动新增的字段

Dgraph初始化并且drop-all时表结构丢失

背景

#7 提供了Dgraph初始化操作

    {
      "name": "arc.dgraph.init",
      "type": "java.lang.Boolean",
      "description": "init dgraph schema by dgraph define.",
      "defaultValue": "false"
    },
    {
      "name": "arc.dgraph.drop-all",
      "type": "java.lang.Boolean",
      "description": "drop all data! & schema! when init.",
      "defaultValue": "false"
    }

现象

同时开启初始化及drop-all操作时,表结构Schema丢失

arc:
  dgraph:
    urls: localhost:39081
    init: true
    drop-all: true

期望

只删除数据,表结构按照schema完成初始化

原因

  1. 同时执行时,dgraph会先变更表结构,再执行drop-all操作,导致表结构被删除

其他

dgraph4j 提供了更细粒度的drop控制

生成代码在不同系统下格式不同

现象

团队开发人员使用不同系统进行开发,再调用 generator 生成代码时因为格式不同导致diff

期望

不同系统下生成代码格式一致

详情

  • 换行不同
  • tab->space数量不同
  • graphqlMethod 格式串行

Dgraph反序列化工具类型嵌套bug

背景

Dgraph反序列化工具JSONFieldDeserializerUtil#getJsonMap 未判断类型循环嵌套的情况

期望

添加循环嵌套的判断,已经处理过的类不进行处理

Thanks for the Dgraph project

Cant thank you enough for writing a project on Dgraph graph ql Java . I am working on something similar . Not going to look at your code until I work something out but one good thing is atleast there are people working on this stack . There's a scarcity of good resource for this stack .

提供FilterDSL简化查询

背景

在进行列表查询时,搜索条件不是稳定,可能会产生变动。是否提供Filter DSL 允许适用房自定义组装搜索条件? 目的

  1. schema 稳定,相关变动无需更新schema
  2. 定义DSL及解析规则后,无需额外实现相关变更

参考

  1. neo4j - graphql 实现的filter dsl

移除lombok依赖

背景

未简化代码使用Lombok,提升了使用成本(需要安装idea plugin)

方案

  1. 移除lombok依赖
  2. 修改相关代码

验收

不影响已有功能

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.