Skip to content

Latest commit

 

History

History
649 lines (454 loc) · 39.4 KB

tutorial_en.md

File metadata and controls

649 lines (454 loc) · 39.4 KB

Development Tutorial

Nop platform source code:

Nop platform is a concrete implementation of reversible computation theory. In order to demonstrate the related concepts of reversible computation theory, it has a built-in low-code development process for the backoffice management system, which can be used to quickly develop the backoffice management system in a low-code manner, and can automatically provide product customization capabilities by using the built-in mechanism of the platform without special design. The following takes the development of the nop-app-mall project as an example to introduce the built-in low-code development process of the Nop platform.

Nop-app-mall is an example application of a simple electronic mall. The project source code is at nop-app-mall

1. Design Excel Data Model

First, we need to design a data model in Excel format, in which database tables, fields and table association information are defined.

In the Excel model, we can specify the following information:

  1. Tag: It is used to make additional marks for tables, fields, etc. For example, seq indicates that the field value is automatically generated by SequenceGenerator, and var indicates that the field value will be initialized as a random value, which needs to be marked as a dynamic variable in the automated unit test.

  2. Display: control whether the field is displayed and whether it is allowed to be updated. The information provided here is used by the frontend to automatically generate list and form pages.

  3. Domain: For a field type with special business semantics, you can specify a specific data domain for it, and the system can process it uniformly after identifying the data domain. For example, domain = createTime indicates that the field is a creation time field, which is automatically initialized to the current time when a new entity is created.

  4. Dictionary: The data dictionary is used to restrict field value to a limited range of options. A dictionary can be an enum class in Java, such as a io.nop.xlang.xdef.XDefOverride. It can also be defined in a yaml file, for example, mall/aftersale-status corresponds to a file /nop/dict/mall/aftersale-status.dict.yaml

  5. Association: The association relationship between tables can be configured through the Association List. [Attribute Name] is the attribute name of the corresponding parent entity in the child table entity, and [Associated Attribute Name] is the attribute name of the corresponding child table entity set in the parent entity. For example, the [Attribute Name] corresponding to the parentId associated field in the department table is parent, and the [Associated Attribute Name] is children. If you do not need to access the entity collection through the ORM engine, you do not need to configure the associated attribute name.

The dictionary table can be directly defined in the Excel model, and the dict.yaml definition dictionary file will be automatically generated according to the model during code generation.

More detailed configuration information can be found in the documentation excel-model.md

Reverse engineering

In addition to writing the database model manually, we can also connect to an existing database and use the nop-cli command-line tool to reverse analyze the database structure and generate the Excel model.

java -Dfile.encoding=UTF8 -jar nop-cli.jar reverse-db litemall -c=com.mysql.cj.jdbc.Driver --username=litemall --password=litemall123456 --jdbcUrl="jdbc:mysql://127.0.0.1:3306/litemall?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC"

The reverse-db command for nop-cli needs to pass in the parameter [database schema name], such as litemall, and then pass in information such as the JDBC connection string through options such as jdbcUrl.

Import a Power Designer model or PDManer model

The gen-orm-excel command of the nop-cli tool allows you to generate an Excel data model from the PDM physical model of the Power Designer design tool.

java -Dfile.encoding=UTF8 -jar nop-cli.jar gen-orm-excel model/test.pdm

An open source alternative to Power Designer, which is fee-based software, is PDManager Metadata Modeling Tool .

The nop-cli tool can also be used to generate Excel data models from PDManer model files.

java -Dfile.encoding=UTF8 -jar nop-cli.jar gen-orm-excel model/test.pdma.json

According to the theory of reversible computation, Pdm model, PDManer model and Excel model are just a visual representation of ORM domain model. ** These representations contain the same information and can, in principle, be transformed into each other **. With the metaprogramming ability in the Nop platform, we can automatically generate orm model files according to the Pdm model during compilation, so we can directly use PowerDesigner or PDManer as the visual design tool of ORM model in the Nop platform.

