Development Tutorial

Nop Platform source code:

The Nop Platform is a concrete implementation of Reversible Computation theory. To demonstrate the related concepts of Reversible Computation, it includes a built-in low-code development process for back-office management systems. This allows you to quickly develop the back-office in a low-code manner, and automatically provides product customization capabilities through the platform’s built-in mechanisms without special design. The following uses the development of the nop-app-mall project as an example to introduce the Nop Platform’s built-in low-code development process.

Nop-app-mall is a sample application for a simple e-commerce 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 that defines database tables, fields, and table association information.

In the Excel model, you can specify the following:

  1. Tag: Used to add annotations to 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 automated unit tests.

  2. Display: Controls 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 a field’s value to a limited set of options. A dictionary can be a Java enum class, such as io.nop.xlang.xdef.XDefOverride. It can also be defined in a yaml file, for example mall/aftersale-status corresponds to the file /nop/dict/mall/aftersale-status.dict.yaml

  5. Association: The association relationships 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.

Dictionary tables can be directly defined in the Excel model. During code generation, dict.yaml dictionary files will be automatically generated according to the model.

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

Reverse engineering

In addition to writing the database model manually, you 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 nop-cli reverse-db command needs to be passed the [database schema name] parameter, such as litemall, and then connection information such as the JDBC connection string via options like jdbcUrl.

Import a Power Designer model or a PDManer model

The nop-cli tool’s gen-orm-excel command lets you generate an Excel data model from the Power Designer PDM physical model.

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

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

The nop-cli tool can also 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 Reversible Computation theory, the PDM model, the PDManer model, and the Excel model are just visual representations of the ORM domain model. These representations contain the same information and can, in principle, be transformed into each other. With the Nop Platform’s metaprogramming capabilities, we can automatically generate ORM model files from the PDM model during compilation, so PowerDesigner or PDManer can be directly used as the visual design tools for ORM models 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 execution mechanism. When loading the app.orm.xml model file, it dynamically generates an inheritable base model according to the Xpl template defined in x:gen-extends, and then uses the x-extends merging algorithm to perform Delta merging on the inherited content.

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

2. Generate initial project code

If you already have an Excel data model, you can use the nop-cli gen command 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 and message definitions exposed by app-mall-api
├─app-mall-codegen   Code generation auxiliary project
├─app-mall-dao       Database entity definitions and ORM model
├─app-mall-service   GraphQL service implementation
├─app-mall-web       AMIS page files and View model definitions
├─app-mall-app       Test packaging project
├─deploy             Database creation statements generated from the Excel model

The Nop Platform provides code generation capabilities 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 io.nop:nop-entropy module’s POM, you need to add more detailed configuration for the exec-maven-plugin.

When using Maven packaging, xgen code under the project’s precompile and postcompile directories will be executed automatically. precompile runs before the compile phase; its execution environment can access all dependent libraries but cannot access the current project’s class directory. postcompile runs after the compile phase and has access to compiled classes and resource files. For example, the app-mall-codegen module’s precompile/gen-orm.xgen file contains:

<c:script>
// Generate dao/entity/xbiz from the ORM model
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, you can subsequently update the project’s code from the Excel data model via Maven packaging, without having to use the nop-cli tool. The Nop Platform uses an incremental code generation design, and regeneration does not overwrite manually adjusted business code. For the design principles, see:

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 launched directly in IDEA to execute code generation.

The code generated from the ORM model includes the full stack from frontend to backend and can be compiled and run directly using mvn install. Without additional configuration, the test application can be launched with:

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 in-memory database at startup and will automatically create database tables according to the ORM model. 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 a default menu item is defined for each backend entity, corresponding to a standard CRUD page.

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

 <resource id="goods-manage" displayName="Product Management" icon="ion:grid-outline" resourceType="TOPM"
           routePath="/goods-manage" component="layouts/default/index">
     <children>
         <resource id="mall-goods-list" displayName="Product List"
                   icon="ant-design:appstore-twotone" component="AMIS"
                   resourceType="SUBM" url="/app/mall/pages/LitemallGoods/main.page.yaml"/>
         <resource id="mall-goods-create" displayName="Product Listing"
                   icon="ant-design:appstore-twotone" component="AMIS"
                   resourceType="SUBM" url="/app/mall/pages/LitemallGoods/add.page.yaml"/>
         <resource id="mall-goods-comment" displayName="Product Reviews"
                   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; specific pages are configured with resourceType = SUBM, component = AMIS. The URL is the virtual path of the page file.

The underlying engine currently supports configuration for operation permissions and data permissions, but the specific permission verification interfaces have not yet been fully implemented.

4. Improve backend services

The Nop Platform will automatically generate Meta metadata description files and corresponding GraphQL service objects to provide a full-featured GraphQL backend service with simple configuration based on the Excel data model. Comparing LitemallGoodsBizModel.java in the app-mall project with AdminGoodsService.java in the original litemall project, it’s clear that the Nop Platform avoids a lot of repetitive code. Generally, you only need to express the difference logic that deviates from standard CRUD.

