概述
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原则的合理应用,框架既满足了大部分通用需求,又为特殊场景提供了足够的扩展空间。
核心价值:
- 配置即代码,提高开发效率和代码复用性
- 模块化设计,支持灵活扩展和定制
- 标准化规范,保证代码质量和团队协作效率