写点什么

低代码平台中的 GraphQL 引擎

作者:canonical
  • 2023-05-15
    北京
  • 本文字数:9704 字

    阅读完需:约 32 分钟

GraphQL是 Facebook 开源的一种面向对象图结构的查询语言。相比于 REST 服务调用方式,GraphQL 提供了可以由调用者控制的、强大而灵活的数据重组能力,而所谓的低代码平台,其技术的基本价值也正在于将原先必须由程序员编码实现的功能,通过抽象和封装,以一种有限组合的方式提供给上层应用,因此可以想见,在低代码平台中 GraphQL 可以与其他组合机制相互促进,实现价值的最大化。在本文中,我将基于可逆计算原理对 Nop 平台中 GraphQL 引擎的设计进行一些理论分析,并介绍我们对 GraphQL 所做的一些扩展。

一. 对象分解

为了进行有效的管理和控制,对于任何一个复杂的系统,我们必然都会把它分解为很多相对独立的子部分。在面向对象技术的语境中,这表现为先有对象,再有方法,我们会先把业务系统划分为一系列的业务对象,然后围绕着这些业务对象来组织业务方法。也就是说,先有名再有实,给对象分配唯一的名称之后,我们可以将整个系统的概念空间划分为多个不相交的子空间



这种分解一般我们称之为纵向分解(主分解维度),它表现为在不同的层面,我们可以识别出类似的对象分解结构,例如在存储层我们识别出订单和客户表,而在界面层我们同样会识别出客户和订单对象的相关页面。


在低代码平台中,这种不同层面的相似性显然是一种可以被利用的信息。一般来说,低代码平台会采用约定大于配置的方式将底层的业务表(业务对象的持久化表示)按照缺省命名约定透出为 Web 层的某种资源链接(Endpoint),并为业务对象配备一组缺省的增删改查页面。也就是说,只要知道业务对象名,我们基本可以猜测出前后端代码所对应的访问路径、代码位置等信息。


<img title="" src="graphql/generative-flow.png" alt="" data-align="center">


Nop 平台的具体做法是以数据模型为基础,自动生成实体定义、SQL 表定义、GraphQL 类型、前端页面等。以部门表 Department 为例,缺省情况下我们会生成一个 GraphQL 类型 Department,并为主外键关联生成对应的属性,例如 parent 和 children。如果增加了 connection 标签,我们还会为关联对象生成分页获取所对应的属性,例如 usersConnection 通过类似Relay Cursor Connection的方式来分页返回属于指定部门的用户。缺省情况下业务对象会自动继承 CrudBizModel,所以它会自动生成 GraphQL 入口操作


extend type Query{    Department__get(id:String!): Department    Department__batchGet(ids:[String!]): [Department]    Department__findPage(query:QueryBeanInput): PageBean_Department    ...} extend type Mutation{    Department__save(data: DepartmentInput): Department    Department__delete(id:String!): Boolean    ...}
复制代码


Nop 平台可以看作是一个自动化的软件生产线,它的输入是用户需求(以 Excel 文档的形式表达),输出是可运行的应用系统,主要通过系统化的增量式代码生成方案来实现生产线的运转。这其中,GraphQL Schema 是根据 Meta 元数据定义和 BizModel 业务模型定义自动生成的一种中间产物,我们并不会手工编写 GraphQL 类型定义,在编写业务代码的过程中也不需要具有任何 GraphQL 相关的知识,不需要实现 GraphQL 特有的 DataFetcher、DataLoader 等接口。具体的技术细节在“差量流水线”一节中会有更详细的介绍。另外可以参考以下文章:


数据驱动的差量化代码生成器

二. Gather and Scatter

软件框架的作用在于对系统逻辑进行某种规范化的组织,对于底层引擎而言,最基本的组织能力是 Gatter(从系统各处收集信息)和 Scatter(将集中管理的信息分发到系统各处)。


