【基于电商履约场景的 DDD 实战】基于 Cola 实现电商履约架构设计(完结)

news2024/11/15 17:15:29

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

基于电商履约场景的 DDD 实战

在这里插入图片描述

基于 Cola 实现电商履约架构设计

我们在实际进行架构设计的时候,不用每一个方法都严格按照 Cola 或者 DDD 去设计,可以根据自己的需求以及习惯,做出一些合理的变动

这里将履约这个模块分为 6 个部分(后两个模块不重要,主要是前 4 个模块要清楚每一个模块的作用):

在这里插入图片描述

  • api:与外界负责交互的模块
  • application:原来的 app 层,负责全局服务的处理
  • domain:领域层
  • infrastructure:基础设施层
  • rpc:与其他模块进行 rpc 通信的接口
  • start:启动 SpringBoot 项目的模块

订单履约流程

订单履约的流程,当订单支付成功之后,订单上下文会发送一个【订单支付成功】的事件,在履约上下文中,就可以监听【订单支付成功】事件,之后开始履约的流程

  • 首先会进入到 api 层

监听器放在 api 层,与外界进行交互,监听器收到事件之后,将外部的事件转为 Command,再调用 application 层提供的【应用服务】来进行处理

api -- 
@Component
@Slf4j
public class OrderPayedEventListener implements MessageListenerConcurrently {
    @Autowired
    private FulfillApplicationService fulfillApplicationService;

    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
        try {
            for (MessageExt messageExt : list) {
                String message = new String(messageExt.getBody());
                log.info("OrderPayedEventListener message:{}", message);

                // 1、将message转化为OrderPayedEvent领域事件
                OrderPayedEvent orderPayedEvent = JSONObject.parseObject(message, OrderPayedEvent.class);
                // 2、把领域事件,转换为一个command
                OrderFulfillCommand orderFulfillCommand = buildCommand(orderPayedEvent);
                // 3、交给app层的应用服务逻辑,去推动命令的流程编排和执行
                fulfillApplicationService.executeOrderFulfill(orderFulfillCommand);
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        } catch (Exception e) {
            log.error("consumer error", e);
            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
        }
    }
}
  • application 层

在 application 层,就通过 Executor 执行履约的操作

@Component
public class FulfillApplicationService {

    @Autowired
    private OrderFulfillCommandExecutor orderFulfillCommandExecutor;
   
    public void executeOrderFulfill(OrderFulfillCommand orderFulfillCommand) {
        // 通过 Executor 来进行履约的处理
        orderFulfillCommandExecutor.execute(orderFulfillCommand);
    }
}

这里我们将 Executor 的具体实现也放在 application 层中去,在 Executor 中实现具体的履约流程:

  • 首先是 保存订单 ,这里先将 Command 给转成履约单 FulfillOrder,再将履约单进行保存操作,并且插入日志

    这里保存履约单的操作写到了 Gateway 中去,因为 Gateway 就是与下层 Infrastructure 层交互的

    保存订单之后通过 【fulfillOrderLogDomainService】 来插入操作日志,这里将日志的插入操作写入到了 【fulfillOrderLogDomainService】 (即履约订单日志领域服务)中去,在这个 【DomainService】 中再调用 Gateway 去和 Infrastructure 进行交互,因为这里在插入日志信息的时候,还有一些额外的信息需要设置一下,所以在 DomainService 层进行了设置,之后再通过 Gateway 进行保存,可以保证 Gateway 层的职责比较单一(仅仅和 Infrastructure 交互或者其他模块交互)

  • 其次是 风控拦截 ,风控拦截已经是其他模块的功能了,毫无疑问使用 Gateway 来和其他上下文进行通信,这里在 Gateway 中就可以直接和风控模块暴露的 rpc 接口进行通信了(使用 Gateway 层解耦)

    如果该订单被风控拦截了,使用【domainEventGateway】来发布被拦截的领域事件

  • 接下来是 预分仓 ,由于预分仓这里,我们需要一些距离计算的方法来选择合适的仓库,而 Gateway 的目的是和其他模块进行交互,功能比较单一,因此预分仓这里我们再去创建一个【warehouseDomainService】即仓库的领域服务,在仓库的领域服务中再通过 Gateway 和仓库交互来获取所有仓库并且进行仓库的选择

    预分仓之后通过 【fulfillOrderLogDomainService】 来插入操作日志

  • 下来是 分物流 ,这里和预分仓是类似的,分物流中的操作也比较复杂,所以不能直接放到 Gateway 中去,要先创建一个【logisticsDomainService】物流的领域服务,在物流的领域服务中去执行分物流的一系列操作

    分物流之后通过 【fulfillOrderLogDomainService】 来插入操作日志

  • 最后是 下库房 ,先通过 Gateway 来和库房上下文进行交互,调用库房上下文暴露的接口来保存履约单

    下库房之后通过 【fulfillOrderLogDomainService】 来插入操作日志

