跳到主要内容

架构设计

版权声明

采用自顶向下的设计模式,并仅声明要做什么,而不涉及如何做的问题。

自顶向下可以将整个平台划分为以下部分:

平台应用前端站点资源页面操作后台服务接口模型从属于11..n从属于11..n从属于1*从属于1*从属于*1从属于11..n引用10..1包含1*调用1*

平台直接面向使用者,为用户提供访问入口,并将用户触发的请求转发给应用; 而应用是一类功能业务的集合,负责响应用户业务处理需求; 而服务为业务应用的功能集合,负责处理一组业务功能,为可独立运行的实体; 模块则是对业务功能的组织,一般为导航菜单。

也就是,一个或多个模块组成一个服务,一个或多个服务组成一个应用, 一个或多个应用组成一个平台,一个平台面向一个或多个用户。

<!-- 按照状态迁移的模式进行 DSL 设计 -->
<platform displayName="渡舟平台" domain="duzhou.crazydan.io">
<!--
@url 用于标识请求的路由,也就是平台将根据 url 将对应的请求路由到指定的应用,
再由应用路由到指定的服务
@domain 被托管应用的访问域名,该应用是被独立访问和管理的,
平台只负责对其运行资源的分配和管理。注:对其请求的路由还是得经过平台
service@type 用于标识服务类型,具有相同类型标识的服务实例将被视为同一个服务,
用于以共享模式为多个租户提供服务,因此,可自由对服务实例进行增减。
对于定制化的服务,则由服务实例向平台注册不同的类型标识,
再在定义 app 时引入该类型的 service 即可
-->

<app displayName="平台用户应用" url="/user">
<services>
<!-- 一个服务就是一套可运行代码,但实际运行的可能会有多个服务实例 -->
<service displayName="用户服务"
type="PlatformUser" version="1.0.0">
</service>
</services>

<web>
<site subTitle="用户管理" url="">
<resources>
<resource displayName="角色管理" url="/role">
</resource>
</resources>
</site>
</web>
</app>

<app displayName="平台管理应用" url="/management">
<services>
<service displayName="平台管理服务"
type="PlatformManagement" version="1.0.0">
</service>
<service displayName="平台网关服务"
type="PlatformGateway" version="1.0.0">
</service>
<!-- 用于与 Agent 对接以部署和管理包括平台管理应用服务在内的全部服务 -->
<service displayName="平台控制服务"
type="PlatformCtroller" version="1.0.0">
</service>
</services>

<web>...</web>
</app>

<app displayName="租户管理应用" url="/tenant">
<services>
<service displayName="租户管理服务" url="/management"
type="TenantManagement" version="1.0.0">
</service>
<service displayName="租户计费服务" url="/charging"
type="TenantCharging" version="1.0.0">
</service>
<service displayName="租户自服务" url="/self-service"
type="TenantSelfService" version="1.0.0">
</service>
</services>

<web>...</web>
</app>

<!--
租户应用,本质上可视为在数据中添加了租户标识的普通应用,
实际开发的应用中可对数据模型始终加上租户标识属性,
从而便于直接接入到多租户平台中。也可以通过 Delta 机制无缝实现对多租户的支持。

租户应用,对于平台而言属于托管应用,平台仅负责维护其正常运行,
并对未激活的租户应用做访问限制,其余的均由应用自身处理。
-->
<app displayName="租户应用 A" url="/a" domain="tenant.duzhou.crazydan.io">
<services>
<!-- 用户服务为通用服务 -->
<service displayName="用户服务" url="/user"
type="User" version="1.0.0">
</service>

<service displayName="组织机构服务" url="/org"
type="Organization" version="1.0.0">
</service>
</services>

<web>...</web>
</app>

<app displayName="租户应用 B" url="" domain="b.tenant.duzhou.crazydan.io">
<services>
<service displayName="组织机构服务" url="/org"
type="TenantB_Org" version="1.0.0">
</service>
</services>

<web>...</web>
</app>
</platform>

平台以最大化资源利用率为原则进行各类应用的服务部署, 没有定制化的服务均为共享服务模式,有定制化的且可适用于多个租户的,同样也是共享服务模式。