Nop 平台以业务对象(BizObject)为主分解维度来组织业务逻辑,它所要解决的一个基本问题是:如何构造 BizObject 以及如何将通用的知识应用于 BizObject?



Nop 平台支持从 ProCode 到 LowCode 再到 NoCode 的平滑过渡。首先,它内置了一些开箱即用的通用模型,例如实现增删改查的 CrudBizModel。然后在开发实施阶段,我们可以通过 LowCode 技术,基于业务模型自动生成业务相关的一系列代码,并在这些生成代码的基础上进行增量化的调整。最后,在系统运行阶段,客户可以通过可视化设计器等以 NoCode 的方式定制系统已有的功能(修改、删除)或者增加新的功能。


NopGraphQL 引擎在初始化的时候会利用 IoC 容器的动态扫描能力发现所有标记了@BizModel注解的 bean,并把它们按照 BizObjName 配置进行归类合并。例如


@BizModel("NopAuthUser")public class NopAuthUserBizModel extends CrudBizModel<NopAuthUser>{    @BizMutation    public void changeSelfPassword(@Name("oldPassword") String oldPassword,                                   @Name("newPassword") String newPassword) {        ...    }}

@BizModel("NopAuthUser")public class NopAuthUserBizModelEx{ @BizMutation public void otherOperation(){ ... }
@BizMutation @Priority(NORMAL_PRIORITY-100) public void changeSelfPassword@Name("oldPassword") String oldPassword, @Name("newPassword") String newPassword) { ... }}
复制代码


NopAuthUserBizModel 和 NopAuthUserBizModelEx 的 BizObjectName 都是 NopAuthUser,它们的方法会叠加在一起共同生成 NopAuthUser 业务对象上的方法。当出现同名的函数时,会按照@Priority优先级配置选择优先级更高的实现。如果优先级相同且函数名相同,则会抛出异常。


NopGraphQL 引擎在构造 BizObject 的时候还会检查 xbiz 扩展模型,我们可以通过在 NopAuthUser.xbiz 模型文件中增加方法来扩展 BizObject,这个模型文件可以在线更新,更新后会即时起效,无需重新初始化 GraphQL 类型定义。xbiz 文件中定义的方法优先级最高,它会自动覆盖 JavaBean 中定义的业务方法。


如果把对象名相同的 BizModel 看作是对象的一个切片,则 NopGraphQL 引擎相当于是在系统初始化的时候动态收集这些对象切片,然后像 docker 镜像一样把它们叠加在一起,构成完整的对象定义。在运行时,最上层的 xbiz 切片可以被动态修改,并覆盖下层切片的功能。


BizModel 切片的概念有些类似于游戏开发领域中的 Entity Component System (ECS)模式,只是它累加的是动态行为而不是局部状态。


与 Gather 对偶的能力是 Scatter:我们经常需要做一些全局规则的抽象,需要将某些公共知识自动推送到不同的业务对象中。NopGraphQL 主要通过 AOP 机制和元编程机制来实现信息的分发:


  1. 公共的机制可以作为 AOP 拦截器作用于符合条件的业务方法上

  2. xbiz 文件中可以通过 XLang 中通用的 x:gen-extends 元编程机制动态生成方法定义。也可以使用外部的 CodeGenerator 来生成代码。

三. CRUD 模型

在一般的业务开发中,CRUD(Create/Read/Update/Delete)操作往往是不同的业务对象中相似度最高的部分,因此有必要对它们进行统一抽象。NopGraphQL 使用设计模式中的模板方法(Template Method)模式提供了通用的 CRUD 实现:CrudBizModel。具体使用方法是从 CrudBizModel 类继承,然后可以通过实现 defaultPrepareSave/afterEntityChange 等函数补充定制逻辑。参见代码


CrudBizModel.java


ObjMetaBasedValidator.java


NopAuthUserBizModel.java

3.1 元数据驱动