4.1 Add Entity Function

Fixed logic that depends only on entity fields can be implemented directly as methods on entity objects. For example, the retailPrice field on the goods table corresponds to the lowest price among the product’s SKUs.

class LitemallGoods extends _LitemallGoods{
    /**
     * retailPrice records the current product's lowest price
     */
    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 NopOrm data access engine, it is convenient to directly access collections of associated objects.

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

4.2 Add supplementary logic

The Nop Platform can automatically handle all CRUD logic using the description information defined in the XMeta file, for example:

  1. Automatically validate parameter correctness based on metadata (non-null, within dictionary bounds, meets format requirements, etc.)
  2. Support complex query conditions, paging, and sorting
  3. Submit master-detail information at once, automatically detecting changes to the detail data
  4. Automatically add a query condition deleted = false when logical deletion is enabled
  5. Verify that unique key constraints are not violated, e.g., goods names must be unique
  6. Automatically record creator, creation time, modifier, modification time, etc.
  7. When the current entity is deleted, automatically delete all associated objects marked as cascade-delete

When you need to enhance standard CRUD logic, you can override functions such as defaultPrepareSave in CrudBizModel’s derived classes. For example, when saving product information, automatically synchronize the redundant retailPrice field on the goods.

@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 example above also overrides defaultPrepareQuery. In this function, we transform complex query conditions submitted by the frontend. For example, the keywords field submitted by the frontend can be transformed into a fuzzy query on both the keywords and name fields in the database table.

4.3 Add 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. You can then use the SqlLibManager mechanism, which is more convenient and powerful than MyBatis, to implement database access. The sql-lib file supports EQL object query syntax or native SQL. EQL can be adapted to most relational databases through the Dialect model, so it is generally recommended to use EQL whenever 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 in the sql-lib source section are automatically replaced with SQL parameters. Because EQL can infer field types from the ORM model, there is no need to specify JDBC parameter types for each expression as in MyBatis. For detailed documentation, see sql-lib.md

Add the corresponding Java method to 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 Spring’s JpaRepository, but provides more functionality. For detailed documentation, see dao.md

4.4 Add GraphQL Query/Mutation/Loader

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

@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 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, you can add and modify Query/Mutation/DataLoader in the GraphQL model online without changing Java source code.

The xbiz model has a built-in finite state machine and can implement simple state transition logic via configuration, without Java programming. For example, simple approval status transitions.

With XDSL’s built-in x:gen-extends mechanism, you can introduce workflow support for the xbiz model in one line.

<biz>
   <x:gen-extends>
      <!-- Dynamically generate workflow-related backend service functions for business objects -->
      <biz-gen:GenWorkflowSupport/>
   </x:gen-extends>
</biz>

Workflow engine integration is currently in an initial stage.

5. Improve the frontend page

5.1 Refine Forms and Tables

The view outline model (view.xml) is a frontend 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 add/delete/update/query logic via simple configuration. It is much more compact than general page-oriented description models. For example, when adjusting the add and edit pages for goods, you only need the following layout description:

<form id="edit" size="lg">
            <layout>
                ========== intro[Product Introduction] ================
                goodsSn[Product SKU] name[Product Name]
                counterPrice[Market Price]
                isNew[New Release] isHot[Popular Recommendation]
                isOnSale[On Shelf]
                picUrl[Product Image on Product Page]
                gallery[Product Promo Images, as a JSON array]
                unit[Unit, e.g., piece/box]
                keywords[Product Keywords, comma-separated]
                categoryId[Category ID] brandId[Brand ID]
                brief[Brief Description]
                detail[Detailed Description, rich text]

                =========specs[Specifications]=======
                !specifications

                =========goodsProducts[Inventory]=======
                !products

                =========attrs[Attributes]========
                !attributes

            </layout>
            <cells>
                <cell id="unit">
                    <placeholder>piece/item/box</placeholder>
                </cell>
                <cell id="specifications">
                    <!-- You can directly specify the control used by the field via 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">
                    <!-- You can reference the grid from an external view model to display the child table -->
                    <view path="/app/mall/pages/LitemallGoodsProduct/LitemallGoodsProduct.view.xml"
                          grid="ref-edit"/>
                </cell>
            </cells>
</form>

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

Based on the form and table model, the Nop Platform will automatically analyze and obtain the backend field list needed, and automatically generate the selection part of the GraphQL request, avoiding inconsistencies between the UI and backend request data caused by manual writing.

5.2 Adjust field linkage logic

Field linkage logic can be expressed via data-binding property expressions, for example:

 <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>
                <!--
                Change the Add button to navigate to the add page
                -->
                <action id="add-button" x:override="merge-replace" actionType="link" url="/mall-goods-create">

