- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
领域驱动设计(Domain Driven Design,简称:DDD)设计思想和方法论早在2005年时候就被提出来,但是一直没有被重视和推荐使用,直到2015年之后微服务流行之后,再次被人重视和推荐使用.
下面我来介绍一下DDD设计思想和方法论,同时结合我们在实际项目中应用总结和思考.
目录 。
1、为什么要用DDD 。
2、DDD架构设计 。
3、领域建模方法 。
4、代码实践 。
5、问题总结 。
。
1、为什么要用DDD 。
面向过程 很多时候,我们都是操着面向对象的语言干着面向过程的勾当。大部分的系统都是从单一业务开始的。但是随着支持的业务越来越多,代码里面开始出现大量的if-else逻辑,这个时候代码开始有坏味道,没闻到的同学就这么继续往上堆,闻到的同学会重构一下,但因为系统没有统一的可扩展架构,重构的技法也各不相同,这种代码的不一致性也是一种理解上的复杂度.
分层不合理 分层太多和没有分层都会导致系统复杂度的上升.
随心所欲 随心所欲是因为缺少规范和约束.
面向对象 深入的理解面向对象设计原则、模式、方法论有很多,同时要具备非常好的业务理解力和抽象能力。 SOLID是单一职责原则(SRP),开闭原则(OCP),里氏替换原则(LSP),接口隔离原则(ISP)和依赖倒置原则(DIP)的缩写,原则是要比模式更基础更重要的指导准则,深入理解面向对象设计原则极大的提升我们的面向对象设计的能力和代码质量.
分层设计 分层最大的好处就是分离关注点,让每一层只解决该层关注的问题,从而将复杂的问题简化,起到分而治之的作用.
领域建模 领域模型可以准确地反映业务语言,使业务语义显性化,而传统的J2EE或Spring+Hibernate/Mybatis等事务性编程模型只关心数据,这些数据对象除了简单setter/getter方法外,没有任何业务方法,被称为成贫血模式.
规范设计 各归其位、命名约定、通用业务状态码约定等.
。
DDD概念定义 。
领域驱动设计 :一种软件开发方法,是一种软件核心复杂性应对之道,它可以帮助我们设计高质量的软件模型.
DDD目的:
。
领域模型与事务脚本对比 。
。
富血模型: 就是属性和方法都封装在Domain Object里,所有业务都直接操作Domain Object。 service层只是完成一些简单的事务之类的,甚至可以不用service层。也就是直接从action->entity.
贫血模型: 就是一个对象里只有属性,要实现业务,要依靠service层来实现相关方法,service层的实现是面向过程的,大量传统的分层应用action->service->dao->entity的方式就是这种贫血的模式实现.
。
举个例子,银行转账实现类 。
各位看官看到上面的代码是不是有一种似曾相似的感觉?咋一看也没有啥问题,能正常运行,能快速交付业务。如果仅是为了应付需求任务交差,当然没有什么啥问题了.
从设计角度来看确实存在一些问题,代码糊在一起,没有分层设计,伪面向对象的方法.
我们码农总得要有自己一点的追求,为伟大代码事业创造一点艺术贡献! 。
答案是肯定的! 。
客观来说上面的这段代码不算复杂,也算写得中规中举。下面我来让贴一段曾经在我们实际生产系统跑了将近一年的神代码,你才知道什么叫复杂和神奇! 16层if..else+for循环!就问你怕了没有?
请看神代码片段 。
if (contentOld != null && contentNew != null) {
map.put("ModelId", contentNew.getModelId());//ModelId
map.put("name", contentNew.getName());//Name
map.put("Description", contentNew.getDescription());//Description
map.put("fujianGroup", attachments);//附件组信息
for (int i = 0; i < contentOld.getGroups().size(); i++) {//数据库保存 组
MscsApiShowerRoomModelGroups groupsOld = contentOld.getGroups().get(i);
if ("纳米易结".equals(groupsOld.getGroupName())) {//新增的组信息
map.put("nanometerGroup", groupsOld);//纳米易结
} else if ("石基先发".equals(groupsOld.getGroupName())) {
map.put("stoneGroupFirst", groupsOld);//石基先发
} else if ("客户其它内容".equals(groupsOld.getGroupName())) {
map.put("otherGroup", groupsOld);//客户其它内容
} else {//原来的组 通过组ID 来判定
if ("1".equals(groupsOld.getGroupId())) {//产品规格组
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
groups.setOtherValue(groupsOld.getOtherValue());//设置单选按钮
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
for (MscsApiShowerRoomModelProperties properties : options.getProperties()) {
for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) {
if (properties.getPropertyId() != null && properties.getPropertyId().equals(properties2.getPropertyId())) {
properties2.setDefaults(properties.getDefaults());
}
}
}
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("productGroup", groups);//产品规格组
}
}
// map.put("productGroup",null);//产品规格组
} else if ("2".equals(groupsOld.getGroupId())) {//开门方向与产品方向
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("doorGroup", groups);//开门方向与产品方向
}
}
} else if ("3".equals(groupsOld.getGroupId())) {//产品颜色
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("productColorGroup", groups);//产品颜色
}
}
} else if ("14".equals(groupsOld.getGroupId())) {//玻璃工艺
String name = "";
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
groups.setOtherValue(groupsOld.getOtherValue());
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getName() != null && categories.getName().equals(categories2.getName())) {
name = categories.getName();
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
//设置 默认数据的位置 选中的数组 为第一个 默认显现
if (groups.getGategories() != null && !"".equals(name)) {
List<MscsApiShowerRoomModelCategories> arry = new ArrayList<MscsApiShowerRoomModelCategories>();
List<MscsApiShowerRoomModelCategories> arry1 = new ArrayList<MscsApiShowerRoomModelCategories>();
List<MscsApiShowerRoomModelCategories> arry2 = new ArrayList<MscsApiShowerRoomModelCategories>();
for (MscsApiShowerRoomModelCategories categories : groups.getGategories()) {
if (name.equals(categories.getName())) {
arry1.add(categories);
} else {
arry2.add(categories);
}
}
for (MscsApiShowerRoomModelCategories a : arry1) {
arry.add(a);
}
for (MscsApiShowerRoomModelCategories a : arry2) {
arry.add(a);
}
groups.setGategories(arry);
}
map.put("glassGroup", groups);//玻璃工艺
}
}
// map.put("glassGroup", null);//玻璃工艺
} else if ("4".equals(groupsOld.getGroupId())) {//玻璃贴膜
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
groups.setOtherValue(groupsOld.getOtherValue());
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getName() != null && categories.getName().equals(categories2.getName())) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("filmGroup", groups);//玻璃贴膜
// break ;
}
}
} else if ("5".equals(groupsOld.getGroupId())) {//玻璃厚度
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getName() != null && categories.getName().equals(categories2.getName())) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("thicknessGroup", groups);//玻璃厚度
}
}
} else if ("6".equals(groupsOld.getGroupId())) {//拉手款式
String name = "";
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getName() != null && categories.getName().equals(categories2.getName())) {
name = categories.getName();
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
//设置 默认数据的位置 选中的数组 为第一个 默认显现
if (groups.getGategories() != null && !"".equals(name)) {
List<MscsApiShowerRoomModelCategories> arry = new ArrayList<MscsApiShowerRoomModelCategories>();
List<MscsApiShowerRoomModelCategories> arry1 = new ArrayList<MscsApiShowerRoomModelCategories>();
List<MscsApiShowerRoomModelCategories> arry2 = new ArrayList<MscsApiShowerRoomModelCategories>();
for (MscsApiShowerRoomModelCategories categories : groups.getGategories()) {
if (name.equals(categories.getName())) {
arry1.add(categories);
} else {
arry2.add(categories);
}
}
for (MscsApiShowerRoomModelCategories a : arry1) {
arry.add(a);
}
for (MscsApiShowerRoomModelCategories a : arry2) {
arry.add(a);
}
groups.setGategories(arry);
}
map.put("handleGroup", groups);//拉手款式
}
}
// map.put("handleGroup", null);//拉手款式
} else if ("7".equals(groupsOld.getGroupId())) {//石基
String name = "";
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getName() != null && categories.getName().equals(categories2.getName())) {
name = categories.getName();
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
//设置 默认数据的位置 选中的数组 为第一个 默认显现
if (groups.getGategories() != null && !"".equals(name)) {
List<MscsApiShowerRoomModelCategories> arry = new ArrayList<MscsApiShowerRoomModelCategories>();
List<MscsApiShowerRoomModelCategories> arry1 = new ArrayList<MscsApiShowerRoomModelCategories>();
List<MscsApiShowerRoomModelCategories> arry2 = new ArrayList<MscsApiShowerRoomModelCategories>();
for (MscsApiShowerRoomModelCategories categories : groups.getGategories()) {
if (name.equals(categories.getName())) {
arry1.add(categories);
} else {
arry2.add(categories);
}
}
for (MscsApiShowerRoomModelCategories a : arry1) {
arry.add(a);
}
for (MscsApiShowerRoomModelCategories a : arry2) {
arry.add(a);
}
groups.setGategories(arry);
}
map.put("stoneGroup", groups);//拉手款式
}
}
} else if ("8".equals(groupsOld.getGroupId())) {//层架
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
for (MscsApiShowerRoomModelProperties properties : options.getProperties()) {
for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) {
if (properties.getPropertyId() != null && properties.getPropertyId().equals(properties2.getPropertyId())) {
properties2.setDefaults(properties.getDefaults());
}
}
}
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("shelfGroup", groups);//层架
}
}
// map.put("shelfGroup", null);//层架
} else if ("9".equals(groupsOld.getGroupId())) {//木架包装
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("mujiaGroup", groups);//木架包装
}
}
} else if ("32".equals(groupsOld.getGroupId())) {//玻璃宽度
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
if (options.getProperties() != null) {
for (MscsApiShowerRoomModelProperties properties : options.getProperties()) {
for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) {
if (StringUtils.isNotEmpty(properties.getCode()) && properties.getCode().equals(properties2.getCode())) {
properties2.setDefaults(properties.getDefaults());
}
}
}
}
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("glassWidthGroup", groups);// 玻璃宽度
}
}
}
}
}
}
}
。
各位看官你们看,看完是不是很想吐血!像上面这种代码请问谁敢去维护 ? 谁看谁骂,谁改谁死 !这种神代码绝对是可以拿来当教材用的~(在这里展示这段代码主要为了说明如果系统设计和分层不合理,将会带来严重的后果,可以说代码就像狗屎一样) 。
PS:说明一下当时写这些代码的作者因为一些原因离职了,我们当年系统上线后将近一年多的时间里不敢去修改这神代码,也没有人敢改.
如果业务需求一直稳定不改,那可以还勉强维持着,但是那有正常业务不改需求的?天底下应该没有! 所以它始终像一颗大雷在我们的头顶上滚动着! 。
后来经过两次重大重构设计之后,把它消灭了!篇幅原因这里的故事就不展开讲了(将来哪天有空专门写写: 那些年那些神代码 !).
回到主题上,银行转账那一段代码对比之下是不是小屋见大屋?下面我们基于银行转账那一段代码,展示一下怎么拆解和设计,变成领域设计模型.
转变成领域设计如下:
重新拆解转换之后,设计领域对象,代码分层结构清晰了很多,从此世界也变得清新了。你说香不香吗?
。
2、领域驱动设计架构设计 。
领域驱动设计分层 。
User Interface为用户接口层 ,也是经常我们看到Controller层类似,主要负责系统入口协议等,该层不处理任何业务逻辑,只负责调用接入和应用转发.
Application是应用层 ,和以往事务脚本的Service是截然不同的,该层中并不做详细的业务逻辑的封装,而是创建Domian并调用Domain中的相应方法完成业务逻辑处理,并调用Infrastructure完成Domain的持久化操作,该层需要负责事务的控制,保证数据的一致性.
Domain层是核心领域层 ,核心的业务逻辑应该以Domain为对象进行分类封装,Domain的划分以业务为基准,Domain层在技术层面的建模通用技巧在下面会做详细介绍,该层只能向下调用Infrastructure完成自身模型的初始化和持久化.
Infrastructure层类似于以往事务脚本的Dao层 ,以往的面向事务脚本的编程中,以数据表为主,所有的事务脚本直指目的就是完成表的CURD,而DDD中以模型为核心,Infrastructure层是为了重建已有的Domain,并在退出时持久化内存中的Domain对象。Infrastructure层不仅包含对关系数据库的处理,还包括对分布式缓存处理、对外系统的接入(integration)以及分布式消息队列的push操作.
一些常用的关键术语:
实体和值对象 在代码中皆表示为一个类(对象),从业务上来讲也分别表示一个业务实体。但是两者是有区别,实体是一个完整的具有生命周期的可以通过唯一标识来识别东西的类(对象),而值对象则为了度量和描述领域中的一个属性,将不同的相关属性组合成一个概念整体的类(对象) 。 。
例如,客户可以认为是一个实体,一个客户就是具有生命周期的东西,具有唯一的标识可以将A客户和B客户分开(唯一标识:身份证号码),而这个客户的地址(例如:广州市/白云区/欧派软件园)就应该定义为一个值对象,当我们定义好一个地址,它既可以是A客户的地址也可以是B客户的地址,当我需要更新A客户地址时,可以直接销毁A客户关联的地址对象然后重新创建一个地址对象关联到A客户上.
领域事件 是由一个特定领域因为一个用户Command触发的发生在过去的行为产生的事件,而这个事件是系统中其它部分感兴趣的.
在现在的分布式环境下,业务系统与业务系统之间并不是割裂的,而消息绝对是系统之间耦合度最低,最健壮,最容易扩展的一种通信机制.
领域服务 和以往事务脚本的Service只能说非常像,唯一不同的是,以往事务脚本的Service是自己全权负责业务逻辑,而领域服务不仅编写业务逻辑,还会调用实体和值对象的方法来协调实体和值对象,从而避免实体和值对象的耦合。当我们建模之后发现有些业务既不单单属于A领域对象,又不单单属于B领域对象,我们可以那么我们可以抽象出一个Service来完成此项业务,那么这个类就是领域服务.
资源库 也叫数据仓库,主要是完成领域对象的重建以及对象的持久化操作。资源库的设计主要是为了调用基础设施层来完成表的CURD、缓存的操作以及外系统接口的调用.
。
领域驱动设计架构图 。
这里引用阿里团队开源的可乐(Cola)领域设计架构示图,如下图所示:
DDD最核心思想是设计内部 六边形 结构.
从内往外看,最内层也是最核心就是Domain层(包括:Domain model和Domain Service),
第二层是Application层(包括:Application Service); 。
第三层业务逻辑层(Business Logic也可以叫基础设施层)主对外提供服务接口,对内解决基础服务包装构建.
。
DDD分层设计之调用示图 。
。
。
上图已经非常清楚展示了分层设计以及各层调用关系,一图胜千言,大家认真详细看图就可以理解了.
。
。
操作命令和对象抽象 ,通过命令与查询分离设计方式实现服务接口调用.
CQRS(Command Query Separation,命令查询分离) 基本思想在于,任何一个对象的方法可以分为两大类:
。
扩展点功能概念 。
领域驱动设计另外一个重要的地方是扩展点.
可乐包在扩展点功能设计中引入的概念: 业务、用例、场景.
。
。
例如我们要实现右图中所展示的扩展:在tmall这个业务下——下单用例——88VIP场景——用户身份校验进行扩展,我们只需要声明一个如下的扩展实现(Extension)就可以了.
。
3、领域建模 。
领域架构设计并不复杂,复杂的业务需求怎么转化为领域模型,这也是最难的地方,需要业务梳理足够清晰,同时需要有足够抽象能力和领域划分.
领域建模基于业务的建模的基础上,需要花较重的时间比例在梳理业务和模型设计上面;而同时领域建没有通用的统一结构设计,得看具体业务具体分析。下面举个例子说明一下领域建模方法。希望能够各位得到一点启动.
领域模型不是简单POJO或数据实体对象,他还有行为和状态,主要体现在事件机制、值对象上面.
这里先不深入讨论,先抛个影子,后面抽空补上更详细的说明.
。
。
4、领域驱动设计实现 。
。
4.1、分层设计 。
。
各层分工:
4.2、建立二方库组件 。
二方库组件: api:存放的是应用对外的接口。 dto.domainmodel:用来做数据传输的轻量级领域对象。 dto.domainevent: 用来做数据传输的领域事件。 Application里的组件: service:接口实现的facade,没有业务逻辑,可以包含对不同终端的adapter。 eventhandler:处理领域事件,包括本域的和外域的。 executor:用来处理(Command)和查询(Query)。 interceptor:COLA提供的对所有请求的AOP处理机制。 Domain里的组件: domain:领域实体,允许继承domainmodel。 domainservice: 领域服务,用来提供更粗粒度的领域能力。 gateway:对外依赖的网关接口,包括存储、RPC、Search等.
。
大部分情况下,二方库的确是用来定义服务接口和数据协议的。但是二方库区别于JSON的地方是它不仅仅是协议,它还是一个Java对象,一个Jar包。既然是Java对象,就意味着我们就有可能让DTO升级为一个Domain Model(有数据,有行为,有继承) 。 Domain Model 用到的所有数据如果都是自恰的,那么这些计算是不需要借助外面的辅助,自己就能完成。比如CustomerCO拥有身份证号码,那么判断当前这个CustomerCO的性别、年龄等信息时是依靠自己(身份证号码)就能完成的。是一种共享内核, CustomerCO把自己领域的知识(语言、数据和行为)通过二方库暴露出去了,假如有100个应用需要获取性别或年龄时做判断,客户端就不需要自己实现了.
。
应用不同Bounded Context之间的协作,要充分利用好二方库的桥梁作用。其协作方式如下图所示:
。
4.3、主要组件依赖关系 。
依赖关系示例,如以Customer会员业务对象举例如下图所示 。
。
4.4、上下文接口 。
。
4.5、代码实现 。
对外接口代码示例 。
参数校验 。
。
API接口服务层示例 。
命令总线示例 。
。
全局异常捕获示例代码 。
。
5、问题与总结 。
领域驱动设计结构非常清晰,适合复杂业务场景,代码结构清晰,代码维护代会低很多。当然也需要业务建模与抽象能力,与传统面象数据开发方法有本质的不同,需要转变开发者的思维方法,对团队有一定学习成本.
。
最后此篇关于领域驱动设计DDD应用与最佳实践的文章就讲到这里了,如果你想了解更多关于领域驱动设计DDD应用与最佳实践的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
本文分享自华为云社区《大模型LLM之分布式训练》,作者: 码上开花_Lancer。 随着语言模型参数量和所需训练数据量的急速增长,单个机器上有限的资源已无法满足大语言模型训练的要求。需要设计分布式训
本文分享自华为云社区《五大基础算法--动态规划法》,作者: 大金(内蒙的)。 一、基本概念 动态规划法,和分治法极其相似。区别就是,在求解子问题时,会保存该子问题的解,后面的子问题求解时,可以直接拿来
pip install scp pip install pexpect 测试代码: import os import stat import paramiko # 用于调用scp命令 def s
我目前正在实现“ token ”REST 服务。 token 只是一个字符串,由一些参数构建而成,然后经过哈希处理并在一定时间后过期。 我想在我的 REST 服务中有一个可以验证 token 的端点,
打开软删除后,我在客户端上添加一条记录,推送,删除添加的记录推送,然后尝试使用与初始记录相同的主键添加新记录(然后推送),我得到一个异常(exception)。 EntityDomainManager
打开软删除后,我在客户端上添加一条记录,推送,删除添加的记录推送,然后尝试使用与初始记录相同的主键添加新记录(然后推送),我得到一个异常(exception)。 EntityDomainManager
我有一个应用程序,每 x 秒接收一次天气信息。我想将此数据保存到 XML 文件中。 我应该为每个天气通知创建一个新的 XML 文件,还是将每个通知附加到同一个 XML 文件中?我不确定 XML 标准的
我猜我们大多数人都必须在某个时候处理这个问题,所以我想我会问这个问题。 当您的 BLL 中有很多集合并且您发现自己一遍又一遍地编写相同的旧内联(匿名)谓词时,显然有必要进行封装,但实现封装的最佳方
我有一些 c# 代码已经运行了一段时间了..我不得不说,虽然我了解 OO 原则的基础知识,但显然有不止一种方法可以给猫剥皮(尽管我讨厌那个短语!)。 因此,我有一个基本抽象类作为基本数据服务类,如下所
我设计了一个 SQL 数据库系统(使用 Postgre),我有一个问题,即创建一个关系/引用的常见做法是什么,这种关系/引用即使在引用的对象被删除时也能持续存在。 比如有一个UserORM,还有Act
我们的目标是搜索用户输入的字符串并计算在其中找到多少元音。不幸的是我被困在这里,有什么帮助吗? def numVowels(s): vowels= "AEIOUaeiou" if s
我有一个适用于我的“items”int 数组的旋转函数。下面的代码完成了它,除了我不必要地传输值。我正在努力实现“就地”轮换。我的意思是 ptrs 会递增或递减,而不是从数组中获取值。我需要通过这种方
我有一个 json 存储在我的应用程序文档文件夹中,我需要在我的所有 View 中使用它。我正在加载 json 并将其添加到每个 View 中的 NSMutableArray。但现在我了解到,我可以将
我用 C++ 开始了一个项目。这种语言的内存管理对我来说是新的。 我过去常常使用 new () 创建对象,然后传递指针,虽然它可以工作,但调试起来很痛苦,人们看到代码时会用有趣的眼神看着我。我为它没有
已结束。 这个问题是 off-topic .它目前不接受答案。 想要改进这个问题? Update the question所以它是on-topic堆栈溢出。 关闭 10 年前。 Improve thi
保持类松散耦合是编写易于理解、修改和调试的代码的一个重要方面——我明白这一点。然而,作为一个新手,几乎任何时候我都会超越我所苦苦挣扎的最简单的例子。 我或多或少地了解如何将字符串、整数和简单数据类型封
我发现我需要编写大量重复代码,因为我无法从其他 Controller 调用函数。例如,这里新闻提要内容在我的代码中重复,我对一个 Controller 做一些特定的事情,然后需要像这样加载我的新闻提要
假设需要一种数字数据类型,其允许值在指定范围内。更具体地说,假设要定义一个整数类型,其最小值为0,最大值为5000。这种情况在很多情况下都会出现,例如在对数据库数据类型,XSD数据类型进行建模时。 在
假设我想循环整个数组来访问每个元素。使用 for 循环、for...in 循环或 for...of 循环是 JavaScript 开发人员的标准做法吗? 例如: var myArray = ["app
我有一个旧的 SL4/ria 应用程序,我希望用 Breeze 取代它。我有一个关于内存使用和缓存的问题。我的应用程序加载工作列表(一个典型的用户可以访问大约 1,000 个这些工作)。此外,还有很多
我是一名优秀的程序员,十分优秀!