CrudBizModel 采用的是元数据驱动的实现方式,它会读取 xmeta 配置文件中的内容,内置实现了数据验证、自动初始化、级联删除、逻辑删除、数据权限等多种常见需求,所以一般情况下只需要调整 xmeta 和 xbiz 配置文件,并不需要编写定制逻辑。


  1. 数据验证:类似于 GraphQL 的输出选择,NopGraphQL 可以对输入字段进行选择性验证和转换,这体现了输入和输出的对偶性


   validatedData = new ObjMetaBasedValidator(bizObjName,objMeta,context)                       .validateForSave(input,inputSelection)
复制代码


  1. 自动初始化:在 meta 中可以配置字段的 autoExpr 表达式,更新或者修改的时候可以根据该配置自动初始化字段值。autoExpr 表达式可以根据数据模型中的 domain 配置自动生成。

  2. 自动转换:根据 meta 中配置 transformIn 表达式,对输入的属性值进行适配转换。transformIn 表达式可以根据数据模型中的 domain 配置自动生成。

  3. 级联删除:标记为 cascade-delete 的子表数据会随着主表数据的删除一并删除,而且会执行子表对应的 BizObject 业务对象上的定义的删除逻辑。

  4. 逻辑删除:如果启用 delFlag 逻辑删除标记字段,则底层的 ORM 引擎会自动将删除调用转换为修改 delFlag 的操作,并且对所有查询都自动应用 delFlag=0 的过滤条件,除非明确在 SQL 对象上设置 disableLogicalDelete 属性。

  5. 数据权限:所有读取到的实体记录都会自动验证是否满足数据权限要求。

3.2 复杂查询

CrudBizModel 对于复杂查询提供了三个标准接口


PageBean<OrmEntity> findPage(QueryBean query, FieldSelectionBean selection);List<OrmEntity> findList(QueryBean query);OrmEntity findFirst(QueryBean query);
复制代码


  1. findPage 会根据查询条件返回分页查询结果,分页逻辑可以采用 cursor+next page 的方式,也可以采用传统的 offset+limit 的方式。selection 对应于前端调用时传入的返回字段集合。如果没有要求返回 total 总页数,则 findPage 内部会跳过总页数查询,如果没有要求返回 items 数据列表,则实际会调整真正的分页查询本身

  2. findList 根据查询条件返回列表数据,如果没有设置分页大小,则按照 meta 上的配置选择 maxPageSize 条记录。

  3. findFirst 返回满足条件的第一条记录。


QueryBean 类似于 Hibernate 中的 Criteria 查询对象,支持复杂的 and/or 嵌套查询条件以及排序条件。QueryBean 可以由前台直接构造,在送到 dao 中真正执行之前它会经历如下处理过程:


  1. 验证查询条件中只包含标记为 queriable 的字段,且查询算符在每个字段的 allowFilterOp 集合中,缺省只允许按照相等条件进行查询。例如配置用户名支持模糊查询


   <!-- 支持按照相等或者模糊匹配的方式进行查询,缺省前端生成的控件为模糊查询 -->   <prop name="userName" allowFilterOp="eq,contains"              xui:defaultFilterOp="contains"/>  
复制代码


  1. 追加数据权限过滤条件,例如过滤只能查看管理单位是本单位的数据。

  2. 增加按主键字段排序的排序条件。分页查询时如果不进行排序,则因为数据库并发执行的原因,返回的结果集合可能是随机的。所以所有分页查询原则上都应该具有排序条件,确保排序后的分页顺序一致。


QueryBean 利用底层的 NopOrm 引擎的能力,可以很自然的支持关联对象查询,例如


<eq name="manager.dept.type" value="1" />
复制代码


表示按照 manager.dept.type = 1 条件进行过滤,自动根据manager_id关联对应的部门表。


