领域驱动设计DDD实际项目落地最佳实践

这篇具有很好参考价值的文章主要介绍了领域驱动设计DDD实际项目落地最佳实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

领域驱动设计(Domain Driven Design,简称:DDD)设计思想和方法论早在2005年时候就被提出来,但是一直没有被重视和推荐使用,直到2015年之后微服务流行之后,再次被人重视和推荐使用。

下面我来介绍一下DDD设计思想和方法论,同时结合我们在实际项目中应用总结和思考。

目录

1、为什么要用DDD

2、DDD架构设计

3、领域建模方法

4、代码实践

5、问题总结

 文章来源地址https://www.toymoban.com/news/detail-482315.html

1、为什么要用DDD


面向过程
      很多时候,我们都是操着面向对象的语言干着面向过程的勾当。大部分的系统都是从单一业务开始的。但是随着支持的业务越来越多,代码里面开始出现大量的if-else逻辑,这个时候代码开始有坏味道,没闻到的同学就这么继续往上堆,闻到的同学会重构一下,但因为系统没有统一的可扩展架构,重构的技法也各不相同,这种代码的不一致性也是一种理解上的复杂度。

分层不合理
      分层太多和没有分层都会导致系统复杂度的上升。

随心所欲
      随心所欲是因为缺少规范和约束。

面向对象
     深入的理解面向对象设计原则、模式、方法论有很多,同时要具备非常好的业务理解力和抽象能力。
     SOLID是单一职责原则(SRP),开闭原则(OCP),里氏替换原则(LSP),接口隔离原则(ISP)和依赖倒置原则(DIP)的缩写,原则是要比模式更基础更重要的指导准则,深入理解面向对象设计原则极大的提升我们的面向对象设计的能力和代码质量。

分层设计
      分层最大的好处就是分离关注点,让每一层只解决该层关注的问题,从而将复杂的问题简化,起到分而治之的作用。

领域建模
      领域模型可以准确地反映业务语言,使业务语义显性化,而传统的J2EE或Spring+Hibernate/Mybatis等事务性编程模型只关心数据,这些数据对象除了简单setter/getter方法外,没有任何业务方法,被称为成贫血模式。

规范设计
        各归其位、命名约定、通用业务状态码约定等。

DDD概念定义

领域驱动设计:一种软件开发方法,是一种软件核心复杂性应对之道,它可以帮助我们设计高质量的软件模型。

DDD目的:

  1. DDD是为了解决复杂业务逻辑的问题
  2. DDD的核心问题不是技术问题,而是关于讨论、聆听、理解、发现业务价值的问题
  3. DDD是技术人员拥有产品思维的一个体现
  4. DDD的学习曲线很陡主要是因为它是一种方法论,每个人对它的理解存在差异

 

领域模型与事务脚本对比

领域驱动设计DDD实际项目落地最佳实践

 

富血模型:就是属性和方法都封装在Domain Object里,所有业务都直接操作Domain Object。 service层只是完成一些简单的事务之类的,甚至可以不用service层。也就是直接从action->entity。

贫血模型:就是一个对象里只有属性,要实现业务,要依靠service层来实现相关方法,service层的实现是面向过程的,大量传统的分层应用action->service->dao->entity的方式就是这种贫血的模式实现。

举个例子,银行转账实现类

领域驱动设计DDD实际项目落地最佳实践

各位看官看到上面的代码是不是有一种似曾相似的感觉?咋一看也没有啥问题,能正常运行,能快速交付业务。如果仅是为了应付需求任务交差,当然没有什么啥问题了。

从设计角度来看确实存在一些问题,代码糊在一起,没有分层设计,伪面向对象的方法。

我们码农总得要有自己一点的追求,为伟大代码事业创造一点艺术贡献!

  • 如果业务需求快速变化怎么办,需求越来越复杂怎么办?
  • 如果团队里面有多人协作,代码需要经过多人反复修改接手传承,你敢保证后面接手的人不会骂你吗?
  • 难道设计上有没有可以改进的空间?

答案是正面的!

客观来说上面的这段代码不算复杂,也算写得中规中举。下面我来让贴一段曾经在我们实际生产系统跑了将近一年的神代码,你才知道什么叫复杂和神奇!16层if..else+for循环!就问你怕了没有?

》》》请点开看下面神码片段👇👇

请看神码--地狱18层!
 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);// 玻璃宽度
                                }
                            }
                        }
                    }
                }
            }
        }

领域驱动设计DDD实际项目落地最佳实践

 