<!-- app.orm.xml -->
<orm x:schema="/nop/schema/orm/orm.xdef"
     x:extends="base.orm.xml" x:dump="true"
     xmlns:x="/nop/schema/xdsl.xdef" xmlns:xpl="/nop/schema/xpl.xdef">
    <x:gen-extends>
        <pdman:GenOrm src="test.pdma.json" xpl:lib="/nop/orm/xlib/pdman.xlib"
                      versionCol="REVISION"
                      createrCol="CREATED_BY" createTimeCol="CREATED_TIME"
                      updaterCol="UPDATED_BY" updateTimeCol="UPDATED_TIME"
                      tenantCol="TENANT_ID"
        />
    </x:gen-extends>
</orm>

x:gen-extends Is a compile-time running mechanism. In the process of loading the app.orm.xml model file, it will dynamically generate an inheritable base model according to x:gen-extends the Xpl template defined in, and then use the x-extends merging algorithm to perform delta merging on the inherited content.

x:gen-extends The mechanism is a built-in syntax for the XLang language, which is described in detail in xdsl.md

2. Generate initial project code

If you have already obtained an Excel data model, you can use the gen command of the nop-cli command-line tool to generate the initial project code

java -jar nop-cli.jar gen -t=/nop/templates/orm model/app-mall.orm.xlsx

The generated contents are as follows:

├─app-mall-api       Interface definition and message definition exposed by app-mall-api
├─app-mall-codegen   code generation auxiliary project
├─app-mall-dao       database entity definition and ORM model
├─app-mall-service   GraphQL service implementation
├─app-mall-web       AMIS page file and View model definition
├─app-mall-app       test used packaging project
├─deploy             database creation statements generated according to Excel model

The Nop platform provides code generation capability integrated with Maven. You only need to add the following configuration in the POM file:

<pom>
    <parent>
        <artifactId>nop-entropy</artifactId>
        <groupId>io.github.entropy-cloud</groupId>
        <version>2.0.0-SNAPSHOT</version>
    </parent>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</pom>

If you do not inherit from the POM file of the io.nop:nop-entropy module, you need to add more detailed configuration for the exec-maven-plugin plug-in.

When the Maven packaging function is used, the xgen code under the precompile and postcompile directories of the project will be automatically executed, where precompile is executed before the compile phase, and the execution environment can access all dependent libraries, but cannot access the class directory of the current project. Postcompile, on the other hand, is executed after the compile phase and has access to the compiled class and resource files. For example, the contents of the precompile/gen-orm.xgen file for the app-mall-codegen module are

<c:script>
// 根据ORM模型生成dao/entity/xbiz
codeGenerator.withTargetDir("../").renderModel('../../model/app-mall.orm.xlsx','/nop/templates/orm', '/',$scope);
</c:script>

The above instruction is equivalent to manually executing the nop-cli gen command, so ** Once the initial project is generated, the code for the current project can then be updated against the Excel data model through Maven packaging, eliminating the need to use the nop-cli tool **. The Nop platform uses an incremental code generation design, and regeneration does not override manually adjusted business code. See the article for the design principles.