如果底层的 ORM 引擎不支持关联查询,也可以自行编写一个 QueryTransformer 接口来对 QueryBean 进行变换,例如将上面的等于判断转换为一个子查询


o.manager_id in (select user.id from User user, Dept dept       where user.dept_id = dept.id and dept.type = 1)
复制代码


在前端,为了通过以表单方式构造复杂查询条件,我们做了如下约定:


字段名格式为: filter_{propName}__{filterOp}
复制代码


例如 filter_userName__contains表示按照 contains 运算符对 userName 字段进行过滤。对于 filterOp 为 eq(等于条件)的情况,可以省略 filterOp 的部分,例如 filter_userId 等价于filter_userId__eq

3.3 this 指针:知识的相对化

GraphQL 中定义的操作名是全局名称,例如 query{ getUser(id:3){ id, userName}}查询中用到的 getUser 方法需要在整个模型中具有唯一性,这一要求对于复用代码来说是不利的。


NopGraphQL 中实现 CRUD 时只需要继承 CrudBizModel 基类,对外暴露的 GraphQL 操作名由对象名和方法名拼接而成。


class CrudBizModel<T>{    @BizQuery    @GraphQLReturn(bizObjName="THIS_OBJ")    public T get(@Name("id")String id){       ....     }}
@BizModel("NopAuthUser")class NopAuthUserBizModel extends CrudBizModel<NopAuthUser>{
}
复制代码


上面的示例中,NopGraphQL 引擎会自动生成一个 query 操作NopAuthUser_get,并且它的返回类型为THIS_OBJ,这意味着它会被替换为当前对象所对应的 BizObjName,即 NopAuthUser。


注意到,采用这种实现方案,我们可以针对同一个实现类提供不同的 GraphQL 类型。例如


@BizModel("NopAuthUser_admin")public NopAuthUserAdminBizModel extends CrudBizModel<NopAuthUser>{
}
复制代码


同样是从CrudBizModel<NopAuthUser>继承,但是因为 BizModel 注解中提供的 bizObjName 为NopAuthUser_admin,则 get 方法返回的字段集合可以有别于普通的 NopAuthUser,对后台调用的权限要求也可能不一样。


也就是说,对象上的方法名是一个局部名称,它的语义是相对于 this 指针而定义的。在不具备全部知识的情况下,我们可以基于相对知识编制相当复杂的逻辑,然后注入不同的 this 指针,就可以改变整个一组调用的具体含义。这实际上是面向对象最基本的设计原理。


面向对象技术创造了一个特殊的名---this 指针,它是一种约定了的固化了的局部名称。使用 this 指针使得我们区分了领域(domain)的内外。在 domain 外对象可以有各种称谓,而 domain 内我们直接通过 this 直接指代当前对象。

代码本身只是一种形式表达,它的具体含义需要一个诠释的过程才能确定。基于对象指针的调用形式直接导向了诠释的多样化:只要注入不同的 this 指针,就可以提供不同的诠释。


在前台的实现中,我们使用了类似的策略:前台脚本根据方法名的后缀自动判断方法签名,例如所有以_findPage为后缀的方法它的缺省签名都是


XXX_findPage(query:QueryBeanInput):PageBean_XXX
复制代码

四. 框架无关的设计

使用传统的 Web 框架在编写业务代码的时候总是不可避免的会用到框架特有的一些环境对象,例如 HttpServletRequest 或者 SpringMVC 中的 ModelAndView 等。这些对象都和框架特定的运行时环境强相关,使得我们的代码与某个运行时环境绑定,难以应用到多种使用场景中。最明显的,一个为在线 API 调用编制的服务函数,一般无法直接作为消息队列的消费者来使用。我们必须抽象出一个额外的层次:Service 层,然后在 Service 层的基础上分别包装为 Controller 和 MessageConsumer,让它们负责响应 Web 请求和消息队列。


