Skip to content

Latest commit

 

History

History
354 lines (227 loc) · 15.2 KB

README_CN.md

File metadata and controls

354 lines (227 loc) · 15.2 KB

使用 dddappp 低代码工具开发 Sui 去中心化应用

English | 中文

前提条件

目前 dddappp 低代码工具以 Docker 镜像的方式发布,供开发者体验。

工具所生成应用的链下服务使用 Java 语言编写,默认使用了 MySQL 数据库。

所以在开始体验前,你需要先:

  • 安装 Sui

  • 安装 Docker

  • 安装 MySQL 数据库。

  • 安装 JDK 和 Maven。工具目前生成的链下服务使用 Java 语言。

如果你已经安装了 Docker,可以使用 Docker 来运行一个 MySQL 数据库服务。比如:

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

示例:重现 Demo 应用的开发过程

我们在 GitHub 上放置了一个使用 dddappp 低代码工具开发的 Demo 应用。这个应用的代码分为两部分:

你可以按照下面的介绍重现该 Demo 的开发过程。

需要说明的是,下面我们假设你将 Move 合约发布到 Sui devnet 上,所以略过如发布到其他网络所需的对某些配置文件的修改的说明。

提示

本 Demo 使用的数据模型相当复杂,而且,需要你“编写”一些业务逻辑(虽然你可能只是需要拷贝粘贴一下它们)。

如果你的业务需求只是对一些数据模型进行 CRUD(Create、Read、Update、Delete),那么,这个例子可能更适合你来参考: https://github.com/dddappp/sui-blog-example

编写 DDDML 模型文件

我们的低代码工具依赖 DDDML(领域驱动设计建模语言)描述的领域模型来生成应用的各部分代码。

提示

关于 DDDML,这里有一篇入门的介绍文章:《DDDML 简介:开启去中心化应用低代码开发的钥匙》。这篇文章包含了本 Demo 使用的一些 DDDML 模型文件的详细讲解。

你可以创建一个目录,比如叫做 test,来放置应用的所有代码,然后在该目录下面创建一个子目录 dddml。我们一般在这个目录下放置按照 DDDML 的规范编写的模型文件。

你可以把这里的示例模型文件下载/拷贝到 dddml 目录:https://github.com/wubuku/Dapp-LCDP-Demo/tree/main/domain-model/sui

在这些模型中,有些生造的例子可能已经复杂到了有点“荒谬”的地步,但我们的工具没有被“难倒”。

运行 dddappp 项目创建工具

使用 Docker 运行项目创建工具:

docker run \
-v /PATH/TO/test:/myapp \
wubuku/dddappp:0.0.1 \
--dddmlDirectoryPath /myapp/dddml \
--boundedContextName Test.SuiTestProj1 \
--boundedContextJavaPackageName org.test.suitestproj1 \
--boundedContextSuiPackageName sui_test_proj1 \
--javaProjectsDirectoryPath /myapp/sui-java-service \
--javaProjectNamePrefix suitestproj1 \
--pomGroupId test.suitestproj1 \
--suiMoveProjectDirectoryPath /myapp/sui-contracts

上面的命令参数很直白:

  • 注意将 /PATH/TO/test 替换为你实际放置应用代码的本机目录的路径。这一行表示将该本机目录挂载到容器内的 /myapp 目录。
  • dddmlDirectoryPath 是 DDDML 模型文件所在的目录。它应该是容器内可以读取的目录路径。
  • 把参数 boundedContextName 的值理解为你要开发的应用的名称即可。名称有多个部分时请使用点号分隔,每个部分使用 PascalCase 命名风格。Bounded-context 是领域驱动设计(DDD)中的一个术语,指的是一个特定的问题域范围,包含了特定的业务边界、约束和语言,这个概念你暂时不能理解也没有太大的关系。
  • boundedContextJavaPackageName 是链下服务的 Java 包名。按照 Java 的命名规范,它应该全小写、各部分以点号分隔。
  • boundedContextSuiPackageName 是链上 Sui 合约的包名。按照 Sui 的开发规范,它应该采用全小写的 snake_case 命名风格。
  • javaProjectsDirectoryPath 是放置链下服务代码的目录路径。链下服务由多个模块(项目)组成。它应该使用容器内的可以读写的目录路径。
  • javaProjectNamePrefix 是组成链下服务的各模块的名称前缀。建议使用一个全小写的名称。
  • pomGroupId 链下服务的 GroupId,我们使用 Maven 作为链下服务的项目管理工具。它应该全小写、各部分以点号分隔。
  • suiMoveProjectDirectoryPath 是放置链上 Sui 合约代码的目录路径。它应该使用容器内可以读写的目录路径。

上面的命令执行成功后,在本地目录 /PATH/TO/test 下应该会增加两个目录 sui-java-servicesui-contracts

此时可以尝试编译链下服务。进入目录 sui-java-service,执行:

mvn compile

如果没有意外,编译应该可以成功。

此时,链上合约还不能通过编译,因为“业务逻辑”还没有实现。下面我们就来实现它们。

实现业务逻辑