各位看官你们看,看完是不是很想吐血!像上面这种代码请问谁敢去维护谁看谁骂,谁改谁死!这种神代码绝对是可以拿来当教材用的~(在这里展示这段代码主要为了说明如果系统设计和分层不合理,将会带来严重的后果,可以说代码就像狗屎一样)

PS:说明一下当时写这些代码的作者因为一些原因离职了,我们当年系统上线后将近一年多的时间里不敢去修改这神代码,也没有人敢改。

如果业务需求一直稳定不改,那可以还勉强维持着,但是那有正常业务不改需求的?天底下应该没有!所以它始终像一颗大雷在我们的头顶上滚动着!

后来经过两次重大重构设计之后,把它消灭了!篇幅原因这里的故事就不展开讲了,有兴趣的请看另一篇文章:《那些年那些神码》。

回到主题上,银行转账那一段代码对比之下是不是小屋见大屋?下面我们基于银行转账那一段代码,展示一下怎么拆解和设计,变成领域设计模型。

转变成领域设计如下:

领域驱动设计DDD实际项目落地最佳实践

  • 抽出Account Domain类;
  • 抽出转账类即领域服务;
  • 抽出透支策略接口与实现类

重新拆解转换之后,设计领域对象,代码分层结构清晰了很多,后续业务折腾变化容易维护和扩展,从此世界也变得清新了,你说香不香吗?

 

2、领域驱动设计架构设计

2.1、分层结构转变

先看分层结构思路转变,领域驱动设计分层与传统三层结构有比较直观的区别。

领域驱动设计DDD实际项目落地最佳实践

User Interface为用户接口层,也是经常我们看到Controller层类似,主要负责系统入口协议等,该层不处理任何业务逻辑,只负责调用接入和应用转发。

Application是应用层,和以往事务脚本的Service是截然不同的,该层中并不做详细的业务逻辑的封装,而是创建Domian并调用Domain中的相应方法完成业务逻辑处理,并调用Infrastructure完成Domain的持久化操作,该层需要负责事务的控制,保证数据的一致性。

Domain层是核心领域层,核心的业务逻辑应该以Domain为对象进行分类封装,Domain的划分以业务为基准,Domain层在技术层面的建模通用技巧在下面会做详细介绍,该层只能向下调用Infrastructure完成自身模型的初始化和持久化。

Infrastructure层类似于以往事务脚本的Dao层,以往的面向事务脚本的编程中,以数据表为主,所有的事务脚本直指目的就是完成表的CURD,而DDD中以模型为核心,Infrastructure层是为了重建已有的Domain,并在退出时持久化内存中的Domain对象。Infrastructure层不仅包含对关系数据库的处理,还包括对分布式缓存处理、对外系统的接入(integration)以及分布式消息队列的push操作。

一些常用的关键术语:

  • 实体 - Entity
  • 值对象 - Value Objects
  • 领域服务 - Domain Services
  • 领域事件 - Domain Events
  • 模块 - Modules
  • 聚合 - Aggregate
  • 资源库 - Repository

实体和值对象在代码中皆表示为一个类(对象),从业务上来讲也分别表示一个业务实体。但是两者是有区别,实体是一个完整的具有生命周期的可以通过唯一标识来识别东西的类(对象),而值对象则为了度量和描述领域中的一个属性,将不同的相关属性组合成一个概念整体的类(对象) 。 

例如,客户可以认为是一个实体,一个客户就是具有生命周期的东西,具有唯一的标识可以将A客户和B客户分开(唯一标识:身份证号码),而这个客户的地址(例如:广州市/白云区/欧派软件园)就应该定义为一个值对象,当我们定义好一个地址,它既可以是A客户的地址也可以是B客户的地址,当我需要更新A客户地址时,可以直接销毁A客户关联的地址对象然后重新创建一个地址对象关联到A客户上。

领域事件是由一个特定领域因为一个用户Command触发的发生在过去的行为产生的事件,而这个事件是系统中其它部分感兴趣的。

在现在的分布式环境下,业务系统与业务系统之间并不是割裂的,而消息绝对是系统之间耦合度最低,最健壮,最容易扩展的一种通信机制。

领域服务和以往事务脚本的Service只能说非常像,唯一不同的是,以往事务脚本的Service是自己全权负责业务逻辑,而领域服务不仅编写业务逻辑,还会调用实体和值对象的方法来协调实体和值对象,从而避免实体和值对象的耦合。当我们建模之后发现有些业务既不单单属于A领域对象,又不单单属于B领域对象,我们可以那么我们可以抽象出一个Service来完成此项业务,那么这个类就是领域服务。