NopGraphQL 在实现业务方法时,采用的是一种框架无关的非侵入式设计,它扩展了服务方法的使用场景,简化了服务层的编写。具体来说,NopGraphQL 引入了少量注解,使用 POJO 对象来作为输入输出对象,自动将业务方法翻译为 GraphQL 引擎所需的 DataFetcher 和 DataLoader。例如


@BizModel("MyEntity")class MyBizModel{    @BizQuery    public MyEntity get(@Name("id")String id){        return ...     }
@BizLoader public String extProp(@ContextSource MyEntity entity){ ... }
@BizLoader(forType=OtherEntity.class) public String otherProp(@ContextSource OtherEntity entity){ ... }
@BizLoader("someProp") public CompletionStage<List<SomeObject>> batchLoadSomePropAsync( @ContextSource List<MyEntity> entities){ ... }}
复制代码


  1. @BizQuery表示本方法将被映射为 GraphQL 中的 query 调用,@BizMutation将被映射为 GraphQL 中的 mutation 调用。

  2. @BizLoader为 GraphQL 类型的属性提供 fetcher 和 loader 定义。注意,为了保证概念的简单性,NopGraphQL 要求所有属性都必须在 xmeta 文件中声明,BizModel 中仅是为已定义的属性提供定制的加载器。

  3. 如果返回值类型为 CompletionStage,则表示该方法异步执行

  4. 如果标注了@BizLoader注解的方法的 ContextSource 参数为 List 类型,则表示它对应 GraphQL 的 DataLoader 实现,支持批量加载。



基于 NopGraphQL 引擎编写的服务方法,可以看作具有如下函数签名


ApiResponse<Object> service(ApiRequest<Map> request);
class ApiRequest<T>{ Map<String,Object> headers; FieldSelectionBean selection; T data;}
复制代码


服务方法都是接收一个 POJO 的 request 对象,返回一个 POJO 的 response 对象。因为输入和输出都是简单对象,所以可以无需编码,只需要简单配置,就可以做到


  1. 把 GraphQL 服务方法发布为消息队列的消费者,它从一个 topic 接收 request 对象,向另一个 topic 发送返回消息,如果 header 中标注了 one-way,则忽略返回消息。

  2. 将 GraphQL 服务方法发布为 RPC 服务函数

  3. 从批处理文件中读取 Request 对象,依次调用服务方法,批量提交,失败重试,然后把返回的 Response 消息写入到输出文件中。

五. REST Over GraphQL

GraphQL 引擎可以运行在 REST 服务之上,提供所谓 federation 的功能,将多个 REST 服务组合为一个统一的 GraphQL 端点。那么反过来是不是也可以将底层的 GraphQL 服务方法拆解开来,暴露为一个个独立的 REST 资源?


NopGraphQL 借助 lazy 字段的概念,对 GraphQL 类型定义 Eager 加载的属性集合,通过规范化的方式将 GraphQL 模型中的方法转化为 REST 服务。具体 REST 链接格式如下


/r/{operationName}?@selection=a,b,c{d,e}
复制代码


  1. 通过 request body 来传参数

  2. /r/{operationName}为服务链接,通过可选的@selection参数来指定对返回结果的字段选择。如果不指定,则后台会自动返回所有没有标记为 lazy 的属性。代码生成的时候,关联表的数据缺省会被标记为 lazy,因此它们在缺省情况下不会包含在 REST 调用的返回结果中。


如果是 query 请求,则可以通过 GET 方法来进行调用,此时可以通过 URL 参数来传递调用参数。例如


GET /r/NopAuthUser_get?id=3
复制代码


等价于执行 NopAuthUser_get(id:3)。


Nop 平台的前端框架在百度 AMIS 框架的基础上,对 GraphQL 调用做了进一步的简化。在前端,我们现在可以使用如下 url 格式来发起 GraphQL 调用,


api: {    url: '@query:NopAuthUser__get/id,userName?id=$id'}
复制代码