工具已经在目录 sui-contracts/sources 下生成了一些以 _logic.move 结尾的文件。文件中包含实现业务逻辑的函数的脚手架代码,即函数的签名部分。现在你只需要填充其中函数的实现部分。

你可以考虑从这里拷贝已经写好的业务逻辑的实现代码:https://github.com/wubuku/Dapp-LCDP-Demo/tree/main/sui-contracts/sources

你还可以将这个 Demo 应用的代码库 clone 下来,然后执行像下面这样的一个 shell 脚本来完成拷贝工作(注意将 _PATH_TO_/Dapp-LCDP-Demo_PATH_TO_/test 替换为你本机上的实际路径):

#!/bin/bash

source_dir="_PATH_TO_/Dapp-LCDP-Demo/sui-contracts/sources"
target_dir="_PATH_TO_/test/sui-contracts/sources"

old_keyword="sui_contracts"
new_keyword="sui_test_proj1"

for file in "${source_dir}"/*_logic.move; do
  if [[ -f "$file" ]] && grep -q "$old_keyword" "$file"; then
    cp "$file" "${target_dir}/$(basename "$file")"
    sed -i "" "s/$old_keyword/$new_keyword/g" "${target_dir}/$(basename "$file")"
  fi
done

然后进入目录 sui-contracts 执行编译,应该可以编译成功了:

sui move build

到现在为止,应用开发的编码阶段已经完成了!是不是非常简单?


下面我们来部署和测试 Demo 应用。

发布 Sui 合约

在完成业务逻辑的编写之后,在目录 sui-contracts 下执行以下命令,将合约发布到链上:

sui client publish --gas-budget 30000

提示

有时候 testnet 上的 gas 价格很高,请视需要调整命令中的 gas-budget 参数的值。

如果执行成功,会输出这次发布的交易摘要(Transaction Digest)。例如:

----- Transaction Digest ----
BZXe8c5nBjoyacUJTkcfoLgFuU9xWRksAMSfaEU3XrSM
----- Transaction Data ----
#...

记下这个交易摘要。

配置链下服务

打开位于目录 sui-java-service/suitestproj1-service-rest/src/main/resources 下的 application-test.yml 文件,设置发布的交易摘要。设置之后类似这样:

sui:
  contract:
    jsonrpc:
      url: "https://fullnode.devnet.sui.io/"
      #url: "http://localhost:9000"
    package-publish-transaction: "BZXe8c5nBjoyacUJTkcfoLgFuU9xWRksAMSfaEU3XrSM"

这是链下服务唯一必需配置的地方,就是这么简单。

创建链下服务的数据库

使用 MySQL 客户端连接本地的 MySQL 服务器,执行以下脚本创建一个空的数据库(假设名称为 test2):

CREATE SCHEMA `test2` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

进入 sui-java-service 目录,打包 Java 项目:

mvn package

然后运行一个命令行工具,初始化数据库:

java -jar ./suitestproj1-service-cli/target/suitestproj1-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

启动链下服务

sui-java-service 目录下,执行以下命令启动链下服务:

mvn -pl suitestproj1-service-rest -am spring-boot:run

特别提示:使用这个 Cheatsheet

在链下服务启动后,你可以访问这个网址,得到一个如何使用 Sui Client CLI 调用链上合约的速查表(Cheatsheet):http://localhost:1023/api/sui.contract/SuiClientCLICheatsheet.md

在 Cheatsheet 中连刚才发布的合约的 Package Id、创建某些实体时需要使用到的(Id Generator 的)Object Id 都帮你填好了。你需要填写的参数有包含“类型和含义(名字)”的占位符。 你可以拷贝这些命令,视你的需要稍作修改,然后在命令行终端中直接执行。

提交一些测试交易

接下来你可以尝试提交一些交易,调用链上的 Move 函数了。

比如,创建一个产品:

sui client call --package 0x88e91efb24e2e2fc9255af351d5035797071500df38b915f15b74271f389a595 --module product_aggregate --function create \
--args \"product_name_1\" \"1000\" \"0x95cf09389ed279d22e0df9baee507457e0359518c396569aa8f1247b918fb73d\" \
--gas-budget 100000

输出类似:

----- Transaction Effects ----
Status : Success
Created Objects:
  - ID: 0x96b16b23bcf70562889f2e6c74b3561d00829afade0b498dfa69bc085e2bc318 , Owner: Immutable

记录上面命令输出的产品的 Object Id,我们准备使用它来创建一个订单。

当然,你也可以链下服务的 RESTful API 来获得已经创建的产品的信息:http://localhost:1023/api/Products

执行下面的命令,可以创建一个订单:

sui client call --package 0x88e91efb24e2e2fc9255af351d5035797071500df38b915f15b74271f389a595 --module order_aggregate --function create \
--args \"0x96b16b23bcf70562889f2e6c74b3561d00829afade0b498dfa69bc085e2bc318\" \"1\" \
--gas-budget 100000

你可以访问 RESTful API 获取已创建的订单信息:http://localhost:1023/api/Orders

可以使用参数过滤订单信息,比如:http://localhost:1023/api/Orders?totalAmount=1000


我们知道,调用 Sui Move 合约的交易的输入参数只能包含基础类型或对象引用。而在我们的 DDDML 示例模型中,DaySummary 对象的领域 ID 是一个“多层嵌套的值对象”,这个 ID 由用户通过“Create 方法”赋值。你不用担心,工具生成的代码会帮你处理好这个问题。

执行下面的命令将创建一个 DaySummary 对象:

sui client call --package 0x88e91efb24e2e2fc9255af351d5035797071500df38b915f15b74271f389a595 --module day_summary_aggregate --function create \
--args 2023 \"ChineseLunar\" 4 false 10 \"Shanghai\" \"string_description\" \"vector_u8_meta_data\" '["string_array_data_item"]' '["vector_u8_optional_data"]' \"0x1676110fa451e75daf1d6a0204053662a8936e4267d475ac9e38101b9d1de340\" \
--gas-budget 100000

然后,通过这个接口,你可以获取已创建的 DaySummary 对象的列表:http://localhost:1023/api/DaySummaries

源代码

你可能已经注意到,根据以上过程重现的源代码已经放置在本代码库:https://github.com/dddappp/A-Sui-Demo

忽略某些文件的生成

项目创建工具生成的代码文件中,有一大部分是根据模型推导而来的样板代码。当模型发生了修改后,重新运行项目创建工具,这些代码文件默认情况下会被重新生成和覆盖。这些文件的开头大多数都有“autogenerated”字样的注释。

如果你想要修改这些文件,并且在重新生成代码时它们不被覆盖,那么你可以在特定的目录下放置一个 .dddmlignore 文件来做到这一点:

  • javaProjectsDirectoryPath 参数指定的目录。
  • suiMoveProjectDirectoryPath 参数指定的目录。

.dddmlignore 文件与 .gitignore 文件类似,每一行代表一个忽略规则。比如,下面的规则表示需要忽略重新生成所有目录层级中以 Order 或 order 开头的文件:

**/[Oo]rder*