@Component
@Slf4j
public class OrderFulfillCommandExecutor {
	// ...

    // 专门负责订单履约流程的编排,把这个流程按照战术建模的设计,完成落地开发
    @Transactional(rollbackFor = Exception.class)
    public void execute(OrderFulfillCommand orderFulfillCommand) {
        // 第一步,保存订单,需要去使用履约订单仓储/gateway来进行保存
        FulfillOrder fulfillOrder = fulfillOrderFactory.createFulfillOrder(orderFulfillCommand);
        log.info("创建fulfillOrder实体:{}", JSONObject.toJSONString(fulfillOrder));
        fulfillOrderGateway.save(fulfillOrder);
        fulfillOrderLogDomainService.saveOrderCreatedLog(fulfillOrder);


        // 第二步,风控拦截
         Boolean interceptResult = riskControlApiGateway.riskControlIntercept(fulfillOrder);
        log.info("风控拦截:{}", JSONObject.toJSONString(interceptResult));
        if (!interceptResult) {
            fulfillOrder.riskReject();
            // 如果被风控拦截了,此时就需要发布订单被拦截的领域事件,通知人工审核
            domainEventGateway.publishOrderInterceptedEvent(new OrderInterceptedEvent(fulfillOrder.getFulfillId().getFulfillId()));
            return;
        }else {
            fulfillOrder.riskPass();
        }

        // 第三步,预分仓
        Warehouse warehouse = warehouseDomainService.preAllocateWarehouse(fulfillOrder);
        log.info("预分仓:{}", JSONObject.toJSONString(fulfillOrder.getFulfillOrderWarehouse().getWarehouseId()));
        fulfillOrderLogDomainService.saveOrderWarehousedLog(fulfillOrder);

        // 第四步,分物流
        logisticsDomainService.allocateLogistics(fulfillOrder, warehouse);
        fulfillOrderLogDomainService.saveOrderLogisticsLog(fulfillOrder);
        log.info("分物流:{}",fulfillOrder.getLogisticsOrder().getLogisticsId());

        // 第五步,下发库房
        warehouseApiGateway.sendFulfillOrder(fulfillOrder, warehouse);
        fulfillOrderLogDomainService.saveOrderInStoredLog(fulfillOrder);
        log.info("下发库房");
    }
}

上边的履约流程总共有 5 个步骤,这里只挑选两个具有代表性(Gateway 分别和 Infrastructure、其他模块交互的两种情况)的步骤说一下:

这里先说一下第一步:保存订单,在保存订单的时候,需要两步:

1、通过 【fulfillOrderGateway】 与 Infrastructure 交互,进行订单的保存

2、通过 【fulfillOrderLogDomainService】 进行日志的插入操作

这里在 Gateway 中直接调用了 Infrastructure 层的 DAO 进行数据库操作,整个流程就结束了

@Component
public class FulfillOrderGatewayImpl implements FulfillOrderGateway {
	@Override
    public void save(FulfillOrder fulfillOrder) {
        FulfillOrderDO fulfillOrderDO = fulfillOrderDOConverter.convert(fulfillOrder);
        List<FulfillOrderItemDO> fulfillOrderItemDOs = mapStructSupport.convertToFulfillOrderItemDOs(fulfillOrder.getFulfillOrderItems());

        fulfillOrderDAO.save(fulfillOrderDO);
        fulfillOrderItemDAO.saveBatch(fulfillOrderItemDOs);
    }
}

接下来再看一下第二步:风控拦截,需要 2 步:

1、通过 【riskControlApiGateway】与风控模块进行交互,判断当前订单是否要被拦截

2、如果被拦截了,需要通过【domainEventGateway】发布【订单被拦截的事件】

这里在 Gateway 中直接就引入了风控模块的 API,通过 RPC 直接调用

@Component
public class RiskControlApiGatewayImpl implements RiskControlApiGateway {
    @DubboReference(version = "1.0.0", retries = 0)
    private RiskApi riskApi;
	@Override
    public Boolean riskControlIntercept(FulfillOrder fulfillOrder) {
        // 通过 Converter 将 fulfillOrder 转为发往风控拦截的请求 FulfillOrderRiskRequest
        FulfillOrderRiskRequest fulfillOrderRiskRequest = fulfillOrderRiskRequestConverter.convert(fulfillOrder);
        // 调用风控拦截的 API 判断是否拦截
        FulfillOrderRiskDTO fulfillOrderRiskDTO = riskApi.fulfillOrderRiskControlIntercept(fulfillOrderRiskRequest);
        if(fulfillOrderRiskDTO == null) {
            log.warn("风控调用失败");
            return false;
        }
        if(!fulfillOrderRiskDTO.getRiskResult()) {
            log.warn("风控检查拒绝通过");
            return false;
        }
        return true;
    }

}

