基于领域模型的快速建站框架

289 阅读5分钟

前言

对于当下的中后台系统,大部分从事开发的人其实都在对接产品、后端、UI、测试,然后干着 CRUD 的工作。但是如果我们沉淀出一个快速建站框架,这个框架能够通过 DSL 描述一个个模版页,不同的系统需要基于这些模版页去进行配置,那么这个系统 80% 其实都是现成的、可快速搭建的,剩下的 20% 需要自己去定制化开发页面。

正文会用 DSL 描述一个模版页 -> 指示板 dashboard,然后基于系统模型去配置这个模版页进行快速搭建。

下面这张图是整个快速建站框架的架构图。

image.png

指示板 dashboard DSL

什么是 DSL

DSL是一种工具,它的核心价值在于,可以更加清晰地就系统某部分的意图进行沟通;是针对某一特定领域,具有受限表达性的一种计算机程序设计语言。

本文使用 json-schema + js 对象去描述一个模版页。

用 DSL 描述 dashboard

image.png

头部菜单

最外层 menu 字段描述头部菜单。

菜单就有子菜单,或者直接就是菜单项。这里使用 menuType 进行区分,group 表示有子菜单,可进行递归;module 表示菜单项。

menu: [{
    key: '',  // 菜单唯一描述
    name: '', //菜单名称
    menuType: '', // 枚举值,group / module
}]

菜单项

当 menuType 为 module 时,可展示侧边栏 sider、数据页面 schema-view 或者定制化页面 custom-view。

sider 可点击展示侧边栏菜单,菜单配置同头部菜单。

// 当 moduleType == sider 时
siderConfig: {
  menu: [{
    // 可递归 menuItem (除 moduleType == sider 外)
  }, ...]
},

schema-view 可表示各个字段含义,以及在不同组件(表格...)中的作用。

// 当  moduleType == schema 时
schemaConfig: {
  api: '', // 数据源 API (遵循 RESTFUL 规范)
  schema: { // 板块数据结构
    type: 'object',
    properties: {
      key: {
        ...schema, // 标准 schema 配置
        type: '', // 字段类型
        label: '', // 字段的中文名
        // 字段在 table 中的相关配置
        tableOption: {
          ...elTableColumnConfig, // 标准 el-table-column 配置
          toFixed: 0, // 保留几位小数
          visible: true, // 默认为 true (false 时,表示不在表单中显示)
        },
      },
      ...
    },
    required: [], // 必填字段
  },
  tableConfig: {
    rowButtons: [{
      label: '', // 按钮中文名
      eventKey: '', // 按钮事件名
      eventOption: {
        // 当 eventKey === 'remove'
        params: {
          // paramsKey = 参数的键值
          // rowValueKey = 参数值 (当格式为 schema::xxx 的时候,到 table 中找响应的字段)
          paramKey: rowValueKey
        }
      }, // 按钮事件具体配置
     ...elButtonConfig, // 标准 el-button 配置
    }, ...],
  }, // table 相关配置
}

DSL 总结

{
  mode: 'dashboard', // 模板类型,不同模板类型对应不一样的模板数据结构
  name: '', // 名词
  desc: '', // 描述
  icon: '', // icon
  homePage: '', // 首页(项目配置)
  // 头部菜单
  menu: [{
    key: '',  // 菜单唯一描述
    name: '', //菜单名称
    menuType: '', // 枚举值,group / module
    // 当 menuType == group 时,可填
    subMenu: [{
      // 可递归 menuItem
    }, ...],

    // 当 menuType == module 时,可填
    moduleType: '', // 枚举值:sider / iframe / custom / schema

    // 当 moduleType == sider 时
    siderConfig: {
      menu: [{
        // 可递归 menuItem (除 moduleType == sider 外)
      }, ...]
    },
    // 当  moduleType == custom 时
    customConfig: {
      path: '', // 自定义路由路径
    },
    // 当  moduleType == schema 时
    schemaConfig: {
      api: '', // 数据源 API (遵循 RESTFUL 规范)
      schema: { // 板块数据结构
        type: 'object',
        properties: {
          key: {
            ...schema, // 标准 schema 配置
            type: '', // 字段类型
            label: '', // 字段的中文名
            // 字段在 table 中的相关配置
            tableOption: {
              ...elTableColumnConfig, // 标准 el-table-column 配置
              toFixed: 0, // 保留几位小数
              visible: true, // 默认为 true (false 时,表示不在表单中显示)
            },
          },
          ...
        },
        required: [], // 必填字段
      },
      tableConfig: {
        rowButtons: [{
          label: '', // 按钮中文名
          eventKey: '', // 按钮事件名
          eventOption: {
            // 当 eventKey === 'showComponent',
            comName: '', // 组件名称
            // 当 eventKey === 'remove'
            params: {
              // paramsKey = 参数的键值
              // rowValueKey = 参数值 (当格式为 schema::xxx 的时候,到 table 中找响应的字段)
              paramKey: rowValueKey
            }
          }, // 按钮事件具体配置
         ...elButtonConfig, // 标准 el-button 配置
        }, ...],
      }, // table 相关配置
    }
  }, ...]
}

