前言
领域驱动设计(简称 ddd)概念来源于2004年著名建模专家Eric Evans 发表的他最具影响力的书籍:《领域驱动设计——软件核心复杂性应对之道》(Domain-Driven Design –Tackling Complexity in the Heart of Software),简称Evans DDD,领域驱动设计思想进入软件开发者的视野。在将近20年的发展中领域模型设计一直占据着非常重要的位置,但其直接面向业务场景的设计思想,更适合在具有特定业务场景的模型构建。在日常我们见到的DDD模型多数是具有特定业务背景的特定业务(Domain-Specific Modeling)特定领域建模工具。
近两年,随着新一代WEB技术以及,微服务、中台技术,云原生应用的推广;领域驱动模型(DDD)再次成为软件设计领域的热点。 而低代码/无代码平台是进近几年持续高速发展的一个技术领域。
一,OneCode-工具集 简介
OneCode-DSM**(以下简称DSM)**工具集是建立是以OneCode低代码引擎为基础专注于低代码建模应用的高阶建模工具。
在OneCode引擎中,出了为普通用户提供无代码的拖动设计器,低代码的业务逻辑编排器,之外还提供了供专业业务领域专家的使用的DSM建模工具。
OneCode-DSM 应用
(1)可视化设计器
以可视化设计器引擎为主体的表单报表工具,在日常常用的表单报表中是以无代码的方式来实现业务流审批以及数据大屏展现设计,移动展现等应用。
(2)低代码服务集成工具
(3)DSM建模工具
二,OneCode-DSM工具
(1)工具组成概览
DSM建模工具是OneCode建模的辅助工具,提供了资源库管理模块,领域模型构建模块,以及视图工厂配置模块。
仓储模型模块,主要功能是辅助用户将用户的数据库,外部API接口,以及已有的“代码”应用通过转换器转变为可被DSM识别的资源部格式。
领域模型模块是DSM核心工具,在领域模型中导入的资源会同具体场景下的值对象,场景菜单、通用域服务根据具体的业务场景完成领域模型的建模工作。
视图工厂是领域模型的具体实现,在领域模型应用中建模输出的产物会通过出码工厂输出位视图应用,这些视图应用会通过视图工厂进一步加工处理输出为用户交互应用。
(2)OneCode语言(注解)组成
(3)建模流程
三,仓储库建模
(1)通过数据库建模
仓储工具中使用频率最高的是数据库的转换应用,用户通过数据库工具完成数据源配置。
数据源配置
库表映射代码示例:
1
|
@DBTable(configKey="fdt",cname="领导信息",tableName="FDT_LINGDAO",primaryKey="uuid") @Repository(repositoryId="54fa1fd2-cae4-44b0-8387-f10f98bdd63c") public interface FdtLingdao { @Uid @DBField(cnName="主键",dbFieldName="uuid") public String getUuid(); public void setUuid(String uuid); @CustomAnnotation(caption="名称") @DBField(cnName="名称",length=20,dbFieldName="test") public String getTest(); public void setTest(String test); @CustomAnnotation(caption="领导姓名") @DBField(cnName="领导姓名",length=20,dbFieldName="name") public String getName(); public void setName(String name); @CustomAnnotation(caption="职务") @DBField(cnName="职务",length=100,dbFieldName="duty") public String getDuty(); public void setDuty(String duty); @CustomAnnotation(caption="创建时间") @DBField(cnName="创建时间",length=0,dbType=ColType.DATETIME,dbFieldName="createtime") public Date getCreatetime(); public void setCreatetime(Date createtime); }
|
库表装载器CRUD代码示例:
1
|
@Aggregation(type = AggregationType.aggregationEntity,entityClass=FdtLingdao.class,rootClass= FdtLingdaoService.class) public interface FdtLingdaoService { @APIEventAnnotation(bindMenu = {CustomMenuItem.reload}) public List<FdtLingdao> findAll()throws JDSException; public List<FdtLingdao> findByWhere(String where)throws JDSException; @APIEventAnnotation(bindMenu = {CustomMenuItem.search}) public List<FdtLingdao> find(FdtLingdao fdtLingdao)throws JDSException; @APIEventAnnotation(bindMenu = {CustomMenuItem.add,CustomMenuItem.editor}) public FdtLingdao getFdtLingdaoInfo(String uuid) throws JDSException; @APIEventAnnotation(bindMenu = {CustomMenuItem.save}, callback = {CustomCallBack.ReloadParent, CustomCallBack.Close}) public FdtLingdao update( FdtLingdao fdtLingdao) throws JDSException; @APIEventAnnotation(bindMenu = {CustomMenuItem.delete}, callback = {CustomCallBack.Reload}) public Boolean delete(String uuid) throws JDSException; }
|
(2)通过实体建模
仓储实体实例
1
|
@Entity @Aggregation(type = AggregationType.aggregationRoot,sourceClass = Role.class, rootClass = Role.class) public interface Role extends java.io.Serializable { @MethodChinaName(cname = "角色标识") @Uid public String getRoleId(); public void setRoleId(String roleId); @MethodChinaName(cname = "名称") @Caption public String getName(); public void setName(String name); @MethodChinaName(cname = "角色类型") public RoleType getType(); public void setType(RoleType type); @MethodChinaName(cname = "级数") public String getRoleNum(); public void setRoleNum(String num); @MethodChinaName(cname = "系统ID") @Pid public String getSysId(); public void setSysId(String sysId); @MethodChinaName(cname = "人员") @Ref(ref = RefType.m2m, view = ViewType.grid) public List<Person> getPersonList(); @MethodChinaName(cname = "部门") @Ref(ref = RefType.m2m, view = ViewType.grid) public List<Org> getOrgList(); public List<String> getOrgIdList(); }
|
(3) 实体关系
仓储建模的一个核心目的是将结构化的数据转变为面向对象的模式,而这其中非常重要的一点则是实体关系的处理,DSM设计中针对数据库表允许用户在导入数据库后再次进行实体关系建模,将数据库表按 1:1 ,1:N, N:N模型建立关系。完成建模后在出码的过程中会根据业务模板设定,进行实体模型的转变,在实体代码中以 @Ref 关系标签完成建模应用。
数据库模型关系 | 实体关系 | 实体注解配置 |
1:N | 一对多 | @Ref(ref = RefType.o2m) |
N:N | 多对多 | @Ref(ref = RefType.m2m) |
1:1 | 一对一 | @Ref(ref = RefType.o2o) |
| 引用 | @Ref(ref = RefType.ref) |
| 查找 | @Ref(ref = RefType.) |
实体模型关系在基础的模型上根据仓储模型设计,独立扩展了
1
|
public enum RefType implements IconEnumstype { ref("引用", "spafont spa-icon-c-databinder"), o2m("一对多", "spafont spa-icon-c-treeview"), m2o("多对一", "spafont spa-icon-alignwh"), f2f("自循环", "spafont spa-icon-cancel"), o2o("一对一", "spafont spa-icon-hmirror"), find("查找", "xui-icon-search"), m2m("多对多", "spafont spa-icon-alignwh"); private final String imageClass; private final String name; RefType(String name, String imageClass) { this.name = name; this.imageClass = imageClass; } }
|
(4) 仓储建模模板
(5)仓储模型常用注解
注解名称 | 用途 | 实例 |
@DBTable | 数据库表映射配置主要包含了,数据源标识,表名称以及主键信息 | @DBTable(configKey="fdt",tableName="FDT_LINGDAO",primaryKey="uuid") |
@DBField | 数据库字段配置 | @DBField(cnName="创建时间",length=0,dbType=ColType.DATETIME,dbFieldName="createtime") |
@Uid | 实体字段,在数据库实体中一般标识为主键,在DDD模型中作为唯一值 | @Uid |
@Pid | 父级组件字段,通常在关系实体中用于标识父级对象的主键 | @Pid |
@CustomAnnotation | 常用实体注解,注解属性中会包括,字段的展示类型,可读属性,展示注解等。 | @CustomAnnotation(caption="职务") |
@Caption | 标题注解一般作用在表格行数据的展示中作为默认显示字段,如Person (人员对象中)会将name作为默认展示选项 | @Caption |
@Ref | 实体关系属性 | @Ref(ref = RefType.m2m, view = ViewType.grid) |
@APIEventAnnotation | API服务注解,是对外服务的标识。添加该注解后,在后续的模型建模中会对应转换为Web API服务 | @APIEventAnnotation(bindMenu = {CustomMenuItem.search}) |
四,领域建模
(1)领域建模组成
(2)领域建模原理
模型渲染原理
OneCode 本身基于JAVA语言体系,是在Java Spring 注解基础上的一套扩展子集,混合编译引擎器通过扩展注解构建完整的Domain模型,通过读取标准Spring 注解完成普通Web数据交付及调度过程。
示例注解说明:
完整示例代码:
1
|
@Controller @RequestMapping("/admin/org/deparment/") @MethodChinaName(cname = "部门管理", imageClass = "bpmfont bpmgongzuoliuzuhuzicaidan") @Aggregation(sourceClass = IDeparmentService.class, rootClass = Org.class) public interface IDeparmentAPI { @RequestMapping(method = RequestMethod.POST, value = "Persons") @GridViewAnnotation() @ModuleAnnotation(caption = "人员列表") @APIEventAnnotation(bindMenu = {CustomMenuItem.treeNodeEditor}) @ResponseBody public <K extends IPersonGrid> ListResultModel<List<K>> getPersons(String orgId); @RequestMapping(method = RequestMethod.POST, value = "loadChild") @APIEventAnnotation(bindMenu = {CustomMenuItem.loadChild}) @ResponseBody public <T extends IDeparmentTree> ListResultModel<List<T>> loadChild(String parentId); @MethodChinaName(cname = "部门信息") @RequestMapping(method = RequestMethod.POST, value = "DeparmentInfo") @NavGroupViewAnnotation(saveUrl = "admin.org.deparment.saveOrg") @DialogAnnotation @ModuleAnnotation(caption = "编辑部门信息", width = "600", height = "480") @APIEventAnnotation(callback = {CustomCallBack.ReloadParent, CustomCallBack.Close}, bindMenu = {CustomMenuItem.editor}) @ResponseBody public <K extends IDeparmentNav> ResultModel<K> getDeparmentInfo(String orgId); @MethodChinaName(cname = "添加部门") @RequestMapping(method = RequestMethod.POST, value = "addDeparment") @FormViewAnnotation() @DialogAnnotation @ModuleAnnotation(caption = "添加部门", width = "350", height = "260") @APIEventAnnotation(bindMenu = {CustomMenuItem.add}, autoRun = true) @ResponseBody public <M extends IAddDeparmentForm> ResultModel<M> addDeparment(String parentId); @MethodChinaName(cname = "保存机构信息") @RequestMapping(value = {"saveOrg"}, method = {RequestMethod.GET, RequestMethod.POST}) @APIEventAnnotation(callback = {CustomCallBack.ReloadParent, CustomCallBack.Close}, bindMenu = {CustomMenuItem.save}) public @ResponseBody ResultModel<Boolean> saveOrg(@RequestBody IAddDeparmentForm deparmentForm); @MethodChinaName(cname = "删除部门") @RequestMapping(value = {"delOrg"}, method = {RequestMethod.GET, RequestMethod.POST}) @APIEventAnnotation(callback = {CustomCallBack.Reload, CustomCallBack.ReloadParent}, bindMenu = {CustomMenuItem.delete}) public @ResponseBody ResultModel<Boolean> delOrg(String orgId); ; }
|
(3)聚合配置
建模配置
接口配置
窗体配置
执行事件传递
(4)聚合分类
(5)聚合根
聚合根设计,通常来标识其独立的域应用,具有全局唯一性的特点。 在 通用域中,具有时间轴维度的对象描述,如工作流中的流程示例(ProcessInst)对象。在获取聚合根后,可以依据时间轴一次向前获取,流程定义对象,向后获取可以取得流程历史数据,而根据当前节点则可以获取权限,数据表单、业务示例状态的等等实体数据。
流程示例聚合根配置
流程实例聚合根源码
1
|
@Entity @MethodChinaName(cname = "流程实例") @Aggregation(type = AggregationType.aggregationRoot, sourceClass = ProcessInst.class, rootClass = ProcessInst.class) public interface ProcessInst extends java.io.Serializable { @MethodChinaName(cname = "流程实例UUID") @Uid public String getProcessInstId(); @MethodChinaName(cname = "流程定义UUID") @Pid public String getProcessDefId(); @MethodChinaName(cname = "流程定义版本UUID") @Pid public String getProcessDefVersionId(); @MethodChinaName(cname = "流程实例名称") public String getName(); @MethodChinaName(cname = "紧急程度") public String getUrgency(); @MethodChinaName(cname = "流程实例状态") public ProcessInstStatus getState(); @MethodChinaName(cname = "流程实例副本数量") public int getCopyNumber(); @MethodChinaName(cname = "程实例启动时间") public Date getStartTime(); @MethodChinaName(cname = "流程实例办结时间") public Date getEndTime(); @MethodChinaName(cname = " 流程实例时间限制") public Date getLimitTime(); @MethodChinaName(cname = "流程实例状态") public ProcessInstStatus getRunStatus(); @MethodChinaName(cname = "流程定义版本") @Ref(ref = RefType.m2o, view = ViewType.grid) public ProcessDefVersion getProcessDefVersion() throws BPMException; @MethodChinaName(cname = "流程定义") @Ref(ref = RefType.m2o, view = ViewType.dic) public ProcessDef getProcessDef() throws BPMException; @MethodChinaName(cname = "活动实例") @Ref(ref = RefType.o2m, view = ViewType.grid) public List<ActivityInst> getActivityInstList() throws BPMException; @MethodChinaName(cname = "流程属性值", returnStr = "getWorkflowAttribute($R('attName'))", display = false) public Object getWorkflowAttribute(ProcessInstAtt name); @MethodChinaName(cname = "权限属性值", returnStr = "getRightAttribute($R('attName'))", display = false) public Object getRightAttribute(ProcessInstAtt name); @MethodChinaName(cname = "应用属性值", returnStr = "getAppAttribute($R('attName'))", display = false) public Object getAppAttribute(ProcessInstAtt name); @MethodChinaName(cname = "定制属性值", returnStr = "getAttribute($R('attName'))", display = false) public String getAttribute(String name); @MethodChinaName(cname = "取得流程中的所有属性值", returnStr = "getAllAttribute()", display = false) @Ref(ref = RefType.o2m, view = ViewType.grid) public List<AttributeInst> getAllAttribute(); @MethodChinaName(cname = "个人定制属性值", returnStr = "getAttribute($R('personId'),$R('attName'))", display = true) public String getPersonAttribute(String personId, String name); @MethodChinaName(cname = "设置定制属性", returnStr = "setAttribute($R('attName'),$R('value'))", display = false) public void setAttribute(String name, String value) throws BPMException; @MethodChinaName(cname = "设置个人定制属性", returnStr = "setAttribute($R('personId'),$R('attName'),$R('value'))", display = false) public void setPersonAttribute(String personId, String name, String value) throws BPMException; @MethodChinaName(cname = "更新流程实例名称(公文标题)", returnStr = "updateProcessInstUrgency($R('processInstName'))") public ReturnType updateProcessInstName(String name) throws BPMException; @MethodChinaName(cname = "更新流程实例紧急程度", returnStr = "updateProcessInstUrgency($R('urgency'))") public ReturnType updateProcessInstUrgency( String urgency) throws BPMException; @MethodChinaName(cname = "流程实例挂起", returnStr = "suspendProcessInst()", display = false) public ReturnType suspendProcessInst() throws BPMException; @MethodChinaName(cname = "继续流程实例", returnStr = "resumeProcessInst()", display = false) public ReturnType resumeProcessInst() throws BPMException; @MethodChinaName(cname = "取得活动的历史数据, 根据流程实例") public List<ActivityInstHistory> getActivityInstHistoryListByProcessInst() throws BPMException; @MethodChinaName(cname = "中止流程实例", returnStr = "abortProcessInst()", display = false) public ReturnType abortProcessInst() throws BPMException; @MethodChinaName(cname = "流程实例完成", returnStr = "completeProcessInst()", display = false) public ReturnType completeProcessInst() throws BPMException; @MethodChinaName(cname = "删除流程实例", returnStr = "deleteProcessInst()", display = false) public ReturnType deleteProcessInst() throws BPMException; @MethodChinaName(cname = "获取表单数据") public DataMap getFormValues() throws BPMException; @MethodChinaName(cname = "更新表单数据") public void updateFormValues(DataMap dataMap) throws BPMException; }
|
(6)聚合实体
聚合实体,通常用来描述独立实体机构,例如业务表单中单表或简单关联表关系。通常只包含简单的值关系,功能上也仅限于,查询列表、保存表单等简单应用。
单表表单
代码示例
1
|
@Controller @RequestMapping("/test/fdtlingdaoservice/") @Aggregation(type=AggregationType.aggregationEntity,sourceClass=FdtLingdaoService.class) public interface FdtLingdaoAPI { @APIEventAnnotation(bindMenu=CustomMenuItem.search) @RequestMapping(value="searchUrl") @ModuleAnnotation @GridViewAnnotation @ResponseBody public ListResultModel<List<FindGridView>> find (FdtLingdao fdtLingdao); @APIEventAnnotation(bindMenu={CustomMenuItem.add,CustomMenuItem.editor}) @RequestMapping(value="addPath") @ModuleAnnotation @FormViewAnnotation @ResponseBody public ResultModel<GetFdtLingdaoInfoView> getFdtLingdaoInfo (String uuid); @APIEventAnnotation(bindMenu=CustomMenuItem.save,callback={CustomCallBack.ReloadParent,CustomCallBack.Close}) @RequestMapping(value="update") @ResponseBody public ResultModel<FdtLingdao> update (@RequestBody FdtLingdao fdtLingdao); @RequestMapping(value="findByWhere") @ResponseBody public ListResultModel<List<FdtLingdao>> findByWhere (String where); @APIEventAnnotation(bindMenu=CustomMenuItem.delete,callback=CustomCallBack.Reload) @RequestMapping(value="delete") @ResponseBody public ResultModel<Boolean> delete (String uuid); @APIEventAnnotation(bindMenu=CustomMenuItem.reload) @RequestMapping(value="dataUrl") @ModuleAnnotation @GridViewAnnotation @ResponseBody public ListResultModel<List<FindAllGridView>> findAll (); }
|
(6)动作菜单
动作菜单在DDD模型中并未定义,但在低代码应用却有着很重要的一席。
动作菜单建模中,主要将菜单展现与关联动作结合在一起。
工作流发送菜单建模
菜单展现位置
编辑器右键菜单
常用菜单注解
注解 | 位置 | 示例 |
@ToolBarMenu | 容器顶部工具栏 | @ToolBarMenu(hAlign = HAlignType.left, handler = false, menuClass = JavaRepositoryEditorTools.class) |
@MenuBarMenu | 顶部菜单栏 | @MenuBarMenu(menuType = CustomMenuType.sub, caption = "新建", imageClass = "xuicon xui-uicmd-add", index = 5) |
@BottomBarMenu | 容器底部按钮栏 | @BottomBarMenu(menuClass = CustomBuildAction.class) |
@PageBar | 分页栏 | @PageBar(pageCount = 100) |
@GridRowCmd | 列表行操作按钮栏 | @GridRowCmd(tagCmdsAlign = TagCmdsAlign.left, menuClass = {AttachMentService.class}) |
@RightContextMenu | 右键菜单栏 | @RightContextMenu(menuClass = JavaViewPackageMenu.class) |
1
|
@Controller @RequestMapping(value = {"/java/agg/context/"}) @MenuBarMenu(menuType = CustomMenuType.component, caption = "菜单") @Aggregation(type = AggregationType.menu) public class JavaAggPackageMenu { @RequestMapping(method = RequestMethod.POST, value = "paste") @CustomAnnotation(imageClass = "spafont spa-icon-paste", index = 1, caption = "粘贴") @APIEventAnnotation(customRequestData = {RequestPathEnum.treeview, RequestPathEnum.sTagVar}, callback = CustomCallBack.TreeReloadNode) public @ResponseBody TreeListResultModel<List<JavaAggTree>> paste(String sfilePath, String packageName, String domainId, String projectName) { TreeListResultModel<List<JavaAggTree>> result = new TreeListResultModel<List<JavaAggTree>>(); try { DSMFactory dsmFactory = DSMFactory.getInstance(); File desFile = new File(sfilePath); DomainInst domainInst = dsmFactory.getAggregationManager().getDomainInstById(domainId); JavaSrcBean srcBean = dsmFactory.getTempManager().genJavaSrc(desFile, domainInst, null); DSMFactory.getInstance().getBuildFactory().copy(srcBean, packageName); result.setIds(Arrays.asList(new String[]{domainId + "|" + packageName})); } catch (Exception e) { e.printStackTrace(); } return result; } @RequestMapping(method = RequestMethod.POST, value = "reLoad") @CustomAnnotation(imageClass = "xuicon xui-refresh", index = 1, caption = "刷新") @APIEventAnnotation(customRequestData = RequestPathEnum.treeview, callback = CustomCallBack.TreeReloadNode) public @ResponseBody TreeListResultModel<List<JavaAggTree>> reLoad(String packageName, String domainId, String filePath) { TreeListResultModel<List<JavaAggTree>> result = new TreeListResultModel<List<JavaAggTree>>(); String id = domainId + "|" + packageName; result.setIds(Arrays.asList(new String[]{id})); return result; } @RequestMapping(value = {"split"}) @Split @CustomAnnotation(index = 2) @ResponseBody public ResultModel<Boolean> split2() { ResultModel<Boolean> result = new ResultModel<Boolean>(); return result; } @RequestMapping(value = "UploadFile") @APIEventAnnotation(autoRun = true) @DialogAnnotation(width = "450", height = "380", caption = "上传JAVA文件") @ModuleAnnotation @FormViewAnnotation @CustomAnnotation(caption = "上传", index = 3, imageClass = "xui-icon-upload", tips = "上传") public @ResponseBody ResultModel<UPLoadFile> uploadFile(String domainId, String packageName) { ResultModel<UPLoadFile> resultModel = new ResultModel<UPLoadFile>(); try { UPLoadFile upLoadFile = new UPLoadFile(domainId, packageName); resultModel.setData(upLoadFile); } catch (Exception e) { e.printStackTrace(); } return resultModel; } @RequestMapping(value = {"split"}) @Split @CustomAnnotation(index = 4) @ResponseBody public ResultModel<Boolean> split6() { ResultModel<Boolean> result = new ResultModel<Boolean>(); return result; } @RequestMapping(method = RequestMethod.POST, value = "newApp") @CustomAnnotation(imageClass = "xuicon xui-uicmd-add", index = 5) @MenuBarMenu(menuType = CustomMenuType.sub, caption = "新建", imageClass = "xuicon xui-uicmd-add", index = 5) public JavaAggNewMenu getJavaJarAction() { return new JavaAggNewMenu(); } @RequestMapping(method = RequestMethod.POST, value = "importAgg") @CustomAnnotation(imageClass = "xuicon xui-uicmd-add", index = 6) @MenuBarMenu(menuType = CustomMenuType.sub, caption = "导入", imageClass = "spafont spa-icon-html", index = 5) public JavaAggImportMenu importAgg() { return new JavaAggImportMenu(); } @RequestMapping(value = {"split"}) @Split @CustomAnnotation(index = 7) @ResponseBody public ResultModel<Boolean> split7() { ResultModel<Boolean> result = new ResultModel<Boolean>(); return result; } @MethodChinaName(cname = "删除") @RequestMapping(method = RequestMethod.POST, value = "delete") @CustomAnnotation(imageClass = "xuicon xui-icon-minus", index = 8) @APIEventAnnotation(customRequestData = RequestPathEnum.treeview, callback = CustomCallBack.TreeReloadNode) public @ResponseBody TreeListResultModel<List<JavaAggTree>> delete(String domainId, String parentId, String filePath, String javaTempId) { TreeListResultModel<List<JavaAggTree>> result = new TreeListResultModel<List<JavaAggTree>>(); try { File desFile = new File(filePath); DSMFactory dsmFactory = DSMFactory.getInstance(); DomainInst domainInst = dsmFactory.getAggregationManager().getDomainInstById(domainId); if (desFile.exists()) { if (desFile.isDirectory()) { JavaPackage javaPackage = domainInst.getPackageByFile(desFile); dsmFactory.getTempManager().deleteJavaPackage(javaPackage); } else { JavaSrcBean srcBean = dsmFactory.getTempManager().genJavaSrc(desFile, domainInst, javaTempId); dsmFactory.getTempManager().deleteJavaFile(srcBean); } } result.setIds(Arrays.asList(new String[]{parentId})); } catch (JDSException e) { e.printStackTrace(); } return result; } public ESDChrome getCurrChromeDriver() { Object handleId = JDSActionContext.getActionContext().getParams("handleId"); ChromeDriver chrome = null; if (handleId != null) { chrome = ESDEditor.getInstance().getChromeDriverById(handleId.toString()); } if (chrome == null) { chrome = ESDEditor.getInstance().getCurrChromeDriver(); } return new ESDChrome(chrome); } }
|
(7)通用域
通用域将系统中常用服务进行了独立分类可以在工程构建时导入进来。
通用域管理
(8)API服务接口
api服务接口是手工代码接入的域服务,在普通java类上加上聚和接口后会统一归类到该类型管理。
(9)领域模型常用注解
注解名称 | 用途 | 实例 |
@RequestMapping | 直接使用的SpringMvc注解用于将当前方法标识为,web可访问 | @RequestMapping(value = {"AggAPITree"}, method = {RequestMethod.GET, RequestMethod.POST}) |
@ModuleAnnotation | 视图标识,在方法上标识改注解后会被模型编译器识别为视图模型将其内部对象渲染为视图。 | @ModuleAnnotation(dynLoad = true, imageClass = "spafont spa-icon-moveforward", caption = "模块授权") |
@ResponseBody | 直接使用的SpringMvc注解,标识为JSON数据返回 | @ResponseBody |
@DialogAnnotation | 添加该标识时,当前端路由到当前方法时,以独立窗口的方式返回 | @DialogAnnotation(width = "850", height = "750") |
@Aggregation | 领域标识,在类注解中添加该标识,会被DSM引擎自动索引并根据注解中指定类型加载到相关的实体列表中 | @Aggregation(type = AggregationType.customDomain,sourceClass = PersonService.class,rootClass = Person.class) |
@*Domain | 通用域标识 | @OrgDomain@BpmDomain@VfsDomain@MsgDomain@NavDomain |
@*TreeView | 树形注解包括了,导航树、弹出字典树,折叠分组树等注解集合 | @TreeViewAnnotation@NavTreeViewAnnotation@NavFoldingTreeViewAnnotation@PopTreeViewAnnotation |
@GridViewAnnotation | 数据列表注解 | |
@GalleryView*Annotation | 详情图形混合注解 | @GalleryViewAnnotation@NavGalleryViewAnnotation |
@*TabsViewAnnotation | Tab切换页 | @TabsViewAnnotation@NavTabsViewAnnotation@NavFoldingTabsViewAnnotation |
@PopMenuViewAnnotation | 菜单导航 | @PopMenuViewAnnotation |
@NavGroupViewAnnotation | 分组表单 | @NavGroupViewAnnotation |
@FormViewAnnotation | 表单注解 | @FormViewAnnotation |
@*ButtonViewsViewAnnotation | 按钮栏视图 | @ButtonViewsAnnotation@NavButtonViewsAnnotation |
在领域驱动设计(Domain-Driven Design以下简称DDD)中,面向用户的视图层设计,由于其实现方式的多样性以及本身技术复杂度,在实际设计中总是被选择性的遗忘。但在低代码技术突飞猛进的今天,DDD又以全新的姿态进入到了低代码领域。本节我们会在OneCode-dsm领域模型的基础上介绍OneCode视图工厂的相关功能。
本文是OneCode,领域驱动设计的第二个章节,如果您第一次阅读本文。需要先阅读以下两个章节以便于理解(附链接)。
您如果对本文的技术实现更感兴趣并且有一定的编程个基础可以,关注作者,本文用例以及环境代码会同期发布在gitee 开源代码社区。
OneCode低代码引擎,领域驱动设计(DDD)技术实践(一)
五,视图工厂简介
OneCode视图工厂(以下简称ViewFactory),是OneCodeDDD领域驱动设计(DSM)的核心组件,其主要设计目的有两个:
一是针对,领域模型设计器形成的设计模型,根据具体的业务环境进行视图的分解以及领域事件的合并绑定,最终组合输出为可被OneCode设计器兼容的视图文件并作为最终工程输出。
ViewFactory另外一个设计用途是将开发者通过低代码可视化设计器设计的视图页面,通过视图工厂进行逆向转换生成“后端网站地图”进行领域模型的二次绑定或者手工编写后端实现代码。
(1),视图工厂运行原理
在领域工厂中,更多是将贫血性的基础实体对象进行聚合分类整理,形成更利于业务理解与操作的充血模型,并且通过在其接口模型上扩展注解的方式实现其低耦合应用。视图工厂中仍然延续这一风格设计将普通单一的组件通过,后端JAVA代码的聚合将常用功能以及辅助组件进行业务封装形成独立的视图组件。 视图工厂同样也是建立在OneCode语法基础上的扩展注解,通过OneCode编译器最终输出为能够被设计器以及前端框架所识别的JSON代码。
OneCode代码转换实例图
简单列表转换示意
表单渲染
(2),视图工厂设计目的
在视图设计建模中,最容易混淆的一个问题就是,视图设计器与视图工厂的区别是什么?有了视图设计器为什么,还需要视图工厂来建模?视图设计器通过可视化的方式,降低了开发者上手的门槛。但编程本身不单单是一种基础的技能,还需要将业务逻辑,技术技巧进行灵活运用,再分发到实际环境中进一步磨炼,重构,最终形成软件的 “灵魂”业务&技术架构。
这对低代码视图设计方面提出了更高的技术要求:
从业务架构方面能更好的来理解业务语言,诸如:根据业务实体,自动来匹配一张视图。当业务实体发生变更时,自动更新视图。当业务实体的属性发生变化时自动匹配一种更适合用户交互的输入输出方式,如:预警信息显示,红色代表严重、黄色代表警告,绿色代表正常等等。这些在DDD的适用设计采用领域事件与相应的业务域值对象匹配就可以实现通用模型的搭建。
领域构建模型
而在技术架构方面其实也一直有一个争议,即架构优先还是实现优先,一个优良的架构会在系统的健壮性,可扩展性等诸多方面带来优秀的技术体验。但一开始就进行冗余的设计也同样为众多的项目所排斥。在低代码领域也有着先有“中台、微服务”然后再构建低代码应用的提法,但具体的实践中都是普通使用者难以达到的技术要求。
设计器构建模型
视图工厂(ViewFactory)设计目的则是平衡这两种模式的中间融合方案,在视图工厂应用中,允许开发这同时采用两种方式并行开发,并通过调整配置工具属性,实现两者之间的转换并统一到DDD模型中,实现持续集成。
六,视图工厂功能设计概览
(1)模块聚合
视图工厂在通用细粒度的组件基础上,提供了完整视图的封装路径,实现针对模块级别的高聚合配置应用。
聚合功能
页面聚合样例展示
1
|
@PageBar @GridAnnotation(event = CustomGridEvent.editor, customService = IPersonAPI.class, customMenu = {GridMenu.Add, GridMenu.Delete, GridMenu.Reload}) public interface IPersonGrid { @CustomAnnotation(pid = true, hidden = true) public String getPersonId(); @CustomAnnotation(pid = true, hidden = true) public String getRoleId(); @CustomAnnotation(pid = true, hidden = true) public String getOrgId(); @CustomAnnotation(caption = "邮箱") public String getEmail(); @CustomAnnotation(caption = "账户信息", required = true) public String getAccount(); @InputAnnotation(inputType = InputType.password) @CustomAnnotation(caption = "密码", required = true) public String getPassword(); @CustomAnnotation(pid = true, hidden = true) public String getName(); @ComboNumberAnnotation @CustomAnnotation(caption = "手机") public String getMobile(); @CustomAnnotation(caption = "部门名称") public String getOrgName(); }
|
(2)聚合视图概览
(3)聚合配置配置概览
(4)常用视图注解
注解名称 | 视图类型 | 示例 |
@FormAnnotation | 表单视图 | @FormAnnotation(customMenu = {CustomFormMenu.Save, CustomFormMenu.Close}, customService = ColService.class) |
@GridAnnotation | 列表视图 | @GridAnnotation(rowHeight = "4em", customMenu = {GridMenu.Reload, GridMenu.Add, GridMenu.Delete}, customService = {LocalFormulaService.class}, event = CustomGridEvent.editor) |
@TreeAnnotation | 树形视图 | @TreeAnnotation(heplBar = true, caption = "选择人员", selMode =SelModeType.singlecheckbox) |
@NavTreeAnnotation | 树形导航视图 | @NavTreeAnnotation(bottombarMenu = {CustomFormMenu.Save, CustomFormMenu.Close}, customService = AggWebSiteSelectService.class) |
@GalleryAnnotation | 图文列表视图 | @GalleryAnnotation(customMenu = {GridMenu.Reload, GridMenu.Add, GridMenu.Delete}) |
@TabsAnnotation | Tab页视图 | @TabsAnnotation(singleOpen = true) |
@ButtonViewsAnnotation | 按钮组导航 | @ButtonViewsAnnotation(barLocation = BarLocationType.left, barVAlign = VAlignType.top, autoReload = false) |
@NavGroupAnnotation | 分组容器视图 | @NavGroupAnnotation(bottombarMenu = {CustomFormMenu.Save, CustomFormMenu.Close}) |
@NavGalleryAnnotation | 卡片导航 | @NavGalleryAnnotation |
@NavFoldingAnnotation | 折叠页导航 | @NavFoldingAnnotation(bottombarMenu = CustomFormMenu.Close) |
@NavMenuBarAnnotation | 菜单页导航 | @NavMenuBarAnnotation |
七,列表视图组成
列表视图,主要由两部分来组成,领域服务主要负责,实体相关属性以及路由动作相关的操作。
列表视图组成
展示样例
(1)列表功能分解
视图配置是本文主要讲解的部分,在列表配置中,主要由模块配置和子域配置两个部分组成。
视图配置组成
(2)视图模块配置概览
主要负责列表视图的基本属性配置以及跟业务想相关的操作动作配置。
模块配置
(3) 环境变量设置:
在模块构建时通常是在特定环境下运行的,这些特性环境一般是由,聚合实体以及聚合跟的组件值配合当前用户等环境值对象来完成。在列表视图中如果需要添加环境变量,只需要在视图类中添加。
相应的 聚合KEY 并且在字段上添加@Pid,或者@Uid (参考下面具体示例),Uid 代表当前实体的组件值。Pid则是来自于父级以及环境变量值。添加注解后,OneCode解析器会自动关联当前环境并在运行时进行赋值。
常用注解示例
注解名称 | 用途 | 示例 |
@Pid | 环境变量,父级全局 | @Pid |
@Uid | 环境变量,当前主键 | @Uid |
@CustomAnnotation | 自定义隐藏域 | @CustomAnnotation(hidden = true) |
1
|
@PageBar @GridAnnotation(event = CustomGridEvent.editor, customService = IPersonAPI.class, customMenu = {GridMenu.Add, GridMenu.Delete, GridMenu.Reload}) public interface IPersonGrid { @Uid public String getPersonId(); @Pid public String getRoleId(); @Pid public String getOrgId(); }
|
配置界面
(4) 列表视图配置:
视图配置主要是列表本身以及其相关操作栏的设定。
分页栏配置
工具栏配置
工具栏
模块底部按钮栏
常用注解示例
注解名称 | 用途 | 示例 |
GridAnnotation | 列表视图配置 | @GridAnnotation(rowHeight = "4em", customService = {LocalFormulaService.class}, event = CustomGridEvent.editor) |
PageBar | 分页栏 | @PageBar(pageCount = 100) |
ToolBarMenu | 工具栏 | @ToolBarMenu |
MenuBarMenu | 菜单栏 | @MenuBarMenu |
BottomBarMenu | 底部工具栏 | @BottomBarMenu |
(5)视图子域概览
视图子域
在实际应用中,列表通常是以独立的模块来呈现,但在用户在操作时会涉及到操作行甚至操作到表格应用。
(6)行集子域
行子域概览
在行集域属性配置时,通常会根据数据实体的域事件来匹配相关的功能按钮。如:实体操作中,常见的CRUD事件,则会自动在行按钮上匹配上删除图标,在行头设定上启动,增加行标记。如果记录集允许弹出编辑这会对应添加双击编辑事件。
域属性图
行域操作
实际表格域划分示例
常用事件添加管理
注解名称 | 用途 | 实例 |
@GridRowCmd | 表格行按钮 | @GridRowCmd(tagCmdsAlign = TagCmdsAlign.left, menuClass = {DBColAction.class}) |
@RowHead | 行头配置 | @RowHead(selMode = SelModeType.none, gridHandlerCaption = "删除|排序", rowHandlerWidth = "10em", rowNumbered = false) |
| | |
1
|
@PageBar(pageCount = 100) @RowHead(selMode = SelModeType.none, gridHandlerCaption = "删除|排序", rowHandlerWidth = "10em", rowNumbered = false) @GridRowCmd(tagCmdsAlign = TagCmdsAlign.left, menuClass = {DBColAction.class}) @GridAnnotation(customService = {ColService.class}, customMenu = {GridMenu.Reload, GridMenu.Add}, event = CustomGridEvent.editor) public class DbColGridView { @CustomAnnotation(caption = "字段名", uid = true, required = true) private String name; @CustomAnnotation(caption = "类型", required = true) private ColType type = ColType.VARCHAR; @CustomAnnotation(caption = "长度", required = true) private Integer length = 20; @CustomAnnotation(caption = "数字精度") private Integer fractions = 0; @CustomAnnotation(caption = "是否主键") private Boolean pk; @CustomAnnotation(caption = "是否可为空") private Boolean canNull = true; @CustomAnnotation(colSpan = -1, caption = "注解", rowHeight = "100", required = true) private String cnname; }
|
(7)单元格子域
当表格属性设置为可以编辑域时,列表会以列为单位转换位,列表表单视图。单元格应用更多的会涉及到表单的相关操作,会在后续表单章节中做近一步的介绍。
单元格编辑
单元格类型
单元格事件