上边代码比较多,可能看着不太清晰,这里我画张图:

在这里插入图片描述

这里还涉及一些实体对象的转换,监听器收到事件的时候,拿到的是消息,先将消息转为【OrderFulfillCommand】之后,再传入 Application 层,再将 【Command】转为【FulfillOrder】之后,传入 Domain 层,再转为【FulfillOrderDO】传入 Infrastructure 层

【OrderFulfillCommand】-> 【FulfillOrder】 使用 FulfillOrderFactory 来进行转换了,通过这个 Factory 创建一个履约单对象

【FulfillOrder】 ->【FulfillOrderDO】 使用 Converter 来进行转换,Converter 都放在了 Infrastructure 层

至此,订单履约流程的架构设计就完成了

那么可以发现,整个流程,从上到下,api -> application -> domain -> infrastructure,每个层的职责都是很清晰的,但是也正是因为职责划分的很清晰,从而导致代码在写起来的时候,需要比 MVC 架构多写很多的内容

因此对于中小型项目来说,盲目引入 DDD 只会导致项目的工作量剧增,DDD 的目的就是前期通过良好的分层、建模,后期可以降低维护成本

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1420690.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

‘sdkmanager‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

错误信息 sdkmanager 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 这个错误提示是在尝试运行 sdkmanager 命令时出现的。sdkmanager 是 Android SDK 的一部分&#xff0c;用于管理 Android SDK 的版本和组件。 [!] Android toolchain - develop for An…

计算机网络-物理层设备(中继器 集线器)

文章目录 中继器中继器的功能再生数字信号和再生模拟信号同一个协议 集线器&#xff08;多口中继器&#xff09;不具备定向传输的原因集线器是共享式设备的原因集线器的所有接口都处于同一个碰撞域&#xff08;冲突域&#xff09;内的原因 小结 中继器 中继器的功能 中继器的…

一些著名的软件都用什么语言编写?

1、操作系统 Microsoft Windows &#xff1a;汇编 -> C -> C 备注&#xff1a;曾经在智能手机的操作系统&#xff08;Windows Mobile&#xff09;考虑掺点C#写的程序&#xff0c;比如软键盘&#xff0c;结果因为写出来的程序太慢&#xff0c;实在无法和别的模块合并&…

在centos 7 中 安装 配置 并 远程连接 MySQL5.7

目录 安装MySQL 1.卸载CentOS7系统自带的mariadb 2.安装依赖库 3.上传MySQL并解压 4.安装MySQL 配置MySQL 1.修改登录密码 2.修改字符集 3.配置远程连接 前言&#xff1a; 安装MySQL版本&#xff1a;mysql-5.7.30-1.el7.x86_64.rpm-bundle 文件需求后台私信 以下7条为…

LabVIEW直流电机转速检测与控制

研究了使用LabVIEW软件和ELVIS实验平台来检测和控制直流电机的转速。通过集成光电传感器和霍尔传感器&#xff0c;实现了对电机转速的精确测量和调节。 系统组成&#xff1a;系统由NI ELVIS实验平台、光电传感器、霍尔传感器和直流电机组成。通过这些硬件元件&#xff0c;系统…

谷歌把GenAI装进Chrome

谷歌不甘示弱&#xff0c;在其Chrome浏览器中引入了新的实验性生成式AI功能&#xff0c;以简化并为用户提供更高效的浏览体验。 值得注意的是&#xff0c;微软去年推出了Edge浏览器和人工智能驱动的必应搜索引擎的集成。随着Chrome M121版本的发布&#xff0c;谷歌打算利用最新…

代理IP在游戏中的作用有哪些?

游戏代理IP的作用是什么&#xff1f;IP代理软件相当于连接客户端和虚拟服务器的软件“中转站”&#xff0c;在我们向远程服务器提出需求后&#xff0c;代理服务器首先获得用户的请求&#xff0c;然后将服务请求转移到远程服务器&#xff0c;然后将远程服务器反馈的结果转移到客…

C++11—— lambda表达式与包装器

C11—— lambda表达式与包装器 文章目录 C11—— lambda表达式与包装器一、 lambda表达式lambda表达式产生的意义lambda表达式语法函数对象与lambda表达式 二、 包装器functionfunction产生的意义function的用法function使用的例子 bind调整参数顺序固定绑定参数 一、 lambda表…

「仅需三次鼠标,即可开服」幻兽帕鲁全自动部署教程

