Elpis DSL领域模型设计理念

概述

Elpis是一个企业级应用框架【课程来源:抖音: 哲玄前端(全栈)】,其核心理念是通过DSL(领域特定语言)配置 来沉淀80%的重复工作,同时支持20%的定制化功能。这种设计模式将传统的硬编码开发模式转变为配置驱动的开发模式,大大提高了开发效率和代码复用性。

核心设计理念

1. 80/20原则的应用

Elpis框架通过DSL配置覆盖80%的通用业务场景,剩余的20%通过扩展机制实现定制化需求。这种设计既保证了开发效率,又保持了足够的灵活性。

2. 配置即代码

框架将传统的代码逻辑抽象为配置项,通过JSON Schema等标准化的配置格式来描述业务模型,实现了配置即代码的理念。

架构设计

核心引擎层

Elpis的核心引擎位于elpis-core模块,负责整个应用的启动和模块加载:

javascript 复制代码
module.exports = {
    start(options = {}) {
        const app = new Koa();
        
        // 加载各个模块
        middlewareLoader(app);
        routerSchemaLoader(app);
        controllerLoader(app);
        serviceLoader(app);
        configLoader(app);
        extendLoader(app);
        
        // 注册路由
        routerLoader(app);
        
        return app;
    }
}

代码解析 :核心引擎采用模块化加载器模式,通过统一的接口加载不同类型的模块(中间件、路由、控制器等),实现了高度的解耦和可扩展性。

DSL配置层

路由Schema配置

框架通过JSON Schema来定义API的输入输出规范:

javascript 复制代码
module.exports = {
    '/api/proj/proj/product/list': {
        get: {
            query: {
                type: 'object',
                properties: {
                    page: { type: 'string' },
                    size: { type: 'string' }
                },
                required: ['page', 'size']
            }
        }
    }
}

代码解析 :通过JSON Schema定义API的请求参数结构,配合AJV验证库实现自动参数校验,减少了大量的样板代码。

控制器基类设计

框架提供了统一的控制器基类,标准化API响应格式:

javascript 复制代码
class BaseController {
    success(ctx, data = {}, metadata = {}) {
        ctx.status = 200;
        ctx.body = {
            success: true,
            data,
            metadata
        }
    }
    
    fail(ctx, message, code) {
        ctx.body = {
            success: false,
            message,
            code
        }
    }
}

解析:基类封装了通用的成功/失败响应格式,确保API返回结构的一致性,提高了配置及代码的可行性。

前端DSL渲染引擎

Schema驱动的UI组件

Elpis的前端采用Schema驱动的组件设计,通过配置自动生成UI界面:

js 复制代码
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: '商品名称',
                            tableOption: {
                                width: 200
                            },
                            searchOption: {
                                comType: 'dynamicSelect',
                                api: '/api/proj/product_enum/list'
                            }
                        },
                        price: {
                            type: 'number',
                            label: '价格',
                            tableOption: {
                                width: 200
                            },
                            searchOption: {
                                comType: 'select',
                                enumList: []
                            }
                        },
                        inventory: {
                            type: 'number',
                            label: '库存',
                            tableOption: {
                                width: 200
                            },
                        },
                        create_time: {
                            type: 'string',
                            label: '创建时间',
                            tableOption: {},
                            searchOption: {
                                comType: 'dateRange'
                            }
                        }
                    }
                },
                tableConfig: {
                    headerButtons: [
                        {
                            label: '新增商品',
                            eventKey: 'showComponent',
                            type: 'primary',
                            plain: true
                        }
                    ],
                    rowButtons: [
                        {
                            label: '修改',
                            eventKey: 'showComponent',
                            type: 'warning'
                        },
                        {
                            label: '删除',
                            eventKey: 'remove',
                            eventOption: {
                                params: {
                                    product_id: 'schema::product_id'
                                }
                            },
                            type: 'danger'
                        }
                    ]
                }
            }
        },
        {
            key: 'order',
            name: '订单管理',
            menuType: 'module',
            moduleType: 'custom',
            customConfig: {
                path: '/todo'
            }
        },
        {
            key: 'client',
            name: '客户管理',
            menuType: 'module',
            moduleType: 'custom',
            customConfig: {
                path: '/todo'
            }
        },
    ]
}

解析: 以上为配置的根源,也可以称为某一个领域模型的基类,接口地址、导航栏、侧边栏、搜索栏、数据展示... 均来源于此文件 , 我们甚至还可以使用面向对象的理念基于当前文件配置 + 差异化的配置生成不同的实例领域。

javascript 复制代码
export const useSchema = function () {
    const buildDtoSchema = (_schema, comName) => {
        if (!_schema?.properties) { return {} }
        
        const dtoSchema = {
            type: 'object',
            properties: {}
        }
        
        // 提取有效 schema 字段信息
        for(const key in _schema.properties) {
            const props = _schema.properties[key];
            if (props[`${comName}Option`]) {
                let dtoProps = {};
                
                for (const pKey in props) {
                    if (pKey.indexOf('Option') < 0) {
                        dtoProps[pKey] = props[pKey];
                    }
                }
                
                dtoProps = Object.assign({}, dtoProps, {option: props[`${comName}Option`]})
                dtoSchema.properties[key] = dtoProps;
            }
        }
        
        return dtoSchema;
    }
}

解析 :通过解析Schema配置, 输出页面上各个功能区所需要的不同的配置,实现了配置驱动的UI渲染

通用组件

将以上的配置交由页面中的公共组件进行读取,产生真实的 UI(以下以表格为例)。