上面的 url 链接使用了所谓的前缀引导语法,底层的 ajaxFetch 函数会识别@query:前缀,并把它转化为 graphql 请求


query($id:String){    NopAuthUser_get(id:$id){       id, userName    }}
复制代码


ajaxFetch 识别的 graphql url 的格式为


(@query|@mutation):{operationName}/{selection}?参数名=参数值
复制代码


当我们需要为表单或者表格编写加载函数时,如果字段比较多,则手工编写 graphql 请求很容易出现字段遗漏。因为 Nop 平台的前端代码也是自动生成的,所以我们可以利用编译期信息自动生成 graphql 请求,使得我们恰好只选择表单或者表格中用到的数据。具体做法是引入编译期的变量 formSelection, pageSelection 等。例如


@query:NopAuthUser_get/{@formSelection}?id=$id
复制代码


{@formSelection}表示选择当前表单中用到的所有字段。


前缀引导语法是一种适应性非常广泛的 DSL 扩展方案,具体介绍可以参见文章

六. GraphQL 扩展

6.1 Map 类型

GraphQL 是一种强类型的框架,它要求所有数据都有明确的类型定义,这在某些动态场景中使用时并不方便。例如有的时候我们可能需要把一个扩展集合返回到前端。


NopGraphQL 引入了一个特殊的 Scalar 类型: Map,可以利用它来描述那些动态数据结构。例如


type QueryBean{    filter: Map    orderBy: [OrderFieldBean]}
复制代码

6.2 树形结构

对于单位树、菜单树这样的树形结构的获取,NopGraphQL 通过 Directive 机制提供了一个扩展语法,可以直接表达递归拉取数据,例如


NopAuthDept_findList{    value: id,    label: displayName    children @TreeChildren(max=5)}
复制代码


@TreeChild(max=5)表示按照本层的结构最多嵌套 5 层。

七. 差量流水线

在日常开发中,我们经常可以发现一些逻辑结构之间存在相似性和某种不精确的衍生关系,例如后端数据模型与前端页面之间密切的关联,对于最简单的情况,我们可以根据数据模型直接推导得到它对应的增删改查页面,或者反向根据表单字段信息推导得到数据库存储结构。但是,这种不精确的衍生关系很难被现有的技术手段所捕获和利用,如果强行约定一些关联规则,则只能应用于非常受限的特定场景,而且还会导致与其他技术手段的不兼容性,难以复用已有的工具技术,也难以适应需求从简单到复杂的动态演化。


可逆计算理论为实现这种面向动态相似性的复用提供了标准的技术路线:


  1. 借助于嵌入式元编程和代码生成,任意结构 A 和 C 之间都可以建立一条推理管线

  2. 将推理管线分解为多个步骤 : A => B => C

  3. 进一步将推理管线差量化:A => _B => B => _C => C

  4. 每一个环节都允许暂存和透传本步骤不需要使用的扩展信息


