万字长文解析,领域驱动设计(DDD)落地设计

简介: 领域驱动设计(简称 ddd)概念来源于2004年著名建模专家Eric Evans 发表的他最具影响力的书籍:《领域驱动设计——软件核心复杂性应对之道》(Domain-Driven Design –Tackling Complexity in the Heart of Software),简称Evans DDD,领域驱动设计思想进入软件开发者的视野。在将近20年的发展中领域模型设计一直占据着非常重要的位置,但其直接面向业务场景的设计思想,更适合在具有特定业务场景的模型构建。

前言

领域驱动设计(简称 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建模工具。


编辑切换为居中

添加图片注释,不超过 140 字(可选)


OneCode-DSM 应用

(1)可视化设计器

以可视化设计器引擎为主体的表单报表工具,在日常常用的表单报表中是以无代码的方式来实现业务流审批以及数据大屏展现设计,移动展现等应用。


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(2)低代码服务集成工具


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(3)DSM建模工具


编辑切换为居中

添加图片注释,不超过 140 字(可选)


二,OneCode-DSM工具

(1)工具组成概览

DSM建模工具是OneCode建模的辅助工具,提供了资源库管理模块,领域模型构建模块,以及视图工厂配置模块。


编辑切换为居中

添加图片注释,不超过 140 字(可选)


仓储模型模块,主要功能是辅助用户将用户的数据库,外部API接口,以及已有的“代码”应用通过转换器转变为可被DSM识别的资源部格式。

领域模型模块是DSM核心工具,在领域模型中导入的资源会同具体场景下的值对象,场景菜单、通用域服务根据具体的业务场景完成领域模型的建模工作。

视图工厂是领域模型的具体实现,在领域模型应用中建模输出的产物会通过出码工厂输出位视图应用,这些视图应用会通过视图工厂进一步加工处理输出为用户交互应用。

(2)OneCode语言(注解)组成


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(3)建模流程


编辑切换为居中

添加图片注释,不超过 140 字(可选)


三,仓储库建模



编辑切换为居中

添加图片注释,不超过 140 字(可选)


(1)通过数据库建模

仓储工具中使用频率最高的是数据库的转换应用,用户通过数据库工具完成数据源配置。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

数据源配置

库表映射代码示例:

@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代码示例:

@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)通过实体建模

仓储实体实例


编辑切换为居中

添加图片注释,不超过 140 字(可选)


@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) 实体关系


编辑切换为居中

添加图片注释,不超过 140 字(可选)


仓储建模的一个核心目的是将结构化的数据转变为面向对象的模式,而这其中非常重要的一点则是实体关系的处理,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.)


实体模型关系在基础的模型上根据仓储模型设计,独立扩展了

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) 仓储建模模板


编辑切换为居中

添加图片注释,不超过 140 字(可选)



编辑切换为居中

添加图片注释,不超过 140 字(可选)


(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)领域建模组成


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(2)领域建模原理


编辑切换为居中

添加图片注释,不超过 140 字(可选)

模型渲染原理

OneCode 本身基于JAVA语言体系,是在Java Spring 注解基础上的一套扩展子集,混合编译引擎器通过扩展注解构建完整的Domain模型,通过读取标准Spring 注解完成普通Web数据交付及调度过程。

示例注解说明:


编辑切换为居中

添加图片注释,不超过 140 字(可选)


完整示例代码:

@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)聚合配置


编辑切换为居中

添加图片注释,不超过 140 字(可选)

建模配置


编辑切换为居中

添加图片注释,不超过 140 字(可选)

接口配置


编辑切换为居中

添加图片注释,不超过 140 字(可选)


窗体配置


编辑切换为居中

添加图片注释,不超过 140 字(可选)

执行事件传递

(4)聚合分类


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(5)聚合根

聚合根设计,通常来标识其独立的域应用,具有全局唯一性的特点。 在 通用域中,具有时间轴维度的对象描述,如工作流中的流程示例(ProcessInst)对象。在获取聚合根后,可以依据时间轴一次向前获取,流程定义对象,向后获取可以取得流程历史数据,而根据当前节点则可以获取权限,数据表单、业务示例状态的等等实体数据。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

流程示例聚合根配置

流程实例聚合根源码

@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)聚合实体

聚合实体,通常用来描述独立实体机构,例如业务表单中单表或简单关联表关系。通常只包含简单的值关系,功能上也仅限于,查询列表、保存表单等简单应用。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

单表表单

代码示例

@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模型中并未定义,但在低代码应用却有着很重要的一席。

动作菜单建模中,主要将菜单展现与关联动作结合在一起。


添加图片注释,不超过 140 字(可选)

工作流发送菜单建模

菜单展现位置


添加图片注释,不超过 140 字(可选)



添加图片注释,不超过 140 字(可选)

编辑器右键菜单

常用菜单注解


注解

位置

示例

@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)


@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)通用域

通用域将系统中常用服务进行了独立分类可以在工程构建时导入进来。