平台(Platform)和应用(App)均为抽象概念,二者只是用于对不同层面的服务进行分组, 以方便对服务职能和作用范围进行管理。

服务(Server)为最小的部署单元, 可以动态将选中的一个或多个模块放到一个服务中做独立部署(服务实例可多个), 并且原则上使用独立的数据库(通过服务的配置指定数据库连接信息), 跨服务调用的模块内的模型,自动采用 RPC 机制。 服务控制的是运行时环境,为程序提供配置和运行支持,包括代码的动态性支持。 在服务部署时,平台会将服务内所包含的模块发到目标 Agent 上, 再由 Agent 完成初始化并启动服务。

平台自身将提供如下组件或服务,用于部署、监控和管理包括其自身在内的所有服务:

  • Agent:部署代理,其将被部署在任意可用的主机设备上,并主动接入到平台中, 平台将通过 agent 部署各类应用服务,并会动态下发相关配置, 以让其所部署的服务将日志、监控数据等发送到平台指定的服务中
    • 接入平台需要提供唯一的接入码,其由平台生成, 在通过平台下载 agent 安装包时生成,仅可使用一次,且有有效期
    • 平台提供的 agent 将会内置 SSL 公钥,以加密平台与 agent 之间的交互数据
    • 由服务确定下发的配置哪些可即时生效,对于不能即时生效的配置, 在平台侧由用户确定是否需要重启
    • 需监控主机可用资源,以便于动态分配服务资源
  • Gateway:服务网关,负责路由外部请求到平台内部服务及其被托管服务上
    • 每个应用都有一个网关,负责路由 web 资源和 api 请求, 而在平台处也会部署一个入口网关,负责路由应用的请求到应用的网关
    • 根据 url 路径和域名路由请求,对租户应用,需在请求头中附加租户标识

在从零开始的环境中,平台部署的过程为:

  • 在选定的主机上安装平台运行包并启动其服务
  • 通过目标主机的 ip 和平台服务端口访问服务
  • 根据指导步骤完成平台初始配置
  • 从平台管理页面获取 agent 安装包和接入码
  • 按照 agent 安装的指导步骤在其他主机上安装 agent。 注:也可在提供了 ssh 帐号的情况下由平台自主安装(仅安装 agent 时才需使用 ssh 帐号)
  • 由用户触发平台的初始化,包括自动安装 agent、部署 gateway、扩充平台服务实例等

开发过程为:

  • platfrom 开始逐步定义 appwebservice 等 DSL
  • 根据 DSL 自动创建 webservice 等应用服务的 Maven 工程,其包含最基础的依赖和配置

状态迁移

初始化开发环境应用开发应用服务部署应用服务运维

初始化开发环境

开发者开发者浏览器浏览器DuZhou.binDuZhou.bin开发服务开发服务[001]创建 $DUZHOU_HOME 目录并下载 DuZhou.bin 至该目录[002]运行 DuZhou.bin[003]创建 Web 服务[004]检查开发环境[005]检查 Maven 版本及仓库配置[006]检查 JDK 版本[007]返回检查结果[008]显示环境检查结果和待修改配置项[009]更新配置[010]提交[011]调用配置更新 API[012]初始化开发环境[013]返回初始化结果[014]初始化成功[015]确认并跳转到开发服务[016]访问服务[017]返回首页[018]展示开发首页

DuZhou.bin 为使用 Go 编写和打包的二进制可执行文件, 主要负责 渡舟平台 的开发环境的初始化。 其内置一个 Web Server 以便于开发者通过浏览器进行相关的初始化配置。

Go 可以编译出单文件、无依赖、轻量级的二进制文件,而且还可以跨平台编译, 能够满足快速进入开发就绪状态的要求。

在启动 DuZhou.bin 后,程序将创建一个 Web 服务, 并检查其运行所在操作系统中的开发环境:

  • 根据环境变量查找 mvnjava 命令的位置,并获取其版本号
  • 判断开发工具的版本号是否符号要求:必须高于最低版本要求