作为这一技术策略的演示应用,Nop 平台内置了从数据模型到前端页面的一条自动推理管线。



  1. 根据数据模型的需求文档(Excel 格式),由代码生成工具自动生成 ORM 实体关系映射模型。例如从 nop-auth.orm.xlsx 生成_app.orm.xml。参见nop_auth.orm.xlsx_app.orm.xml

  2. ORM 引擎真正使用的模型定义文件app.orm.xml从自动生成的_app.orm.xml文件继承,在关系模型的基础上可以补充更多 ORM 特定的配置,例如全局缓存策略、component 映射、属性别名、动态字段映射等。

  3. 根据实体模型自动生成业务对象元数据定义 xmeta,它定义了对象上具有哪些属性、属性的长度和类型等。参见_NopAuthUser.xmeta

  4. 程序中真正使用的 xmeta 是从自动生成的 xmeta 继承而来,在 ORM 映射关系的基础上,我们可以补充更多业务相关的信息,例如字段是否允许查询,能够按照哪些运算符进行查询,如果对输入字段进行校验和类型转换,如何添加数据库中并不存在的虚拟字段。

  5. 根据 xmeta 信息再补充 BizModel 中定义的操作函数、数据加载函数信息,自动生成 GraphQL 模型。

  6. 根据 xmeta 上定义的字段类型信息以及是否可修改、是否可显示等扩展信息,可以自动生成视图大纲模型 xview。视图大纲模型主要定义界面上存在哪些表单和表格,表单和表格中字段如何布局,字段使用哪个控件进行显示等信息。自动生成的 xview 会缺省根据数据类型和自定义的数据域(domain)信息自动推导得到缺省布局和缺省控件。参见_NopAuthResource.view.xml

  7. 从自动生成的 xview 继承,对界面布局和字段级展现进行精细调整。参见NopAuthResource.view.xml

  8. 利用 XLang 内置的x:gen-extends元编程机制,调用<web:GenPage>标签根据视图大纲模型生成前端框架所需的 json 格式文件。参见main.page.yaml

  9. page.yaml 页面描述文件中可以利用 XLang 内置的 DeltaMerge 机制对自动生成的页面描述进行调整。参见NopAuthUser/main.page.yaml。实际返回到前台的页面内容是编译期自动展开、合并后的结果,可以被 AMIS 框架直接使用,与 Nop 平台此前的处理步骤完全无关,参见_dump/main.page.yaml

  10. Nop 平台中所有的模型都允许增加自定义的扩展信息,例如 ext:show, xui:defaultFilterOp 等,这些信息可以透传到下一阶段,由后续的步骤识别并使用。


在 Nop 平台所建立的这个生产管线中,ORM 对应于存储模型,而 Page 对应于前端页面模型,为了简化从存储模型到页面模型的自动推导,增加了两个中间步骤:XMeta 和 XView,其中 XMeta 还可以产出中间产物 GraphQL 模型。XView 相比于通用的 Page,它的领域属性更强,以一种非常紧凑的方式描述界面上最关键的字段显示、字段布局、字段联动、API 调用等信息,并可以在编译期进行有效性检查。XView 原则上与具体的界面框架无关,所以可以根据它生成适应不同前端框架的页面文件。例如可以根据 xview 直接生成 vue 代码,而不一定是生成 AMIS JSON 描述。


整个推理关系的各个步骤都是可选环节:我们可以从任意步骤直接开始,也可以完全舍弃此前步骤所推理得到的所有信息。例如我们可以手动增加 xview 模型,并不需要它一定具有特定的 xmeta 支持,也可以直接新建 page.yaml 文件,按照 AMIS 组件规范编写 JSON 代码,AMIS 框架的能力完全不会受到推理管线的限制。

总结

Nop 平台是基于可逆计算原理从零开始构建的新一代低代码平台。它采用的是 DSL 优先、模型优先、自动测试优先的正向设计方案,而不是根据已有的程序框架结合部分低代码改造得到,在很多方面可以克服目前业界公开的低代码方案所存在的困难。


NopGraphQL 是 Nop 平台后端服务的运行引擎,它与 NopOrm 和前端的 AMIS 框架相互配合,同时支持 REST 和 GraphQL 两种接口协议,通过模型驱动和元编程的方式自动生成代码,极大降低了需要手工编写的代码量。


关于可逆计算理论的详细介绍,可以参见我此前的文章可逆计算:下一代软件构造理论从张量积看低代码平台的设计低代码平台需要什么样的ORM引擎(1)低代码平台需要什么样的ORM引擎(2)


基于可逆计算理论设计的低代码平台 NopPlatform 已开源:


发布于: 刚刚阅读数: 3
用户头像

canonical

关注

还未添加个人签名 2022-08-30 加入

还未添加个人简介

评论

发布
暂无评论
低代码平台中的GraphQL引擎_开源_canonical_InfoQ写作社区