ini 复制代码
const props = defineProps({
	//  schema 配置
    schema: {
        type: Object,
        default: () => ({})
    },
    /**
     * 表格数据源 api
     */
    api: {
        type: String,
        default: ''
    },
    /**
     * api 请求参数,请求 API 时携带
     */
    apiParams: {
        type: Object,
        default: () => ({})
    }
})

// 根据配置中的 api 地址获取表格数据
const fetchTableData = async () => {
    if (!api.value) return;

    if (loading.value) return;

    showLoading();

    // 请求table 数据
    const res = await $curl({
        method: 'get',
        url: `${api.value}/list`,
        query: {
            ...apiParams.value,
            page: currentPage.value,
            size: pageSize.value,
        }
    });

    hideLoading();

    if (!res || !res.success || !Array.isArray(res.data)) {
        tableData.value = [];
        total.value = 0;
        return;
    }
    
    tableData.value = buildTableData(res.data);
    total.value = res.metadata.total;
}

代码解析:根据配置中的借口 url 请求数据,将数据交由 html 进行渲染

vue 复制代码
<template>
    <el-table v-if="schema && schema.properties" :data="tableData">
        <template v-for="(schemaItem, key) in schema.properties">
            <el-table-column
                v-if="schemaItem.option.visible !== false"
                :key="key"
                :prop="key"
                :label="schemaItem.label"
                v-bind="schemaItem.option"
            ></el-table-column>
        </template>
    </el-table>
</template>

代码解析:表格组件通过读取Schema配置,根据已获取的数据 + 字段配置项自动生成列, 同时也支持每行数据的自定义操作, 大大减少了前端重复有繁琐的搬砖工作。

模块化加载机制

自动发现与加载

框架实现了智能的模块发现和加载机制:

javascript 复制代码
module.exports = (app) => {
    const controllerPath = path.resolve(app.businessPath, `.${sep}controller`);
    const fileList = glob.sync(path.resolve(controllerPath, `.${sep}**${sep}**.js`));
    
    fileList.forEach(file => {
        // 自动解析文件路径并转换为命名空间
        let name = path.resolve(file);
        name = name.substring(name.lastIndexOf(`controller${sep}`) + `controller${sep}`.length, name.lastIndexOf('.'));
        name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
        
        // 动态加载控制器
        const ControllerModule = require(path.resolve(file))(app);
        tempController[names[i]] = new ControllerModule();
    });
}

代码解析 :通过文件系统扫描自动发现模块,并将文件路径转换为JavaScript命名空间,实现了约定优于配置的开发模式。

扩展性设计

对于20%的定制化需求,框架提供了扩展机制:

页面级扩展

javascript 复制代码
// 可以自定义路由进行定制化页面扩展
routes.push({
    path: '/view/dashboard/todo',
    component: () => import('./todo/todo.vue')
})

组件级扩展

js 复制代码
// 同时可以自定义组件,针对不同的功能进行扩展
import input from './complex-view/input/input.vue';
import select from './complex-view/select/select.vue';
import dynamicSelect from './complex-view/dynamic-select/dynamic-select.vue';
import dateRange from './complex-view/date-range/date-range.vue';

const SearchItemConfig = {
    input: {
        component: input,
    },
    select: {
        component: select,
    },
    dynamicSelect: {
        component: dynamicSelect,
    },
    dateRange: {
        component: dateRange,
    },
}

export default SearchItemConfig;

开发效率提升

1. 配置驱动开发

  • 减少样板代码:通过Schema配置自动生成CRUD操作
  • 统一开发规范:标准化的API响应格式和错误处理
  • 快速原型开发:通过配置快速搭建功能原型

2. 组件复用

  • 通用组件库:提供Schema表格、搜索表单等通用组件
  • 配置化定制:通过配置实现组件的个性化定制
  • 零代码渲染:大部分UI通过配置自动生成

3. 自动化工具链

  • 模块自动加载:基于文件系统的自动模块发现
  • 参数自动校验:基于JSON Schema的自动参数验证
  • 路由自动注册:基于配置的自动路由生成

总结

Elpis框架通过DSL领域模型的设计,成功实现了配置驱动的开发模式。这种设计不仅提高了开发效率,还保证了代码质量和一致性。通过80/20原则的合理应用,框架既满足了大部分通用需求,又为特殊场景提供了足够的扩展空间。

核心价值

  • 配置即代码,提高开发效率和代码复用性
  • 模块化设计,支持灵活扩展和定制
  • 标准化规范,保证代码质量和团队协作效率
相关推荐
zhangxingchao7 分钟前
Flutter与H5页面的交互
前端
粥里有勺糖10 分钟前
视野修炼第124期 | 终端艺术字
前端·javascript·github
zhangxingchao26 分钟前
Flutter常见Widget的使用
前端
aiweker33 分钟前
python web开发-Flask数据库集成
前端·python·flask
暴怒的代码39 分钟前
解决Vue2官网Webpack源码泄露漏洞
前端·webpack·node.js
老刘忙Giser1 小时前
C# Process.Start多个参数传递及各个参数之间的空格处理
java·前端·c#
阿珊和她的猫1 小时前
组件之间的双向绑定:v-model
前端·javascript·vue.js·typescript
爱分享的程序员2 小时前
Node.js 实训专栏规划目录
前端·javascript·node.js
阿迪州2 小时前
iframe作为微前端方案的几个问题
前端·面试
我就是避雷针小鬼啊2 小时前
vue2组件库规划
前端