在帕鲁的世界&#xff0c;你可以选择与神奇的生物「帕鲁」一同享受悠闲的生活&#xff0c;也可以投身于与偷猎者进行生死搏斗的冒险。帕鲁可以进行战斗、繁殖、协助你做农活&#xff0c;也可以为你在工厂工作。你也可以将它们进行售卖&#xff0c;或肢解后食用。 本文将为您提…

【芯片设计- RTL 数字逻辑设计入门 番外篇 6.1 -- 术语 Wafer 与 Tile 与 cell 关系介绍】

请阅读【嵌入式开发学习必备专栏 】 文章目录 SoC Tile 与 Cell 与 WaferWaferTileCellTile与Cell的关系示例SoC Tile 与 Cell 与 Wafer 在SoC(System on Chip,系统级芯片)设计中,Wafer, Tile和Cell是常用的术语,它们在不同的设计层次上描述了芯片的组成部分。

图书管理系统(ArrayList和LinkedList)--versions3.0

目录 一、项目要求&#xff1a; 二、项目环境 三、项目使用的知识点 四、项目代码 五、项目运行结果 六、项目难点分析 图书管理系统--versions1.0&#xff1a; 图书管理系统--versions1.0-CSDN博客文章浏览阅读981次&#xff0c;点赞29次&#xff0c;收藏17次。本文使用…

小红的回文串构造

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例1&#xff1a; 输入 abba 输出 baab 样例2&#xff1a; 输入 aba 输出 -1 思路&#xff1a; 由题意&#xff0c;题目保证给出的字符串是回文串的&#xff0c;所以我们只需要获取两个不同的…

Android App开发基础(2)—— App的工程结构

本专栏文章 上一篇 Android开发修炼之路——&#xff08;一&#xff09;Android App开发基础-1 2 App的工程结构 本节介绍App工程的基本结构及其常用配置&#xff0c;首先描述项目和模块的区别&#xff0c;以及工程内部各目录与配置文件的用途说明&#xff1b;其次阐述两种级别…

状态码400以及状态码415

首先检查前端传递的参数是放在header里边还是放在body里边。 此图前端传参post请求&#xff0c;定义为’Content-Type’&#xff1a;‘application/x-www-form-urlencoded’ 此刻他的参数在FormData中。看下图 后端接参数应为&#xff08;此刻参数前边什么都不加默认为requestP…

仅4步,彻底玩转如何在Word中插入页脚数字

Word中首页无需添加数字&#xff0c;从第二页开始添加数字。 Step 1&#xff1a;如下图&#xff0c;将内容设定为如下&#xff0c;“首页不同”一定要选中&#xff1b; Step 2&#xff1a;选择工具栏中的“页码”&#xff1b; Step 3&#xff1a;接下来选择“页码”下的“设置…

防火墙知识普及详解,使用TOR Router把TOR作为默认网关,增加隐私/匿名性

防火墙知识普及详解,使用TOR Router把TOR作为默认网关,增加隐私/匿名性。 #################### 免责声明:工具本身并无好坏,希望大家以遵守《网络安全法》相关法律为前提来使用该工具,支持研究学习,切勿用于非法犯罪活动,对于恶意使用该工具造成的损失,和本人及开发者…

2024 年, Web 前端开发趋势

希腊哲学家赫拉克利特认为&#xff0c;变化是生命中唯一不变的东西。这句话适用于我们的个人生活、行业和职业领域。 尤其是前端开发领域&#xff0c;新技术、开发趋势、库和框架不断涌现&#xff0c;变化并不陌生。最近发生的一些事件正在改变开发人员构建网站和 Web 应用的方…

数据分析-28-小红书消费情况分析(包含代码和数据)

文章目录 0. 代码数据下载1. 项目介绍2. 数据说明3. 分析目的1. 提出问题&#xff1a;哪些消费群体更庞大&#xff0c;平均购买力更强&#xff1f;已知自变量能否准确预测用户购买金额&#xff1f;a. 数据导入b. 去除重复数值和缺失数值c. 简化部分columns的命名&#xff0c; 方…

15EG使用vivado2021.1实现LWIP的网络传输

创建工程模板在hello_world中已经介绍过了&#xff0c;这里直接从配置完zynq ip核开始&#xff0c;由于使用vivado的版本不同&#xff0c;配置ZYNQ时需要用到的tcl文件我会放在工程文件夹下的file文件夹中 配置好IP核后&#xff0c;右键设计模块&#xff0c;点击Generate Outpu…

实际项目中的SpringAOP实现日志打印

目录 一、AOP实现日志 1.1 需求分析&#xff1a; 1.2 定义切面类和切点&#xff1a; 扩展&#xff1a;finally中的代码块一定会执行吗&#xff1f; 扩展 总结 1.3 定义环绕通知 1.4 handleBefore 的具体实现 1.4.1 获取url 1.4.2 获取接口描述信息 1.4.3 后续获取 1.5…