添加图片注释,不超过 140 字(可选)

通用域管理

(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代码。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

OneCode代码转换实例图


编辑切换为居中

添加图片注释,不超过 140 字(可选)

简单列表转换示意


编辑切换为居中

添加图片注释,不超过 140 字(可选)

表单渲染


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(2),视图工厂设计目的

在视图设计建模中,最容易混淆的一个问题就是,视图设计器与视图工厂的区别是什么?有了视图设计器为什么,还需要视图工厂来建模?视图设计器通过可视化的方式,降低了开发者上手的门槛。但编程本身不单单是一种基础的技能,还需要将业务逻辑,技术技巧进行灵活运用,再分发到实际环境中进一步磨炼,重构,最终形成软件的 “灵魂”业务&技术架构。

这对低代码视图设计方面提出了更高的技术要求:

业务架构方面能更好的来理解业务语言,诸如:根据业务实体,自动来匹配一张视图。当业务实体发生变更时,自动更新视图。当业务实体的属性发生变化时自动匹配一种更适合用户交互的输入输出方式,如:预警信息显示,红色代表严重、黄色代表警告,绿色代表正常等等。这些在DDD的适用设计采用领域事件与相应的业务域值对象匹配就可以实现通用模型的搭建。


添加图片注释,不超过 140 字(可选)

领域构建模型



而在技术架构方面其实也一直有一个争议,即架构优先还是实现优先,一个优良的架构会在系统的健壮性,可扩展性等诸多方面带来优秀的技术体验。但一开始就进行冗余的设计也同样为众多的项目所排斥。在低代码领域也有着先有“中台、微服务”然后再构建低代码应用的提法,但具体的实践中都是普通使用者难以达到的技术要求。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

设计器构建模型


视图工厂(ViewFactory)设计目的则是平衡这两种模式的中间融合方案,在视图工厂应用中,允许开发这同时采用两种方式并行开发,并通过调整配置工具属性,实现两者之间的转换并统一到DDD模型中,实现持续集成。

六,视图工厂功能设计概览

(1)模块聚合

视图工厂在通用细粒度的组件基础上,提供了完整视图的封装路径,实现针对模块级别的高聚合配置应用。


添加图片注释,不超过 140 字(可选)

聚合功能


添加图片注释,不超过 140 字(可选)

页面聚合样例展示

@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)聚合视图概览


添加图片注释,不超过 140 字(可选)


(3)聚合配置配置概览


添加图片注释,不超过 140 字(可选)


(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


七,列表视图组成

列表视图,主要由两部分来组成,领域服务主要负责,实体相关属性以及路由动作相关的操作。


添加图片注释,不超过 140 字(可选)

列表视图组成


编辑切换为居中

添加图片注释,不超过 140 字(可选)

展示样例

(1)列表功能分解

视图配置是本文主要讲解的部分,在列表配置中,主要由模块配置和子域配置两个部分组成。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

视图配置组成


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(2)视图模块配置概览

主要负责列表视图的基本属性配置以及跟业务想相关的操作动作配置。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

模块配置

(3) 环境变量设置:

在模块构建时通常是在特定环境下运行的,这些特性环境一般是由,聚合实体以及聚合跟的组件值配合当前用户等环境值对象来完成。在列表视图中如果需要添加环境变量,只需要在视图类中添加。

相应的 聚合KEY 并且在字段上添加@Pid,或者@Uid (参考下面具体示例),Uid 代表当前实体的组件值。Pid则是来自于父级以及环境变量值。添加注解后,OneCode解析器会自动关联当前环境并在运行时进行赋值。

常用注解示例


注解名称

用途

示例

@Pid

环境变量,父级全局

@Pid

@Uid

环境变量,当前主键

@Uid

@CustomAnnotation

自定义隐藏域

@CustomAnnotation(hidden = true)


@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(); }


添加图片注释,不超过 140 字(可选)

配置界面


(4) 列表视图配置:


编辑切换为居中

添加图片注释,不超过 140 字(可选)


视图配置主要是列表本身以及其相关操作栏的设定。

分页栏配置



工具栏配置


添加图片注释,不超过 140 字(可选)

工具栏

模块底部按钮栏


添加图片注释,不超过 140 字(可选)


常用注解示例


注解名称

用途

示例

GridAnnotation

列表视图配置

@GridAnnotation(rowHeight = "4em", customService = {LocalFormulaService.class}, event = CustomGridEvent.editor)

PageBar

分页栏

@PageBar(pageCount = 100)

ToolBarMenu

工具栏

@ToolBarMenu

MenuBarMenu

菜单栏

@MenuBarMenu

BottomBarMenu

底部工具栏

@BottomBarMenu


(5)视图子域概览


添加图片注释,不超过 140 字(可选)

视图子域


在实际应用中,列表通常是以独立的模块来呈现,但在用户在操作时会涉及到操作行甚至操作到表格应用。


编辑切换为居中