Data-Driven Code Generator (https://zhuanlan.zhihu.com/p/540022264)

To facilitate debugging, the initially generated code contains two test classes, AppMallCodeGen. Java and AppMallWebCodeGen. Java, which can be directly started in IDEA to execute code generation.

The code generated from the ORM model contains the full set of code from the frontend to the backend, which can be compiled and run directly using the mvn install command. Without additional configuration, the test application can be launched by the following command:

mvn clean install -DskipTests -Dquarkus.package.type=uber-jar

java -Dfile.encoding=UTF8 -Dquarkus.profile=dev -jar app-mall-app/target/app-mall-app-1.0-SNAPSHOT-runner.jar

app-mall-app uses the built-in H2 memory database to start, and will automatically create database tables according to the ORM model when starting. The default user is nop, and the password is 123.

3. Configure Menus and Authorization Rules

The automatically generated code includes the permission definition file: app-mall-web/src/resources/_vfs/app/mall/auth/_app-mall.action-auth.xml, in which the default menu item is defined for each backend entity, corresponding to the standard CRUD page.

In the app-mall.action-auth.xml file, you can manually add new menu items, or you can mark to delete generated menu items.

 <resource id="goods-manage" displayName="商品管理" icon="ion:grid-outline" resourceType="TOPM"
           routePath="/goods-manage" component="layouts/default/index">
     <children>
         <resource id="mall-goods-list" displayName="商品列表"
                   icon="ant-design:appstore-twotone" component="AMIS"
                   resourceType="SUBM" url="/app/mall/pages/LitemallGoods/main.page.yaml"/>
         <resource id="mall-goods-create" displayName="商品上架"
                   icon="ant-design:appstore-twotone" component="AMIS"
                   resourceType="SUBM" url="/app/mall/pages/LitemallGoods/add.page.yaml"/>
         <resource id="mall-goods-comment" displayName="商品评论"
                   icon="ant-design:appstore-twotone" component="AMIS"
                   resourceType="SUBM" url="/app/mall/pages/LitemallComment/main.page.yaml"/>
     </children>
 </resource>

The menu structure is designed by the jeecgboot project. The top-level menu is configured with resourceType = TOPM, component = layouts/default/index, and the specific page is configured with resourceType = SUBM, component = AMIS. The URL is configured as the virtual path of the page file.

At present, the underlying engine has supported the configuration of operation permission and data permission, but the specific permission verification interface has not been fully implemented.

4. Improve back-end services

The Nop platform will automatically generate Meta metadata description files and corresponding GraphQL service objects ** A full-featured GraphQL backend service with simple configuration ** according to the Excel data model. Comparing the Litemall GoodsBizModel.java in the app-mall project with the AdminGoodsService.java implementation class in the original litemall project, it is clear that the Nop platform can avoid a lot of repetitive code. Generally, it is only necessary to express the difference logic that deviates from the standard CRUD logic.

4.1 Add Entity Function

Fixed logic that depends only on entity fields can be directly implemented as methods of entity objects. For example, the retail Price field on the item table corresponds to the lowest price for the relevant product.

class LitemallGoods extends _LitemallGoods{
    /**
     * retailPrice记录当前商品的最低价
     */
    public void syncRetailPrice() {
        LitemallGoodsProduct minProduct = Underscore.min(getProducts(), LitemallGoodsProduct::getPrice);
        BigDecimal retailPrice = minProduct == null ? minProduct.getPrice() : new BigDecimal(Integer.MAX_VALUE);
        setRetailPrice(retailPrice);
    }
}

With the help of NopOrm data access engine, it is very convenient to directly access the collection of associated objects.

All associated objects and collections of associated objects are lazy-loaded. The BatchLoadQueue mechanism can be utilized to pre-load in advance, thus avoiding the N + 1 problem common in Hibernate. See for orm.md for details

4.2. Add supplementary logic

Nop platform can automatically realize all logics of CRUD by using the description information defined in the XMeta file, for example:

  1. Automatically verify the validity of the parameter according to the metadata configuration (whether it is not empty, whether it is within the dictionary table range, whether it meets the format requirements, etc.)

  2. Realize complex query conditions, as well as paging and sorting

  3. Submit the master-sub table information at one time, and automatically identify the changes to the sub-table data

  4. The query condition of deleted = false is automatically added when logical deletion is enabled

  5. Verify that the unique key constraint is not broken, for example, goods names are not allowed to be duplicated

  6. Automatically record the creator, creation time, modifier, modification time, etc.

  7. When the current entity is deleted, all associated objects marked as cascade-delete are automatically deleted

Functions such as defaultPrepareSave can be overwritten in the derived class of CrudBizModel when we need to enhance the standard CRUD logic. For example, when saving the product information, the redundant field retailPrice on the product is automatically synchronized.

@BizModel("LitemallGoods")
public class LitemallGoodsBizModel extends CrudBizModel<LitemallGoods> {
    public LitemallGoodsBizModel() {
        setEntityName(LitemallGoods.class.getName());
    }

    @Override
    protected void defaultPrepareSave(EntityData<LitemallGoods> entityData, IServiceContext context) {
        entityData.getEntity().syncRetailPrice();
    }

    @Override
    protected void defaultPrepareQuery(QueryBean query, IServiceContext context) {
        TreeBean filter = query.getFilter();
        if (filter != null) {
            TreeBean keywordsFilter = filter.childWithAttr("name", LitemallGoods.PROP_NAME_keywords);
            if (keywordsFilter != null) {
                Object value = keywordsFilter.getAttr("value");
                TreeBean orCond = or(contains(LitemallGoods.PROP_NAME_name, value), contains(LitemallGoods.PROP_NAME_keywords, value));
                filter.replaceChild(keywordsFilter, orCond);
            }
        }
    }
}

The above example code also overwrites the defaultPrepareQuery function. In this function, we transform the complex query conditions submitted by the frontend. For example, the keywords query field submitted by the frontend can be transformed into a fuzzy query for the keywords and name fields in the database table at the same time.

4.3. Increase database access

If a mapper tag is added to a database table in the Excel model, a sql-lib.xml file and a Mapper interface will be automatically generated, and we can use the SqlLibManager mechanism, which is more convenient and powerful than MyBatis, to implement database access. The sql-lib file supports the use of EQL object query syntax or native SQL syntax. The EQL syntax can be adapted to most relational databases through the Dialect model, so it is generally recommended to use the EQL syntax as much as possible.

<!-- LitemallGoods.sql-lib.xml -->
<sql-lib x:schema="/nop/schema/orm/sql-lib.xdef" xmlns:x="/nop/schema/xdsl.xdef">

    <sqls>
        <eql name="syncCartProduct" sqlMethod="execute">
            <arg name="product"/>

            <source>
                update LitemallCart o
                set o.price = ${product.price},
                  o.goodsName = ${product.goods.name},
                  o.picUrl = ${product.url},
                  o.goodsSn = ${product.goods.goodsSn}
                where o.productId = ${product.id}
            </source>
        </eql>
    </sqls>
</sql-lib>

Similar to MyBatis, EL expressions are automatically replaced with SQL parameters in the source section of sql-lib, and because the EQL syntax can deduce the field types from the ORM model, there is no need to specify JDBC parameter types for each expression as MyBatis does. For detailed documentation, see sql-lib.md

Add the corresponding Java method in the Mapper interface:

@SqlLibMapper("/app/mall/sql/LitemallGoods.sql-lib.xml")
public interface LitemallGoodsMapper {

    void syncCartProduct(@Name("product") LitemallGoodsProduct product);
}

The IEntityDao interface in the NOP platform is similar to JpaRespository in Spring, but it provides more functionality. For detailed documentation, see dao.md

4.4 Add Query/Mutation/Loader of GraphQL

Normal Java methods can be converted into GraphQL service methods by adding @BizQuery/@BizMutation/@BizLoader annotations to them.

@BizModel("NopAuthRole")
public class NopAuthRoleBizModel extends CrudBizModel<NopAuthRole> {
    public NopAuthRoleBizModel() {
        setEntityName(NopAuthRole.class.getName());
    }

    @BizLoader
    @GraphQLReturn(bizObjName = "NopAuthUser")
    public List<NopAuthUser> roleUsers(@ContextSource NopAuthRole role) {
        return role.getUserMappings().stream().map(NopAuthUserRole::getUser)
                .sorted(comparing(NopAuthUser::getUserName)).collect(Collectors.toList());
    }

    @BizMutation
    public void removeRoleUsers(@Name("roleId") String roleId,
                                @Name("userIds") Collection<String> userIds) {
        removeRelations(NopAuthUserRole.class,
                "roleId", "userId",
                roleId, userIds);
    }
}

@BizLoader Indicates that an extended attribute is added to the specified object. For example, there is no roleUsers attribute on the NopAuthRole object, but as long as the attribute definition is added to the xmeta file, and then a BizLoader is defined, all requests returning the Role object can access the attribute. For example

query{
    NopAuthRole__get(id: 'admin'){
       name
       roleUsers
    }

    NopAuthRole__findPage(query:$query){
       name
       roleUsers
    }
}

See graphql-java.md for more details

4.5 Define the xbiz model

The Nop platform provides built-in support for ** No Code Development **. Through the xbiz model, we can add and modify Query/Mutation/DataLoader in the GraphQL model online without modifying the Java source code.

The xbiz model has a built-in finite state automaton model, and some simple state transition logic can be completed through configuration without programming in Java. Such as simple approval status migration.

With XDSL's built-in x:gen-extends mechanism, we can introduce workflow support for the xbiz model in one sentence.

<biz>
   <x:gen-extends>
      <!-- 可以动态为业务对象生成工作流相关的后台服务函数-->
      <biz-gen:GenWorkflowSupport/>
   </x:gen-extends>
</biz>

At present, the integration of workflow engine is still in the initial stage.

5. Improve the frontend page

5.1 Refine Forms and Tables

The view outline model (view.xml) is a front-end DSL that is independent of the specific implementation framework and fully oriented to the business domain. It abstracts key elements such as grid, form, layout, page, dialog, and action, and can describe common addition, deletion, modification, and query logic through simple configuration. It is much more compact than the generic page-oriented description model. For example, when adjusting the new and modified pages of the commodity object, we only need to write the following layout description

<form id="edit" size="lg">
            <layout>
                ========== intro[商品介绍] ================
                goodsSn[商品编号] name[商品名称]
                counterPrice[市场价格]
                isNew[是否新品首发] isHot[是否人气推荐]
                isOnSale[是否上架]
                picUrl[商品页面商品图片]
                gallery[商品宣传图片列表,采用JSON数组格式]
                unit[商品单位,例如件、盒]
                keywords[商品关键字,采用逗号间隔]
                categoryId[商品所属类目ID] brandId[Brandid]
                brief[商品简介]
                detail[商品详细介绍,是富文本格式]

                =========specs[商品规格]=======
                !specifications

                =========goodsProducts[商品库存]=======
                !products

                =========attrs[商品参数]========
                !attributes

            </layout>
            <cells>
                <cell id="unit">
                    <placeholder>件/个/盒</placeholder>
                </cell>
                <cell id="specifications">
                    <!-- 可以通过gen-control直接指定字段所用控件 -->
                    <gen-control>
                        <input-table addable="@:true" editable="@:true"
                                     removable="@:true" >
                            ...
                        </input-table>
                    </gen-control>
                    <selection>id,specification,value,picUrl</selection>
                </cell>
                <cell id="products">
                    <!-- 可以引用外部view模型中的grid来显示子表 -->
                    <view path="/app/mall/pages/LitemallGoodsProduct/LitemallGoodsProduct.view.xml"
                          grid="ref-edit"/>
                </cell>
            </cells>
</form>

Layout is a specialized layout domain language that separates the layout information from the presentation control information of specific fields. The control used by a specific field is generally determined by the data type or data domain setting, and we only need to supplement the layout information to realize the page display. For a detailed description of the layout language, see layout.md

** According to the form and table model, the Nop platform will automatically analyze and obtain the backend field list they need to access, so as to automatically generate the selection part of the graphql request, so as to avoid the inconsistency between the UI interface and the backgroud request data caused by manual writing. **。

5. Adjust field linkage logic

Field linkage logic can be expressed through data binding property expressions, such as

 <cell id="pid">
     <requiredOn>${level == 'L2'}</requiredOn>
     <visibleOn>${level == 'L2'}</visibleOn>
 </cell>

5. 3 Adjust action buttons and navigation logic

Common crud pages, single form pages (simple), and multi-tab pages (tab) can be defined and adjusted in the view outline model, and the defined form and table models can be directly referenced in the page model.

Additional page models can be supported by customizing the xview.xdef metamodel file

        <crud name="main" grid="list">
            <listActions>
                <!--
                修改新增按钮的功能为跳转到新增页面
                -->
                <action id="add-button" x:override="merge-replace" actionType="link" url="/mall-goods-create">

                </action>
            </listActions>

            <!-- bounded-merge表示合并结果在当前模型范围内。基础模型中有,当前模型中没有的子节点,会被自动删除。
                 缺省生成的代码中已经定义了row-update-button和row-delete-button,只是配置了x:abstract=true,
                 因此这里只要声明id,表示启用继承的按钮即可,可以避免编写重复的代码。
             -->
            <rowActions x:override="bounded-merge">
                <!--
                    使用drawer而不是对话框来显示编辑表单
                -->
                <action id="row-update-button" actionType="drawer"/>

                <action id="row-delete-button"/>

            </rowActions>
        </crud>

When the code is generated, the corresponding page and operation button will be automatically generated for each business object. They are stored in the view file with the prefix of underscore, for example _LitemallGoods.view.xml. Therefore, when the button configuration is adjusted in the LitemallGoods.view.xml, only the changed information need to be expressed.

5.4 Visual Designer

The actual front-end framework currently used by the Nop platform is AMIS Framework from Baidu, which uses a JSON-formatted page file. In the browser address bar, we directly enter the path to view the contents of the page file (** No need to register the path in the front-end router **), for example

http://localhost:8080/index.html?#/amis/app/mall/pages/LitemallGoods/main.page.yaml

The actual page it corresponds to is src/main/resources/_vfs/app/mall/pages/LitemallGoods/main.page.yaml, and the content is

x:gen-extends: |
    <web:GenPage view="LitemallGoods.view.xml" page="main"
         xpl:lib="/nop/web/xlib/web.xlib" />

This file represents the generation of the AMIS description based on the page model defined in the LitemallGoods.view.xml view outline model.

  1. If the page we need to implement is special and cannot be effectively described by the view outline model, we can directly write the page.yaml file and skip the configuration of the view outline model. That is to say, ** Front-end pages have the full capabilities of the AMIS framework and are not limited by the view outline model **.

  2. Even if we write page.yaml files by hand, we can still introduce local form or grid definitions through x:gen-extends to simplify page writing. Nested JSON nodes can also be dynamically generated using x:gen-extends or x:extends.

  3. The view model defines the page presentation logic that is independent of the specific implementation technology. In principle, it can be adapted to any front-end framework technology. The Nop platform will consider integrate Alibaba's LowCodeEngine in the future.

  4. Based on the automatically generated JSON pages, we manually make delta corrections to the generated code in the page.yaml file (using XDSL's built-in Delta merging technique).

In debug mode, all front-end AMIS pages have two design buttons in the upper right corner.

 amis-editor

  1. If you manually modify the page.yaml or view.xml model file in the backend, you can click Refresh Page to update the frontend

  2. Click the JSON design button to pop up the YAML editor, which allows you to modify the JSON description directly in the frontend and then see the display immediately.

  3. Clicking the visual design button will pop up the amis-editor visual designer, allowing developers to adjust the content of the page through the visual designer. ** Click Save to reversely calculate the difference between the complete page and the generated View, and then save the difference to the page.yaml files **。

For example, after modifying the title of the [商品上架] page in the visual designer as [新增-商品] and saving it, the content in the add.page.yaml file is

x:gen-extends: |
  <web:GenPage view="LitemallGoods.view.xml" page="add" xpl:lib="/nop/web/xlib/web.xlib" />
title: '@i18n:LitemallGoods.forms.add.$title|新增-商品'

The saved content has been converted to delta form.

5.5 Introducing Custom Modules

The source code of the front-end framework of the Nop platform is in the project nop-chaos. In general, we use the built-in components of the framework to develop applications. At this time, we only need to introduce the pre-compiled nop-web-site module on the Java side, and do not need to recompile the front-end nop-chaos project.

The front-end framework is mainly developed by vue3.0, ant-design-vue and Baidu AMIS framework. We have made some extensions on the basis of AMIS framework. See the document amis.md for details. Nop-chaos has built-in SystemJs module loading capability, which can dynamically load front-end modules. For example

{
    "xui:import": "demo.lib.js"
    同级及下级节点中可以通过demo.xxx来访问demo模块中定义的内容。
}

6. Development and debugging

The Nop platform systematically uses metaprogramming and DSL domain languages for development, for which it also provides a series of auxiliary development and debugging tools.

  1. All compile-time composed models are output to the _dump directory, where the source code location for each node and attribute is printed

  2. The nop-idea-plugin module provides an IDEA development plug-in, which can implement code completion, format verification and other functions according to the xdef metamodel definition, and provides breakpoint debugging function for the XScript scripting language and the Xpl template language.

  3. Quarkus framework has built-in graphql-ui development tool, which can view all GraphQL type definitions in the background online, and provide code hints, automatic completion and other functions.

Refer to the document debug.md for detailed introduction

7. Automated testing

Nop platform has a built-in model-driven automated testing framework, which can achieve fully automated test data preparation and verification without special programming.

public class TestLitemallGoodsBizModel extends JunitAutoTestCase {

    @Inject
    IGraphQLEngine graphQLEngine;

    @EnableSnapshot
    @Test
    public void testSave() {
        ContextProvider.getOrCreateContext().setUserId("0");
        ContextProvider.getOrCreateContext().setUserName("test");

        ApiRequest<?> request = input("request.json5", ApiRequest.class);
        IGraphQLExecutionContext context = graphQLEngine.newRpcContext(GraphQLOperationType.mutation,
                "LitemallGoods__save", request);
        Object result = FutureHelper.syncGet(graphQLEngine.executeRpcAsync(context));
        output("response.json5", result);
    }
}

NopAutoTest uses the mechanism of recording and replay to construct test cases.

  1. Inherited from the JunitAutoTest Case class
  2. Write a test method that uses the input function to read the input data and the output function to output the result data. Input data and output data will be recorded to the cases directory during the initial execution.
  3. After successful execution, mark @EnableSnapshot on the test method to enable use of the resulting snapshot data.
  4. Running again after enabling snapshots will initialize an in-memory database with recorded data and automatically verify that the returned result data and the results of modifications to the database match the recorded data.

For a more detailed description, see autotest.md

8. Delta Customization

All the XDSL model files are stored in the src/resources/_vfs directory, and they form a virtual file system. This virtual file system supports the concept of Delta layered overlay (similar to the overlay-fs layered file system in Docker technology), which has layers /_delta/default by default (more layers can be added through configuration). That is, if there is both a file /_vfs/_delta/default/nop/app.orm.xml and a file /nop/app.orm.xml , the version in the delta directory is actually used. In the delta customization file, you can use x:extends="raw:/nop/app.orm.xml" to inherit the specified base model, or use x:extends="super" to inherit the base model of the previous level.

In contrast to the customization mechanisms provided by traditional programming languages, ** The rules of Delta customization are very general and intuitive, and are independent of the specific application implementation**. Taking the customization of the database Dialect used by the ORM engine as an example, if we want to extend the built-in MySQLDialect of the Hibernate framework, we must have some knowledge of the Hibernate framework. Then we also need to know how Spring encapsulates Hibernate, and where to find Dialect and configure it to the current SessionFactory. In the Nop platform, we only need to add files /_vfs/default/nop/dao/dialect/mysql.dialect.xml to ensure that all places using the MySQL dialect are updated to use the new Dialect model.

Delta custom code is stored in a separate directory, which can be separated from the code of the main application. For example, the delta customization file is packaged into the nop-platform-delta module, and when this customization is needed, the corresponding module is imported. We can also introduce multiple delta directories at the same time and then control the order of the delta layers through the nop.core.vfs.delta-layer-ids parameter. For example, the configuration nop.core.vfs.delta-layer-ids=base,hunan enables two delta layers, one for the base product and one above it for a specific deployment version. In this way, we can productize software at a very low cost: ** When a basic product with basically complete functions is implemented at various customers, the code of the basic product can be completely unmodified, and only the Delta customization code need to be added. **.

When developing specific applications, we can use the delta customization mechanism to fix platform bugs or enhance platform functionality. For example, the app-mall project adds more field control support by customizing /_delta/default/nop/web/xlib/control.xlib . For example, if a control <edit-string-array> is added, as long as the data domain of the field is set to string-array in the Excel data model, the front-end interface will automatically use the input-array control of AMIS to edit the field.

For a more detailed description, see xdsl.md

9. GraalVM native compilation

GraalVM is the next generation Java virtual machine developed by Oracle Corporation, which supports Python, JavaScript, R and other languages to run, and can compile Java bytecode into binary machine code to run directly as exe, getting rid of the dependence on JDK. Native executables start faster (perhaps tens of times faster), use less CPU and memory, and take up less disk space.

Nop platform further simplifies GraalVM support on the basis of Quarkus framework, and can easily compile application modules into native executables.

  1. The Quarkus framework itself adapts many third-party libraries to GraalVM

  2. The Nop platform analyzes the IoC container configuration, learns all the beans that need to be created dynamically, and generates the GraalVM configuration.

  3. All reflection operations in the Nop platform are performed through the ReflectionManager helper class, which logs all reflection operations. When the application runs in debug mode, the GraalVM configuration is automatically generated to the src/resources/META-INF/native-image directory based on the reflection information collected

Taking the app-mall project as an example, the following steps are required to compile the native executable: ( Requires prior installation of GraalVM environment )

cd app-mall-app
mvn package -Pnative

The result is target/app-mall-app-1.0-SNAPSHOT-runner.exe. Currently, the size of exe is a little large (146m), mainly because the graalvm.js engine will occupy nearly 60m. If it is not necessary to dynamically execute the JS packaging task, the dependence on the nop-js module can be removed.

You can only use the nop-js module to execute dynamic code in the debugging phase. In principle, you only need to use the generated static JS file when the system is running.

Summary

The built-in delta software production line of the Nop platform is shown in the following figure:

It can be expressed by the following formula

$$ \begin{aligned} XPage &= XExtends\langle XView\rangle + \Delta XPage\\ XView &= XGen\langle XMeta\rangle + \Delta XView \\ XMeta &= XGen\langle ORM \rangle + \Delta XMeta \\ ORM &= XGen\langle ExcelModel \rangle + \Delta ORM\ \ \\ GraphQL &= Builder\langle XMeta\rangle + BizModel\\ \end{aligned} $$

Each step of the entire inference relationship is optional: ** We can start directly from any step, or we can completely discard all the information inferred from the previous step. **. For example, we can manually add an xview model without requiring it to have specific xmeta support, or we can directly create a new page. Yaml file and write JSON code according to the AMIS component specification. The ability of the AMIS framework will not be limited by the reasoning pipeline at all.

In daily development, we can often find that there are similarities and ** Imprecise derivative relation ** between some logical structures, such as the close relationship between the back-end data model and the front-end page. For the simplest case, we can directly deduce the corresponding CRUD page according to the data model. Or the database storage structure is reversely obtained by deducing from the form field information. However, this imprecise derivative relationship is difficult to be captured and utilized by the existing technical means. If some association rules are forcibly agreed, they can only be applied to very limited specific scenarios, and will also lead to incompatibility with other technical means. It is difficult to reuse existing tools and technologies, and it is also difficult to adapt to the dynamic evolution of requirements from simple to complex.

Nop platform provides a standard technical route for realizing the dynamic similarity-oriented reuse based on the reversible computation theory:

  1. With embedded metaprogramming and code generation, ** An inference pipeline can be established between any structure A and C. **

  2. ** The reasoning pipeline is broken down into steps: A = > B = > C **

  3. ** The inference pipeline difference is further quantified. **:A => _B => B => _C => C

  4. ** Each link allows temporary storage and transparent transmission of extended information ** that is not required in this step

Specifically, the chain of logical reasoning from the back end to the front end can be decomposed into four main models:

  1. ORM: Domain Model for the Storage Layer

  2. XMeta: For the domain model of the GraphQL interface layer, the type definition of GraphQL can be generated directly.

  3. XView: Front-end logic understood at the business level, using a small number of UI elements such as forms, tables, buttons, etc., independent of the front-end framework

  4. XPage: a page model that uses a front-end framework