领域模型

通过面向对象模式,来作为系统模型生成机制,能快速且灵活地搭建系统模型对象。

不同领域模型有一个基类,然后继承这个基类延伸具体模型。

image.png

根据 DSL,配置电商系统基类。

  1. 头部菜单:商品管理、订单管理和客户管理。

  2. 商品管理可以配置现成的 schema-view 组件,里面有一个表格,每个字段可以对应表格里面的每一列,操作栏可以配置按钮。(80% 现成的)。

  3. 订单管理和客户管理可以自定义页面(20% 定制化)。

module.exports = {
  model: 'dashboard',
  name: '电商系统',
  menu: [
    {
      key: 'product',
      name: '商品管理',
      menuType: 'module',
      moduleType: 'schema',
      schemaConfig: {
        api: '/api/proj/product',
        schema: {
          type: 'object',
          properties: {
            product_id: {
              type: 'string',
              label: '商品ID',
              tableOption: {
                width: 300,
                'show-overflow-tooltip': true,
              },
            },
            product_name: {
              type: 'string',
              label: '商品名称',
              maxLength: 20,
              minLength: 2,
              tableOption: {
                width: 200,
              },
            },
            price: {
              type: 'number',
              label: '商品价格',
              maximum: 1000,
              minimum: 30,
              tableOption: {
                width: 200,
              },
            },
            inventory: {
              type: 'number',
              label: '库存',
              tableOption: {
                width: 200,
              },
            },
            create_time: {
              type: 'string',
              label: '创建时间',
              tableOption: {},
            },
          },
          required: ['product_name'],
        },
        tableConfig: {
          headerButtons: [
            {
              label: '新增商品',
              eventKey: 'showComponent',
              eventOption: {
                comName: 'createForm',
              },
              type: 'primary',
              plain: true,
            },
          ],
          rowButtons: [
            {
              label: '查看详情',
              eventKey: 'showComponent',
              eventOption: {
                comName: 'detailPanel',
              },
              type: 'primary',
            },
            {
              label: '修改',
              eventKey: 'showComponent',
              eventOption: {
                comName: 'editForm',
              },
              type: 'primary',
            },
            {
              label: '删除',
              eventKey: 'remove',
              type: 'danger',
              eventOption: {
                params: {
                  product_id: 'schema::product_id',
                },
              },
            },
          ],
        },
      },
    },
    {
      key: 'order',
      name: '订单管理',
      menuType: 'module',
      moduleType: 'custom',
      customConfig: {
        path: '/todo',
      },
    },
    {
      key: 'client',
      name: '客户管理',
      menuType: 'module',
      moduleType: 'custom',
      customConfig: {
        path: '/todo',
      },
    },
  ],
};

根据电商系统基类,延伸不同具体电商系统模型。

继承自上面的基类系统,可以延伸出下面这个电商系统。

  1. 头部菜单可以拓展出其他菜单(运营活动)或者修改原有菜单项(订单管理)。
  2. 运营活动可以点击拓展侧边栏菜单,不同侧边菜单栏项可以点击展示不同的组件。
module.exports = {
  name: '某宝',
  desc: '某宝电商系统',
  homePage: '/schema?proj_key=taobao&key=product',
  menu: [{
    key: 'order',
    // iframe 页面
    menuType: 'iframe',
    iframeConfig: {
      path: 'http://www.baidu.com',
    },
  },{
    key: 'operating',
    name: '运营活动',
    menuType: 'module',
    moduleType: 'sider',
    siderConfig: {
      menu: [{
        key: 'coupon',
        name: '优惠券',
        menuType: 'module',
        moduleType: 'custom',
        customConfig: {
          path: '/todo',
        },
      },{
        key: 'limited',
        name: '限量购',
        menuType: 'module',
        moduleType: 'custom',
        customConfig: {
          path: '/todo',
        },
      },{
        key: 'festival',
        name: '节日活动',
        menuType: 'module',
        moduleType: 'custom',
        customConfig: {
          path: '/todo',
        },
      }]
    }
  }]
}

总结

  1. 用 DSL 描述不同模版页,基于面向对象的领域模型结合模版页 DSL 生成具体的系统对象。
  2. 通过解析器去解析这些系统对象,将解析后的配置放入已写完的模版页,就能生成系统的 80% 的页面。这里需要注意的是解析器只需要写一次,模版页也只需要写一次,后面的相关系统都只需要配置好系统对象,就能快速生成系统,这也是这个快速建站框架的目的。

出处:哲玄大佬的大前端全栈实践,以上是自己的见解和总结。