添加图片注释,不超过 140 字(可选)


(6)行集子域


编辑切换为居中

添加图片注释,不超过 140 字(可选)

行子域概览


在行集域属性配置时,通常会根据数据实体的域事件来匹配相关的功能按钮。如:实体操作中,常见的CRUD事件,则会自动在行按钮上匹配上删除图标,在行头设定上启动,增加行标记。如果记录集允许弹出编辑这会对应添加双击编辑事件。


编辑切换为居中

添加图片注释,不超过 140 字(可选)

域属性图


添加图片注释,不超过 140 字(可选)

行域操作


编辑切换为居中

添加图片注释,不超过 140 字(可选)

实际表格域划分示例

常用事件添加管理


编辑切换为居中

添加图片注释,不超过 140 字(可选)



注解名称

用途

实例

@GridRowCmd

表格行按钮

@GridRowCmd(tagCmdsAlign = TagCmdsAlign.left, menuClass = {DBColAction.class})

@RowHead

行头配置

@RowHead(selMode = SelModeType.none, gridHandlerCaption = "删除|排序", rowHandlerWidth = "10em", rowNumbered = false)


@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)单元格子域

当表格属性设置为可以编辑域时,列表会以列为单位转换位,列表表单视图。单元格应用更多的会涉及到表单的相关操作,会在后续表单章节中做近一步的介绍。


添加图片注释,不超过 140 字(可选)

单元格编辑


编辑切换为居中

添加图片注释,不超过 140 字(可选)

单元格类型


添加图片注释,不超过 140 字(可选)

单元格事件

相关文章
|
3月前
|
前端开发 测试技术 人机交互
DDD - 理论到落地从统一语言开始
DDD - 理论到落地从统一语言开始
100 0
|
6月前
|
领域建模
真下饭!字节技术官DDD(领域驱动设计)手册,拆解业务代码首选
至少20年前,一些顶尖的软件设计人员就已经认识到领域建模和设计的重要性,但令人惊讶的是,这么长时间以来几乎没有人写出点儿什么,告诉大家应该做哪些工作或如何去做。尽管这些工作还没有被清楚地表述出来,但一种新的思潮已经形成,它像一股暗流一样在对象社区中涌动,我把这种思潮称为领域驱动设计(domain-driven design)。
|
5月前
|
数据安全/隐私保护
如何把DDD应用到实际项目中来,例子中需要包含具体的领域模型设计,这么做的理由,以及一位这个设计而引进的坑
如何把DDD应用到实际项目中来,例子中需要包含具体的领域模型设计,这么做的理由,以及一位这个设计而引进的坑
|
缓存 前端开发 中间件
DDD 领域驱动设计落地实践系列:工程结构分层设计
前面几篇文章中,笔者给大家阐述了 DDD 领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行 DDD 落地实践进行了详细的讨论,但是还没有涉及到工程层面的落地。实际上所有的这些架构理论到最后都是为了使得我们代码结构更加清晰,从而开发出 bug 少、扩展性强、逻辑清楚的应用。因此本文就是为了解决 DDD 领域驱动落地实践最后一公里问题,将我们分析出来的领域模型通过与工程结构的映射实现真正的落地。
DDD 领域驱动设计落地实践系列:工程结构分层设计
|
2月前
|
设计模式 监控 算法
【领域驱动设计专题】一文带领你透视DDD领域驱动模型的本质和设计原理分析指南(通用语言体系)
【领域驱动设计专题】一文带领你透视DDD领域驱动模型的本质和设计原理分析指南(通用语言体系)
61 2
|
2月前
|
敏捷开发 架构师 Java
【领域驱动设计专题】一文带领你透视DDD领域驱动模型的本质和设计原理分析指南(基本概念篇)
【领域驱动设计专题】一文带领你透视DDD领域驱动模型的本质和设计原理分析指南(基本概念篇)
66 0
|
5G 调度
灵活时隙符号配比 | 带你读《5G 空口设计与实践进阶 》之十八
通过不同时隙格式的选择或不同时隙格式的聚合,NR 可以动态适配当前场景下的业务需求。
灵活时隙符号配比 | 带你读《5G 空口设计与实践进阶 》之十八
|
12天前
|
设计模式 监控 安全
编码之道:从函数到系统架构的思考
【4月更文挑战第29天】在软件开发的世界中,代码不仅仅是一种交流工具,它更是构建艺术的基石。本文将探讨从基本函数编写到整个系统架构设计的过程中所涉及的技术细节与感悟。我们将穿梭于代码的海洋,体会那些看似简单却蕴含深意的编程原则,以及如何在宏观与微观之间寻找平衡,打造出既优雅又高效的系统。
|
10月前
|
数据库
产品第三版面向对象角度的DDD落地
我们应该关注谁来做事,而不是怎么做事
|
10月前
|
消息中间件 架构师 搜索推荐
DDD领域驱动设计的概念解析
DDD领域驱动设计的概念解析
163 0