资源库也叫数据仓库,主要是完成领域对象的重建以及对象的持久化操作。资源库的设计主要是为了调用基础设施层来完成表的CURD、缓存的操作以及外系统接口的调用。

 

2.2、领域驱动设计

这里引用阿里团队开源的可乐(Cola)领域设计架构示图,如下图所示:

领域驱动设计DDD实际项目落地最佳实践

DDD最核心思想是设计内部六边形结构。

从内往外看,最内层也是最核心就是Domain层(包括:Domain model和Domain Service);

第二层是Application层(包括:Application Service);

第三层业务逻辑层(Business Logic也可以叫基础设施层)主对外提供服务接口,对内解决基础服务包装构建。

 

2.3、分层调用示图

DDD分层架构设计之调用示图

 

领域驱动设计DDD实际项目落地最佳实践

 

上图已经非常清楚展示了分层设计以及各层调用关系,一图胜千言,大家认真详细看图就可以理解了。

2.4、命令式读写分离

操作命令和对象抽象,通过命令与查询分离设计方式实现服务接口调用。

CQRS(Command Query Separation,命令查询分离) 基本思想在于,任何一个对象的方法可以分为两大类:

  • 命令(Command):不返回任何结果(void),但会改变对象的状态
  • 查询(Query):返回结果,但是不会改变对象的状态,对系统没有副作用

领域驱动设计DDD实际项目落地最佳实践

 

2.5、扩展点

领域驱动设计另外一个重要的地方是扩展点。

可乐包在扩展点功能设计中引入的概念:业务、用例、场景。

  • 业务(Business):就是一个自负盈亏的财务主体,比如tmall、淘宝和零售通就是三个不同的业务。
  • 用例(Use Case):描述了用户和系统之间的互动,每个用例提供了一个或多个场景。比如,支付订单就是一个典型的用例。
  • 场景(Scenario):场景也被称为用例的实例,包括用例所有的可能情况(正常的和异常的)。比如对于“订单支付”这个用例,就有“可以使用花呗”,“支付宝余额不足”,“银行账户余额不足”等多个场景。

 

领域驱动设计DDD实际项目落地最佳实践

 

例如我们要实现右图中所展示的扩展:在tmall这个业务下——下单用例——88VIP场景——用户身份校验进行扩展,我们只需要声明一个如下的扩展实现(Extension)就可以了。

领域驱动设计DDD实际项目落地最佳实践

领域驱动设计DDD实际项目落地最佳实践

 

 

3、领域建模

领域架构设计并不复杂,复杂的业务需求怎么转化为领域模型,这也是最难的地方,需要业务梳理足够清晰,同时需要有足够抽象能力和领域划分。

领域建模基于业务的建模的基础上,需要花较重的时间比例在梳理业务和模型设计上面;而同时领域建没有通用的统一结构设计,得看具体业务具体分析。下面举个例子说明一下领域建模方法。希望能够各位得到一点启发。

领域驱动设计DDD实际项目落地最佳实践

领域模型不是简单POJO或数据实体对象,他还有行为和状态,主要体现在事件机制、值对象上面。

这里先不深入讨论,先抛个影子,后面抽空补上更详细的说明。

 

 

4、领域驱动设计实现

 

4.1、分层设计

各层分工:

  • Application层主要负责获取输入,组装context,做输入校验,发送消息给领域层做业务处理,监听确认消息;
  • Domain层主要是通过领域服务(Domain Service),领域对象(Domain Object)的交互,对上层提供业务逻辑的处理,然后调用下层Repository做持久化处理;
  • Infrastructure层主要包含Repository,Config,Common和message,Repository负责数据的CRUD操作,数据来源可以是MySQL,NoSql,Search,甚至是HSF等;
  • Config负责应用的配置;Common是一写工具类;负责message通信的也应该放在这一层。

领域驱动设计DDD实际项目落地最佳实践

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等。

 

领域驱动设计DDD实际项目落地最佳实践

大部分情况下,二方库的确是用来定义服务接口和数据协议的。但是二方库区别于JSON的地方是它不仅仅是协议,它还是一个Java对象,一个Jar包。既然是Java对象,就意味着我们就有可能让DTO升级为一个Domain Model(有数据,有行为,有继承) 。
Domain Model用到的所有数据如果都是自恰的,那么这些计算是不需要借助外面的辅助,自己就能完成。比如CustomerCO拥有身份证号码,那么判断当前这个CustomerCO的性别、年龄等信息时是依靠自己(身份证号码)就能完成的。是一种共享内核, CustomerCO把自己领域的知识(语言、数据和行为)通过二方库暴露出去了,假如有100个应用需要获取性别或年龄时做判断,客户端就不需要自己实现了。