                </action>
            </listActions>

            <!-- bounded-merge means the merge result is scoped to the current model.
                 Child nodes present in the base model but absent in the current model will be removed automatically.
                 In the default generated code, row-update-button and row-delete-button are already defined but configured with x:abstract=true.
                 Therefore, declaring only the id here enables the inherited buttons, avoiding redundant code.
             -->
            <rowActions x:override="bounded-merge">
                <!--
                    Use a drawer instead of a dialog to display the edit form
                -->
                <action id="row-update-button" actionType="drawer"/>

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

            </rowActions>
        </crud>

During code generation, a corresponding page and action buttons are automatically generated for each business object. They are stored in view files prefixed with an underscore, for example _LitemallGoods.view.xml. Therefore, when adjusting button configurations in LitemallGoods.view.xml, you only need to express the changed information.

5.4 Visual Designer

The Nop Platform’s current frontend framework is the AMIS Framework from Baidu, which uses JSON-formatted page files. In the browser’s address bar, you can directly enter the path to view the page file content (no need to register the path in the frontend router), for example:

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

The actual page corresponds to src/main/resources/_vfs/app/mall/pages/LitemallGoods/main.page.yaml, with content:

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

This file represents generating AMIS descriptions based on the page model defined in the LitemallGoods.view.xml view outline model.

  1. If the required page is special and cannot be effectively described by the view outline model, you can write the page.yaml file directly and skip the view outline model configuration. That is, frontend pages have the full capabilities of the AMIS framework and are not limited by the view outline model.
  2. Even if you write page.yaml files by hand, you can still introduce local form or grid definitions via x:gen-extends to simplify page authoring. Nested JSON nodes can also be dynamically generated using x:gen-extends or x:extends.
  3. The view model defines page presentation logic independent of specific implementation technologies. In principle, it can be adapted to any frontend framework. The Nop Platform will consider integrating Alibaba’s LowCodeEngine in the future.
  4. Based on automatically generated JSON pages, you can 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 frontend 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, click Refresh Page to update the frontend.

  2. Click the JSON design button to open the YAML editor, which allows you to modify the JSON description directly in the frontend and immediately see the result.

  3. Click the visual design button to open the amis-editor visual designer, allowing developers to adjust page content visually. Click Save to reversely compute the difference between the complete page and the generated View, and then save the difference to the page.yaml file.

For example, after modifying the title of the [Product Listing] page in the visual designer to [Add - Product] and saving, the add.page.yaml file contains:

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

The saved content has been converted to Delta form.

5.5 Introducing Custom Modules

The Nop Platform’s frontend framework source code is in the project nop-chaos. Generally, we use the framework’s built-in components to develop applications. In this case, you only need to introduce the precompiled nop-web-site module on the Java side, without recompiling the frontend nop-chaos project.

The frontend framework is mainly developed with vue 3.0, ant-design-vue, and Baidu AMIS. We have made some extensions on top of AMIS. See amis.md for details. Nop-chaos has built-in SystemJs module loading capabilities, which can dynamically load frontend modules. For example:

{
    "xui:import": "demo.lib.js"
    In sibling and descendant nodes, you can access content defined in the demo module via demo.xxx.
}

6. Development and debugging

The Nop Platform systematically uses metaprogramming and DSL domain languages for development and 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 of each node and attribute is printed.
  2. The nop-idea-plugin module provides an IDEA plugin, which implements code completion, format validation, etc., according to xdef metamodel definitions, and provides breakpoint debugging for the XScript scripting language and the Xpl template language.
  3. The Quarkus framework has a built-in graphql-ui development tool, which can view all backend GraphQL type definitions online and provide code hints, autocompletion, and other functions.

Refer to debug.md for a detailed introduction.

7. Automated testing

The Nop Platform has a built-in model-driven automated testing framework, which can achieve fully automated test data preparation and verification without dedicated 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 a record-and-replay mechanism to construct test cases.

  1. Inherit from the JunitAutoTestCase class
  2. Write a test method that uses the input function to read input data and the output function to write result data. Input 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 using the resulting snapshot data.
  4. Running again with snapshots enabled will initialize an in-memory database with the recorded data and automatically verify that the returned result data and database modifications 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. It supports running languages such as Python, JavaScript, and R, and can compile Java bytecode into native machine code so it can run directly as an executable, removing the dependency on the JDK. Native executables start faster (sometimes by an order of magnitude), use less CPU and memory, and occupy less disk space.

Nop platform further streamlines GraalVM support on the basis of the Quarkus framework, making it easy to 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

XPage=XExtends⟨XView⟩+ΔXPageXView=XGen⟨XMeta⟩+ΔXViewXMeta=XGen⟨ORM⟩+ΔXMetaORM=XGen⟨ExcelModel⟩+ΔORM  GraphQL=Builder⟨XMeta⟩+BizModel\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