开发环境检查完毕后,将会向浏览器返回检查结果,并由浏览器展示开发环境配置页面:

  • Web 服务创建后,便自动打开浏览器并访问该服务
  • 在开发环境检查过程中,前端页面处于等待状态
    • 等待动效为 在海浪中行进的小船,按实际情况显示进度,不加最小延迟
    • 进度信息包括:进度百分比、正在进行的处理
  • 以 WebSocket 机制实现对检查结果的推送

在开发环境配置页面包含以下内容:

  • Maven 配置:当前版本号、安装位置、本地仓库目录位置、远程仓库列表及连接信息、 是否满足最低版本要求等
    • 若不满足最低版本要求,则提供默认版本的下载地址, 由开发者确认是由程序自动下载,还是由其自行下载。 若为自行下载,则需填写安装包的绝对文件路径
    • 本地仓库目录位置从现有配置读取,支持指定新的仓库目录位置
    • 补充必要的远程仓库和连接信息,如 Nop 包的仓库、渡舟部件的公共仓库等
  • Java 配置:当前版本号、安装位置、是否为 JDK
    • 若不满足最低版本要求或不是 JDK,则提供默认的下载地址。 同样可由开发者自行下载并填写安装包的绝对文件路径

开发者完成以上配置后,点击 开始初始化 按钮,由 DuZhou.bin 进行初始化工作:

  • 下载指定版本的 Maven 或 JDK
  • 若需安装新版 Maven,则将获得的 Maven 安装到 $DUZHOU_HOME/tools/maven/<x.y.z> 中, 并配置其本地仓库位置为 $DUZHOU_HOME/tools/maven/repository(默认位置)
    • 生成新的配置文件到 $DUZHOU_HOME/tools/maven/config 中,在使用 mvn 命令时显式指定该文件的位置
  • 若需安装新版 JDK,则将获得的 JDK 安装到 $DUZHOU_HOME/tools/jdk/<x.y.z>
  • 记录已确认的 Maven 和 JDK 的版本、安装目录和相关配置
  • 准备指定版本的 开发服务 部署包:可以由用户下载并放置到 $DUZHOU_HOME 中,或者,自动从默认位置下载
  • 启动开发服务

应用开发

开发者开发者浏览器浏览器开发服务开发服务项目演示项目演示创建项目[001]配置项目信息并提交[002]调用项目创建 API[003]创建项目[004]添加默认 Delta[005]识别 DSL[006]构建项目[007]启动项目演示[008]返回创建结果[009]返回项目开发页[010]显示项目开发页开发项目[011]设计业务模型[012]调用 DSL 更新 API[013]更新演示

项目演示与开发可以以上下布局方式同时展示。

  • 通过开发服务创建项目:填写项目名称、标识、Git 仓库等
  • 选择并安装 Delta 包
  • 定制化开发
  • 业务模型可视化设计
    • 模型更新后自动生成代码并构建
    • 可实时查看调整结果
    • 先设计业务模型,然后自动生成增删改查 API, 再创建模块,并指定模块内包含的 API 和页面资源
  • IDE 辅助开发
    • 直接从 $DUZHOU_HOME/projects/<xxx> 目录中导入工程
    • 监控文件变化,并自动构建
    • 提供 VSCode 开发插件,在 VSCode 直接集成浏览器和开发能力, 支持直接从显示元素中关联并跳转到代码位置
  • 一键提交开发变更代码:查看并验证修改结果无误后,点击提交变更按钮, 并在填写变更日志后,提交并推送到 Git 仓库
  • 应用版本发布
  • Delta 业务打包
  • Delta 版本打包
    • 以版本为 Delta 层,新版 Delta 必然基于某个已有版本实施差量
    • 根据变更内容提取 Delta,并将其打包为新版本
    • 应用 Delta 需按照版本继承关系,从当前版本开始按继承顺序依次应用 各个 Delta 版本层。也可以动态打包两个版本之间的所有版本

应用服务部署

  • 向可用主机内部署服务

应用服务运维

  • 监控
    • 调用链
    • 性能
    • 日志
  • 管理
  • 升级

核心模型

HostResourceList<String> labels