领域驱动设计DDD实际项目落地最佳实践

4.3、上下文

应用不同Bounded Context之间的协作,要充分利用好二方库的桥梁作用。其协作方式如下图所示:

领域驱动设计DDD实际项目落地最佳实践

领域驱动设计DDD实际项目落地最佳实践

 

4.4、主要组件依赖关系

依赖关系示例,如以Customer会员业务对象举例如下图所示

领域驱动设计DDD实际项目落地最佳实践

 

4.5、代码实现

下面以我们系统中客户中心会员体系设计为示例介绍一下怎么使用DDD方法实现代码。

对外接口代码示例

领域驱动设计DDD实际项目落地最佳实践

参数校验

 

领域驱动设计DDD实际项目落地最佳实践

API接口服务层示例

领域驱动设计DDD实际项目落地最佳实践

命令总线示例

 

领域驱动设计DDD实际项目落地最佳实践

全局异常捕获示例代码

领域驱动设计DDD实际项目落地最佳实践

 

4.6、旧项目神码改造

对于旧项目代码,大家都非常头痛,旧系统经常出现一些奇怪的问题,其实就是不稳定引起的。旧系统代码都是神码具多,极难维护,谁都不愿意接手,后来经过我们重大讨论决策后决定对旧项目进行重构。

重构说起来简单,实施起来却是非常的头大,毕竟不是简单的系统,同时也是公司里面业务最重要的业务系统(订单+CRM集团业绩的入口保障),不容许出错;而旧代码库非常庞大,规模接近百万行。代码质量不堪回首,都是地狱级别。

我们痛定思痛,决定对它动用外科手术,当时顶着巨大压力说服大boss同意,游说业务、产品、测试、开发各方一起协作。在保证业务规则和逻辑前提,进行重大的重构设计,主要也是采用DDD领域驱动设计。

这次重构经历了近6个月,顶着各方巨大的压力。但经过几次重大升级发布,终于彻底改头换面,神码级旧系统完成改造。

1个java类近万行神码经过重构改造,确切的说应该重设计重新写代码结构,总结一下有几条宝贵经验:

  • 根据DDD读写分离设计,写入通过实现不同的Command执行器,查询实现不同的查询执行器。
  • 根据不同业务场景增加多个不同的扩展点,有效地解耦业务。
  • 复杂的业务规则引入规则引擎,把业务规则抽象成一条条可动态编辑和可维护规则,并实现动态加载和配置,而不是硬代码。

经过一顿猛烈改造设计,新版本的代码清爽多了!

领域驱动设计DDD实际项目落地最佳实践

把一万行的代码直接搞成了360行左右!

旧代码:

领域驱动设计DDD实际项目落地最佳实践

消灭了18层地狱式代码

改造后新代码结构:

领域驱动设计DDD实际项目落地最佳实践

查询统一执行器如下图所示:

领域驱动设计DDD实际项目落地最佳实践

扩展点抽象查询器

领域驱动设计DDD实际项目落地最佳实践

扩展点执行器抽象类

领域驱动设计DDD实际项目落地最佳实践

经过一番猛操作改造之后,代码简洁很多,变得可读可维护,从此世界清新很多,维护代价极大的变小!

 

5、问题与总结

软件的世界里没有银弹!因此领域模型还是事务脚本没有对错之分, 关键看是否合适:
  • 对于简单的业务场景,使用事务脚本,其优点是简单、直观、易上手
  • 对于复杂的业务场景,比较有效的治理办法就是领域建模,在封装业务逻辑的同时,提升了对象的内聚性和重用性,因为使用了通用语言,使得隐藏的业务逻辑得到显性化表达,使得复杂性治理成为可能。

领域驱动设计结构非常清晰,适合复杂业务场景,代码结构清晰,代码维护代会低很多。当然也需要业务建模与抽象能力,与传统面象数据开发方法有本质的不同,需要转变开发者的思维方法,对团队有一定学习成本。

要求开发者:
  • 有开发卓越软件的激情和毅力
  • 渴望学习和进步
  • 有能力理解软件模式,并懂得如何应用这些模式 
  • 有发掘不同的设计方法能力和耐性
  • 勇于改变现状
  • 希望编写出更好的代码

 