敬请期待

增加 JSON Schema

你可能会觉得手写 DSL 模型有点麻烦,别担心,我们已经在编写 DDDML 的 JSON Schema。这里有个还有待完善的 Schema 文件:https://raw.githubusercontent.com/wubuku/dddml-spec/master/schemas/dddml-schema.json

DDDML 是一种基于 YAML 的 DSL,而 YAML 是 JSON 的超集,所以 JSON Schema 可以生效。

如果你的 IDE 支持,你可以配置一下,然后在用 DDDML 编写模型的时候,就有自动补全之类的支持了。

比如,在 VS Code 的 .vscode/settings.json 文件中可以像这样设置:

{
    "yaml.schemas": {
        "https://raw.githubusercontent.com/wubuku/dddml-spec/master/schemas/dddml-schema.json": [
            "dddml/*.yaml", //file match pattern
            "dddml/*.yml"
        ]
    }
}

图形化建模工具或 AI 辅助建模?

我们之前的想法,下一步的计划,是启动图形化建模工具的开发。

但是,我们现在有了更进一步的想法,那就是训练 AI,让 AI 帮助我们编写 DDDML 代码。我们认为,这可能是一个更好的方向。

增加更多对 DDDML 规范的支持

在开发基于 Sui 的 Dapp 方面,目前我们的命令行工具完成了对 DDDML 规范中比较基础的部分的支持。

在使用 DDDML 领域模型驱动传统应用开发方面,我们还有很多实践经验有待迁移到新的 Dapp 低代码开发工具上。

比如说:

  • 为 Dapp 低代码工具增加对“领域基础类型”对支持。

    • 目前我们的 Dapp 低代码工具已经实现了对 Move 的基本类型(Primitive Types)的支持,以及对由基本类型组合而成的、几乎没有什么业务逻辑的“数据值对象”的支持。
    • DDDML 主张与鼓励大家使用领域中的术语进行分析和建模。我们希望做到:团队一致认可某个领域概念是值对象之后,就可以在 DDDML 文件中直接使用它来作为属性的类型。至于这个类型的具体实现,可以剥离到独立的库中。
  • 我们可以在模型中声明实体的继承关系,工具可以为我们生成合适的代码,即使 Move 语言本身并没有对象继承的支持。

  • 我们可以增加对状态机模式的支持。领域中有些实体可能具有一些重要的“定性”的属性,UML 专门提供了状态机图来描述它们。DDDML 自然也不会错过对状态机模式的支持。

  • 我们可以增加对“树”——层次结构——的支持。我们经常会碰到一些需要构建层次结构的场景。比如一个公司的内部组织,它的各个部门、分支机构,可能就会组成一个甚至多个层次结构。

  • ……

进一步探索“面向资源”与 DDDML 规范的结合

我们相信,DDDML 规范还可以进一步发展,以充分利用 Move 语言面向资源这一程序设计新范式的优势。

[TBD]

一些提示

清理已经退出的 Docker 容器

执行命令:

docker rm $(docker ps -aq --filter "ancestor=wubuku/dddappp:0.0.1")