This is a demo of Move Forms.
Move Forms is a No-Code tool. It is based on dddappp, the Low-Code Development Platform.
Here we need to clarify the difference between Low-Code and No-Code.
-
Low-Code is for professional developers.
There is a consensus (de facto standard) in the industry on what the core features of a Low-Code platform should have.
The bottom line is that they must take a "model-driven" approach. For our discussion of this, see: https://www.dddappp.org/#what-is-a-true-low-code-development-platform
-
No-Code refers to a large category of tools for "end-users".
There is no standardization on what is considered No-Code.
They allow users to create simple applications such as product advertisement pages, online questionnaires, personal blogs, etc.
As can see from this demo, Low-Code has powerful capabilities that can support No-Code tool implementations.
The following explains how Move Forms works.
- First, we use Form Builder to design a form.
- Then, we can export the schema of this form.
- We use a command line tool to convert the schema into a DDDML domain model, from which we can generate on-chain contract and off-chain service.
- In addition, we can use a tool to generate a form submission page from the schema.
- Then end-users can fill out and submit the form.
- We can view the submitted data via the off-chain service.
Tip
About DDDML, here is an introductory article: "Introducing DDDML: The Key to Low-Code Development for Decentralized Applications".
Currently, the dddappp low-code tool is published as a Docker image for developers to experience.
The off-chain services generated by the tool are written in Java and use the MySQL database by default.
So before getting started, you need to:
- Install Aptos CLI.
- Install Docker.
- Install
curl
. (Optional) Installjp
- commandline JSON processor. We can usejp
to process Node API returned JSON results when testing contracts.- Install MySQL database server. If you do not want to try to run the off-chain service, you can ignore this step.
- Install JDK and Maven. If you do not want to try to run the off-chain service, you can ignore this step. The off-chain services generated by the tool currently use Java language.
If you have already installed Docker, you can use Docker to run a MySQL database service. For example:
sudo docker run -p 3306:3306 --name mysql \
-v ~/docker/mysql/conf:/etc/mysql \
-v ~/docker/mysql/logs:/var/log/mysql \
-v ~/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7
XRender is a form solution open-sourced by Alibaba. It supports designing and rendering forms and submitting form data in JSON format to backends. It was originally a Web2 solution. But obviously we can improve it and make it work for Web3.
You can use this Form Schema Builder to design a form: https://xrender.fun/schema-builder-online
You can use the Export (导出) button to export the form's schema to the clipboard, then save the exported schema as a plain text file.
With the XRender form schema file, we can generate the on-chain contracts and off-chain service (sometimes it's called indexer).
This process can be done in one step with dddml CLI tool. However, its internal implementation does this by first generating the DDDML model file, and then generating on-chain contract and off-chain service from the model file.
For the following command, we assume that the saved form schema file is form-schema/form.json
, and the generated model file will be available at dddml/forms.yaml
.
In repository root directory, run:
docker run \
-v .:/myapp \
wubuku/dddappp-aptos:0.0.1 \
--xRenderFormSchema /myapp/form-schema/form.json \
--xRenderFormId AptosFormsDemo \
--xRenderFormStartPageName MainForm \
--xRenderFormUpdatable \
--xRenderFormOpenAt '2023-11-06T07:36:54Z' \
--xRenderFormCutoffAt '2093-11-08T07:36:54+08:00' \
--dddmlDirectoryPath /myapp/dddml \
--boundedContextName Test.AptosFormsDemo \
--aptosMoveProjectDirectoryPath /myapp/aptos-contracts \
--boundedContextAptosPackageName AptosFormsDemo \
--boundedContextAptosNamedAddress aptos_forms_demo \
--boundedContextJavaPackageName org.test.aptosformsdemo \
--javaProjectsDirectoryPath /myapp/aptos-java-service \
--javaProjectNamePrefix aptosformsdemo \
--pomGroupId test.aptosformsdemo
The command parameters above are straightforward:
- The first line indicates mounting your local directory into the
/myapp
directory inside the container. xRenderFormSchema
is the path of XRender form schema file. It should be a readable file path in the container.xRenderFormId
is the ID of XRender form. It's recommended to use PascalCase naming style.- (Optional)
xRenderFormStartPageName
is the name of start page of XRender form. It's recommended to use PascalCase naming style. The default value isMainForm
. - (Optional)
xRenderFormUpdatable
is a boolean value indicating whether the form is updatable. The default value isfalse
. - (Optional)
xRenderFormOpenAt
is the time when the form is open. The default value is empty, which means the form is open immediately. - (Optional)
xRenderFormCutoffAt
is the time when the form is closed. The default value is empty, which means the form is never closed. dddmlDirectoryPath
is the directory where DDDML model files are located. It should be a readable directory path in the container.- Interpret the value of (optional) parameter
boundedContextName
as the name of your application you want to develop. When there are multiple parts in your name, separate them with dots and use PascalCase naming style for each part. Bounded-context is a term in Domain-driven design (DDD) that refers to a specific problem domain scope that contains specific business boundaries, constraints, and language. If you don't understand this concept for now, it's not a big deal. aptosMoveProjectDirectoryPath
is directory path where on-chain Aptos contract code is placed. It should be a readable and writable directory path in container.- (Optional)
boundedContextAptosPackageName
is package name of on-chain Aptos contracts. It's recommended to use PascalCase naming style. - (Optional)
boundedContextAptosNamedAddress
is default named address of on-chain Aptos contracts. It's recommended to use snake_case naming style. - (Optional)
boundedContextJavaPackageName
is Java package name of off-chain service. According to Java naming conventions, it should be all lowercase and parts should be separated by dots. javaProjectsDirectoryPath
is directory path where off-chain service code is placed. Off-chain service consists of multiple modules (projects). It should be a readable and writable directory path in container.- (Optional)
javaProjectNamePrefix
is name prefix of each module of off-chain service. It's recommended to use an all-lowercase name. - (Optional)
pomGroupId
is GroupId of off-chain service. We use Maven as project management tool for off-chain service. It should be all lowercase and parts should be separated by dots.
If you don't specify the optional parameters, the tool will derive them based on xRenderFormId
.
After executing above command successfully, a directory aptos-contracts
should be added to local current directory.
If you modify the code's generation command, take care to replace aptos_forms_demo
with the actual value in the shell command lines used for the following tests.
It should be noted that below we assume that you will publish the Move contract to the Aptos devnet, so we skip the explanation of the modifications to some configuration files required for publishing to other networks.
We can create a new account on devnet to perform the following test.
Confirm that Aptos CLI is installed and enter the directory aptos-contracts
, then run:
aptos init
# Press Enter to confirm using the default values:
aptos account fund-with-faucet --account default --amount 50000000000
# View Aptos Profiles:
aptos config show-profiles
It should display similar information:
{
"Result": {
"default": {
"has_private_key": true,
"public_key": "...",
"account": "{ACCOUNT_ADDRESS}",
"rest_url": "https://fullnode.devnet.aptoslabs.com",
"faucet_url": "https://faucet.devnet.aptoslabs.com"
}
}
}
Note that the form contract depends on a utility package in this project: https://github.com/dddappp/XRender-Form-Utils
We need to publish this package on chain first, and then we can compile the form contract.
In the following command, we assume that the package is published at address 0x71df3ab1b6cf015aa5870a8a6e8ee0951c54e8d7d79bb59fa3b737c3a38fb93b
.
In the directory aptos-contracts
, execute the compilation, which should now succeed:
aptos move compile --named-addresses aptos_forms_demo=default,xrender_form_utils=0x71df3ab1b6cf015aa5870a8a6e8ee0951c54e8d7d79bb59fa3b737c3a38fb93b
# Unit test:
# aptos move test --named-addresses aptos_forms_demo=default,xrender_form_utils=0x71df3ab1b6cf015aa5870a8a6e8ee0951c54e8d7d79bb59fa3b737c3a38fb93b
At this point, the coding phase of the application development is complete! Isn't it very simple?
Next, we will deploy and test the Demo application.
Execute the following command in the directory aptos-contracts
to publish the contract on chain:
aptos move publish --included-artifacts none --skip-fetch-latest-git-deps --named-addresses aptos_forms_demo=default,xrender_form_utils=0x71df3ab1b6cf015aa5870a8a6e8ee0951c54e8d7d79bb59fa3b737c3a38fb93b --assume-yes
If the command is executed successfully, it should display similar information:
{
"Result": {
"transaction_hash": "{TRANSACTION_HASH}"
"gas_used": 20722,
"gas_unit_price": 100,
"sender": "{ACCOUNT_ADDRESS}",
"sequence_number": 0,
"success": true,
"timestamp_us": 1688909362156606,
"version": 11446005,
"vm_status": "Executed successfully"
}
}
Initialize the contract first:
aptos move run --function-id 'default::aptos_forms_demo_init::initialize' --assume-yes
You can do a test of submitting the form using the default account:
aptos move run --function-id 'default::aptos_forms_demo_main_form_aggregate::submit' \
--args u128:12 'string:["A","B"]' 'string:["A","B"]' 'string:fr_8xjs' 'string:1' u128:1 'string:fr_b3ub' u128:1 \
'u16:[2022,1,1,2022,1,2]' 'u16:[2022,1,1,2022,1,2]' u128:1 'u8:[1,1,1,1,1,2]' 'string:["A","B"]' 'string:["1","2"]' \
'u16:[2022,1,1]' 'string:["single_text1"]' u64:1000000 \
--assume-yes
You can do a test of updating the form like this:
aptos move run --function-id 'default::aptos_forms_demo_main_form_aggregate::update' \
--args address:56c01bfdfae128e57544f59f52be70bb883ea2d1fb97ba5774741996f77b4eb7 \
u128:12 'string:["A","B"]' 'string:["A","B"]' 'string:fr_8xjs' 'string:1' u128:1 'string:fr_b3ub' u128:1 \
'u16:[2022,1,1,2022,1,2]' 'u16:[2022,1,1,2022,1,2]' u128:1 'u8:[1,1,1,1,1,2]' 'string:["A","B"]' 'string:["1","2"]' \
'u16:[2022,1,1]' 'string:["single_text1"]' u64:1000000 \
--assume-yes
aptos move run --function-id 'default::aptos_forms_demo_global_aggregate::admin_withdraw_payment_123_vault' \
--args u64:1000000 \
--assume-yes
We're still refining the tool for this step…
We're still refining the tool for this step…
This is a published form submission page: https://low-code-demo-gold.vercel.app/
After running the dddappp tool, an Off-chain service project will be generated in the aptos-java-service
directory.
It can pull application events and entity states on the chain into the off-chain database, and provides query APIs.
The off-chain services generated by the tool are written in Java and use the MySQL database by default.
So before getting started, you need to set up a basic Java development environment:
- Install JDK and Maven.
- Install MySQL database server.
If you have already installed Docker, you can use Docker to run a MySQL database server. For example:
sudo docker run -p 3306:3306 --name mysql \
-v ~/docker/mysql/conf:/etc/mysql \
-v ~/docker/mysql/logs:/var/log/mysql \
-v ~/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7
Use a MySQL client to connect to the local MySQL server and execute the following script to create an empty database (assuming the name is test2
):
CREATE SCHEMA `test2` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
If you want to use a PostgreSQL database, search the code with the keyword postgres
and follow the comment prompts to make changes.
For PostgreSQL, execute the following script:
CREATE DATABASE test2
WITH
OWNER = postgres
ENCODING = 'UTF8'
LOCALE_PROVIDER = 'libc'
CONNECTION LIMIT = -1
IS_TEMPLATE = False;
Go to the aptos-java-service
directory and package the Java project:
mvn package
Then, run a command-line tool to initialize the database:
java -jar ./{form_id}-service-cli/target/{form_id}-service-cli-0.0.1-SNAPSHOT.jar ddl -d "./scripts" -c "jdbc:mysql://127.0.0.1:3306/test2?enabledTLSProtocols=TLSv1.2&characterEncoding=utf8&serverTimezone=GMT%2b0&useLegacyDatetimeCode=false" -u root -p 123456
For PostgreSQL, the following command can be used:
java -jar ./{form_id}-service-cli/target/{form_id}-service-cli-0.0.1-SNAPSHOT.jar ddl -d "./scripts" -c "jdbc:postgresql://127.0.0.1/test2" -u postgres -p 123456
Open the application-test.yml
file located in the directory aptos-java-service/{form_id}-service-rest/src/main/resources
and set the published contract address.
After setting, it should look like this:
aptos:
contract:
address:
"0xd19e5d4634d89efe118138177628d8b2137918bb634fe461ce6061c99a56a0be"
node-api:
base-url: "https://fullnode.devnet.aptoslabs.com/v1"
# if use testnet:
#base-url: "https://fullnode.testnet.aptoslabs.com/v1"
In the aptos-java-service
directory, run the following command to start the off-chain service:
mvn -pl {form_id}-service-rest -am spring-boot:run
You can use the following command to query posts:
curl http://localhost:1023/api/MainForms
Since the dddappp v0.0.1 image is updated frequently, you may be required to manually delete the image and pull it again before docker run
.
# If you have already run it, you may need to Clean Up Exited Docker Containers first
docker rm $(docker ps -aq --filter "ancestor=wubuku/dddappp-aptos:0.0.1")
# remove the image
docker image rm wubuku/dddappp-aptos:0.0.1
# pull the image
docker pull wubuku/dddappp-aptos:0.0.1
Here it is a cheatsheet on how to use the Aptos Client CLI to call on-chain contracts: AptosMoveCLICheatsheet
The parameters you need to fill in are placeholders containing their type and meaning (name). You can copy these commands, modify them as needed, and execute them directly in a terminal.
Our contracts use a separate resource account to hold data of form.
You can get the address of this resource account by using the following command:
curl https://fullnode.devnet.aptoslabs.com/v1/accounts/{ACCOUNT_ADDRESS}/resource/{ACCOUNT_ADDRESS}::aptos_forms_demo_resource_account::ResourceAccount
The output is similar to the following:
{"type":"{ACCOUNT_ADDRESS}::aptos_forms_demo_resource_account::ResourceAccount","data":{"cap":{"account":"{RESOURCE_ACCOUNT_ADDRESS}"}}}
In the location {RESOURCE_ACCOUNT_ADDRESS}
above, the address of the resource account will be displayed.
Execute the following command, noting the replacement of the placeholders {RESOURCE_ACCOUNT_ADDRESS}
and {ACCOUNT_ADDRESS}
:
curl --request GET \
--url 'https://fullnode.testnet.aptoslabs.com/v1/accounts/{RESOURCE_ACCOUNT_ADDRESS}/events/{ACCOUNT_ADDRESS}::aptos_forms_demo_main_form::Events/aptos_forms_demo_main_form_submitted_handle?start=0&limit=10' \
--header 'Accept: application/json'
For example:
curl --request GET \
--url 'https://fullnode.testnet.aptoslabs.com/v1/accounts/0xd2700787f8440e63143653a734cd42bde662e201fca7d5754acd269e6e79ff96/events/0xf0ee3187beaa48e56def59dbe52d7e932c3e559c28ae26568fa91ff9ae85e145::aptos_forms_demo_main_form::Events/aptos_forms_demo_main_form_submitted_handle?start=0&limit=10' \
--header 'Accept: application/json'
curl 'https://fullnode.devnet.aptoslabs.com/v1/accounts/{RESOURCE_ACCOUNT_ADDRESS}/resource/{ACCOUNT_ADDRESS}::aptos_forms_demo_main_form::Tables'
The output is similar to the following:
{"type":"{ACCOUNT_ADDRESS}::aptos_forms_demo_main_form::Tables","data":{"aptos_forms_demo_main_form_table":{"handle":"{FORM_TABLE_HANDLE}"}}}
In the location {FORM_TABLE_HANDLE}
above, the form table handle will be displayed.