到了这里,关于领域驱动设计DDD实际项目落地最佳实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 快速理解DDD领域驱动设计架构思想-基础篇

    本文与大家一起学习并介绍领域驱动设计(Domain Drive Design) 简称DDD,以及为什么我们需要领域驱动设计,它有哪些优缺点,尽量用一些通俗易懂文字来描述讲解领域驱动设计,本篇并不会从深层大论述讲解落地实现,这些大家可以在了解入门后再去深层次学习探讨或在后续进阶

    2024年02月10日
    浏览(48)
  • 【架构】领域驱动设计(DDD)的几种典型架构介绍

    我们生活中都听说了DDD,也了解了DDD,那么怎么将一个新项目从头开始按照DDD的过程进行划分与架构设计呢? 各种服务 IAAS:基础设施服务,Infrastructure-as-a-service PAAS:平台服务,Platform-as-a-service SAAS:软件服务,Software-as-a-service 从图中已经可以很容易看出架构的演进过程,

    2024年02月11日
    浏览(37)
  • 万字长文助你上手软件领域驱动设计 DDD

    最近看了一本书《解构-领域驱动设计》,书中提出了领域驱动设计统一过程(DDDRUP),它指明了实践 DDD 的具体步骤,并很好地串联了各种概念、模式和思想。因此,我对书本内容做了梳理、简化,融入自己的理解,并结合之前阅读的书籍以及实践经验,最终形成这篇文章。

    2024年02月08日
    浏览(45)
  • 软件架构演进过程与微服务设计中的领域驱动设计(DDD)

    软件架构的演进是一个不断改进和解决问题的过程。从传统架构到面向服务架构(SOA),再到微服务架构,每个阶段都带来了新的技术和解决方案。而在微服务架构中,领域驱动设计(DDD)起着至关重要的作用,它能够提高系统的可扩展性、可维护性和可理解性。本文将介绍软件架

    2024年02月16日
    浏览(44)
  • 快速理解DDD领域驱动设计架构思想-基础篇 | 京东物流技术团队

    本文与大家一起学习并介绍领域驱动设计(Domain Drive Design) 简称DDD,以及为什么我们需要领域驱动设计,它有哪些优缺点,尽量用一些通俗易懂文字来描述讲解领域驱动设计,本篇并不会从深层大论述讲解落地实现,这些大家可以在了解入门后再去深层次学习探讨或在后续进阶

    2024年02月09日
    浏览(44)
  • DDD技术方案落地实践

    从接触领域驱动设计的初学阶段,到实现一个旧系统改造到DDD模型,再到按DDD规范落地的3个的项目。对于领域驱动模型设计研发,从开始的各种疑惑到吸收各种先进的理念,目前在技术实施这一块已经基本比较成熟。在既往经验中总结了一些在开发中遇到的技术问题和解决方

    2024年02月05日
    浏览(49)
  • 【实践篇】手把手教你落地DDD

    常见的DDD实现架构有很多种,如经典四层架构、六边形(适配器端口)架构、整洁架构(Clean Architecture)、CQRS架构等。架构无优劣高下之分,只要熟练掌握就都是合适的架构。本文不会逐个去讲解这些架构,感兴趣的读者可以自行去了解。 本文将带领大家从日常的三层架构

    2024年02月06日
    浏览(80)
  • DDD领域驱动

    我们经常讲技术为业务服务,架构设计需要对业务充分理解,在面向复杂的业务场景时,会面临诸多问题: 复杂系统设计 :业务系统多、业务类型多、业务相互耦合,有没有合适的方法来指导模块的边界开发? 多团队协同 :业务系统边界划分不清,系统间依赖复杂,往往一

    2024年02月09日
    浏览(36)
  • DDD[领域驱动模型]

    这是一种思想,不是一个工具。更多内容前往 IT-BLOG Eric Evans 于 2004 年提出的一种软件设计方法和理论。在应用架构的设计中, 领域驱动设计 DDD 占据着非常重要的位置,可以说 DDD 是应用架构设计的核心。 DDD 是一套综合软件系统分析和设计的面向对象建模方法。 过去 系统

    2024年02月04日
    浏览(40)
  • DDD进阶_领域事件是什么?如何开展领域事件驱动开发工作?

    DDD从入门到精通,系列文章传送地址,请点击本链接。   目录 一、什么是领域事件 二、如何识别领域事件 三、领域事件的数据一致性 四、领域事件分类 1、微服务内的领域事件 2、微服务之间的领域事件 五、领域事件案例 六、领域事件总体架构图 1. 事件构建和发布 2、事

    2024年02月15日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包