全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-07-08_想给面试官吹DDD吗?项目像搭积木一样运行:领域驱动设计(DDD)让复杂业务变简单

您的位置:首页 >> 新闻 >> 行业资讯

想给面试官吹DDD吗?项目像搭积木一样运行:领域驱动设计(DDD)让复杂业务变简单 本文主要是带你进入 DDD 架构的世界,先了解 DDD 架构的基本概念,结合例子生动体会,文章最后也展示了整个 DDD 结构的项目结构示意 「架构设计的意义更多的就是在解决系统的反复的维护和迭代时,如何降低成本,这也是架构分层的意义所在」 传统的 MVC 架构在前期开发的时候,就是很省事且刚开始的代码逻辑也比较清晰,但是随着业务的发展 MVC 架构是不可避免的会发生腐化现象 那 DDD 是什么呢? DDD 就是"Domain-driven design (DDD) is a major software design approach. " ,所以「DDD 是一种软件设计方法」。也就是说 DDD 是指导我们做软件工程设计的一种手段,它提供了用切割工程模型的各类技巧,如;领域、界限上下文、实体、值对象、聚合、工厂、仓储等。通过 DDD 的指导思想,我们可以在前期投入更多的时间,更加合理的规划出可持续迭代的工程设计 「充血模型(Rich Domain Model)」指将对象的「属性信息」与「行为逻辑」聚合到一个类中,一个充血模型的对象要具有属性(数据),方法(业务逻辑,业务行为)。与之对应的叫贫血模型,在贫血模型中只有数据属性,没有业务逻辑,业务逻辑全放在 MVC 架构的 Service 层中了。举个例子就是下面的内容: publicclassOrder{ privateLong id; privateLong userId; privateBigDecimal amount; // 没有任何行为,所有逻辑都在OrderService } publicclassOrder{ privateLong id; privateLong userId; privateBigDecimal amount; // 领域行为 publicvoidpay(Payment payment){ // 支付逻辑 } publicvoidcancel(){ // 取消逻辑 } } 「领域模型(Domain Model)」是对业务领域核心概念的建模,是承载业务规则和业务逻辑的核心对象。领域模型不是单纯的数据结构,而是要把行为(业务方法)和数据(属性)结合起来,所以领域模型的目标是反映现实世界的业务场景和规则。每一个领域服务都是有界限的 在 DDD 架构中,推荐的领域模型实现方式就是充血模型。换句话说,领域模型的实现应当是充血的。也就是说,领域模型对象要有自己的业务逻辑和行为,而不是只做“数据搬运工”。 「领域模型 = 充血模型 + 聚合、实体、值对象、工厂等构件」,领域模型是 DDD 的核心,“充血”只是领域模型的实现原则 「实体(Entity)」是指在业务中具有唯一标识的对象,实体的主要特征就是持续的身份(即使属性发现变化,但是它的身份不变,它一直是那个实体)。例如 User,Order 都属于实体,订单可以被修改(比如改收货地址),但它的 OrderId 永远不会变 publicclassUser{ privateLong id; // 唯一标识 privateString name; privateint // 行为方法 } 「值对象(Value Object)」值对象没有唯一标识,它是通过属性值来判断相等性的。值对象关注属性而非像实体一样关注身份,属性是可替换的,不可变的!例如地址 Address,只要内容完全一样的地址就是同一个值对象,没有单独的身份 // 不需要 id,创建后不能更改 publicclassAddress{ privatefinalString province;// final修饰 privatefinalString city;// final修饰 privatefinalString street;// final修饰 // 构造后不可修改 publicAddress(String province, String city, String street){ this.province = province; this.city = city; this.street = street; } // 没有 set 方法 } 为什么说属性是可替换的,不可变的? 先回顾实体,实体他是有自己的身份的,实体关心的"是谁",对于实体来说"身份证号码是唯一的",哪怕属性变了,只要 ID 一样就是同一个人!再来看值对象,值对象没有身份,关心的是"是什么",对于值对象来说"内容完全一样才算一样",比如地址中的城市街道要完全一样才算一样! 来我们就能理解"可替换,不可变"的由来了,只要值是一样的,就可以相互替换。比如你的快递地址,如果内容一样,就能换成任何一个相同内容的 Address 对象,业务完全没影响。实体就不同了,实体不能随意更换,且实体的身份也是不一样的。通常值对象都会设计为不可变(immutable),因为不可变不会让对象中途被修改,从而导致系统混乱,这样一来值对象一旦创建,只能整体替换,不能部分更改 「聚合(Aggregate)」首先先说明:「聚合 = 聚合根 + 其他实体/值对象」,然后我们进一步看这个等式,先了解聚合根 聚合就是将紧密相关的实体和值对象组织在一起,形成一个有边界的业务模块。在聚合中有一个聚合根(Aggregate Root),只有通过聚合根才能访问或修改聚合内容的其他对象 用例子来解释就是:Order 类中,有 orderId(唯一标识),OrderItem(订单项),Address(地址)是其内部成员。那么 Order 就是聚合根,对于 OrderItem 的操作必须通过 Order 这个聚合根进行操作,不可以直接操作 OrderItem publicclassOrder{ // 聚合根 privateLong orderId;// 只有聚合根拥有唯一标识 privateListOrderItem items; // 子实体 privateAddress address; // 值对象 // 聚合内部行为 publicvoidaddItem(OrderItem item){ ... } publicvoidchangeAddress(Address address){ ... } } 为什么聚合根需要一个唯一标识?从上面不难看出,聚合根是聚合的代表以及入口,外部的直接只关心聚合根的 id,其他成员对象即使有自己的 id 也不能作为聚合整体的唯一标识。只要是需要引用或者操作这个聚合,就只能通过聚合根的唯一标识 id 来完成 外部系统、仓库、数据库、消息传递等,只需记住 orderId,就能找到整个 Order 聚合及其内部所有内容 聚合这样做的好处就是,聚合根是唯一的操作入口,保证数据不被乱改。并且事务只需要覆盖一个聚合,性能也高,易于维护,并且聚合之间可以通过领域事件解耦协作 「仓储(Repository)」仓储是领域模型和持久化技术之间的桥梁,是“面向领域”的数据访问对象。首先明白仓储的本质就是面向领域的接口,是"用业务的视角"去查找和保存领域对象,不直接关系底层数据库/缓存的实现细节。 换句话说仓储让我们在领域中只关心业务的实现,然后通过暴露的接口去查询和保存领域对象。领域只写接口,基础设施拼命适配就好了 举例子就是假设我们有一个 OrderRepository 仓储接口 查询订单时可以先查缓存(Redis),没命中再查数据库(MySQL);保存订单时要先写数据库,再写缓存;订单归档后可能写入文件存储或上传到远程归档服务在领域层中,仓储接口如下,领域服务只关心“查找、保存订单”,而不管底层是 MySQL、Redis,还是接口 publicinterfaceOrderRepository{ OrderfindById(Long id);// 根据id查Order实体对象 voidsave(Order order);// 保存Order实体对象 voiddelete(Long id);// 根据id删除实体对象 } 在基础设施层实现具体的细节,不是在领域中实现 publicclassOrderRepositoryImplimplementsOrderRepository{ privatefinalRedisTemplate redisTemplate; privatefinalOrderDao orderDao; // MyBatis privatefinalFileStorageService fileStorageService; privatefinalArchiveRemoteService archiveRemoteService; publicOrderRepositoryImpl(RedisTemplate redisTemplate, OrderDao orderDao, FileStorageService fileStorageService, ArchiveRemoteService archiveRemoteService){ this.redisTemplate = redisTemplate; this.orderDao = orderDao; this.fileStorageService = fileStorageService; this.archiveRemoteService = archiveRemoteService; } @Override publicOrderfindById(Long id){ // 1. 先查缓存 Order order = redisTemplate.get("order:"+ id); if(order !=null) { returnorder; } // 2. 缓存没有,查数据库 order = orderDao.selectById(id); if(order !=null) { // 3. 放入缓存 redisTemplate.set("order:"+ id, order,1, TimeUnit.HOURS); } returnorder; } @Override publicvoidsave(Order order){ // 1. 先写数据库 orderDao.save(order); // 2. 同步缓存 redisTemplate.set("order:"+ order.getId(), order,1, TimeUnit.HOURS); } @Override publicvoiddelete(Long id){ // 1. 删除数据库 orderDao.delete(id); // 2. 删除缓存 redisTemplate.delete("order:"+ id); } // 新增:订单归档,归档到文件+远程服务 publicvoidarchive(Order order){ // 1. 写入本地归档文件 fileStorageService.save(order); // 2. 上传归档数据到远程归档中心 archiveRemoteService.upload(order); } } 「适配器(Adapter)」适配器是外部系统的桥梁,适配器也是像领域暴露接口,而具体的实现是基础设施层实现的。所有和“外部世界”打交道的场景,都可以做成“领域接口 + 适配器实现”模式。举个例子就是领域层需要"给手机号发验证码",不用关心怎么发,找什么渠道发。只需要定义接口 // 包名:domain/service/SmsSender.java publicinterfaceSmsSender{ voidsendCode(String mobile, String code); } 而在基础设施层中实现接口,完成具体的细节 // 包名:infrastructure/adapter/AliyunSmsSender.java publicclassAliyunSmsSenderimplementsSmsSender{ privatefinalAliyunSmsClient aliyunSmsClient; publicAliyunSmsSender(AliyunSmsClient aliyunSmsClient){ this.aliyunSmsClient = aliyunSmsClient; } @Override publicvoidsendCode(String mobile, String code){ // 封装第三方API细节 aliyunSmsClient.send(mobile, code); } } 如果以后想换腾讯云,也只需加一个实现,但是领域层是无感知的,也就是说业务代码不用改动,只需要切换注入的适配器就可以了 publicclassTencentSmsSenderimplementsSmsSender{ // ...同理 } 再举个例子 Repository 和 Adapter 的关系就像是: 仓储就像你的“业务对象仓库”,专门管你的货物(领域对象)怎么存怎么取适配器就像“对外通讯员”,帮你和快递公司、银行、外部政府系统打交道,把你业务上的指令转成各种“外部系统”能懂的操作「领域编排(Application Layer)」领域编排就好比组装领域模块的胶水,就是把多个“界限上下文”或“领域服务”串联起来,完成一个跨领域的完整业务流程。而这个编排的工作是由应用层(Application Layer)完成的,应用层就像是搭积木一样,将每个领域模块编排组装,实现高阶的复杂业务。当然能搭积木的前提就是因为领域模块是解耦、独立的,所以编排层可以根据不同业务需求,灵活“组装”各种领域服务,实现高复用和灵活扩展 举个例子就是现在有一个业务场景:“用户开户成功,自动赠送优惠券并发送短信通知”,现在划分了三个领域服务:用户域(User)负责开户,营销域(Marketing)负责发券,通知域(Notify)负责发短信 那么在应用层(Application Layer)中就有如下代码,它不需要关心具体怎么开户,怎么发卷,怎么发短信,只需要关心流程怎么串起来 publicclassOpenAccountOrchestrationService{ privatefinalUserService userService; privatefinalMarketingService marketingService; privatefinalNotifyService notifyService; publicvoidopenAccountAndGiftCoupon(String userId){ userService.openAccount(userId); marketingService.giftCoupon(userId); notifyService.sendAccountOpenMessage(userId); } } 「触发器层(Trigger Layer)」触发器就好比 MVC 中的 Controller 层,但是 DDD 架构中触发方式不止是 HTTP 接口,还有 MQ,定时任务,WebSocket 等等。触发器层和 MVC 的 Controller 层实现的目标都是一致的,即与业务解耦,只负责接受外部输入和触发业务的信号,而不写任何业务逻辑。所以 Trigger 层和 MVC 中的 Controller 层都是很干净,简洁的 @RestController @RequestMapping("/user") publicclassUserController{ privatefinalUserOrchestrationService userOrchestrationService; publicUserController(UserOrchestrationService userOrchestrationService){ this.userOrchestrationService = userOrchestrationService; } @PostMapping("/register") publicResultVOregister(@RequestBody RegisterRequest req){ // 参数校验、权限校验(可用注解/AOP处理) userOrchestrationService.registerAndGiftCoupon(req.getMobile()); returnResultVO.success(); } } @Component publicclassUserRegisterListener{ privatefinalUserOrchestrationService userOrchestrationService; @KafkaListener(topics ="user.register") publicvoidonUserRegister(String message){ // 解析消息内容 String mobile = parseMobile(message); userOrchestrationService.registerAndGiftCoupon(mobile); } } @Component publicclassCouponExpireScheduler{ privatefinalCouponOrchestrationService couponOrchestrationService; @Scheduled(cron ="0 0 2 * * ?")// 每天凌晨2点 publicvoidexpireCoupons(){ couponOrchestrationService.expireOverdueCoupons(); } } 讲到这里,就不得不提一下领域编排存在的必要性了。如果项目很小,我们允许在触发器层直接调用领域服务,减少层级。如果业务复杂、多领域协作、或未来业务流程可能多变,就比较适合添加领域编排了 「例子」最后以“用户开户并自动赠送优惠券”的业务为例,帮助更好的理解 DDD 项目结构 src/ ├── domain/ # 领域层(核心业务对象和规则) │ ├── user/ │ │ ├── User.java # 实体/聚合 │ │ ├── UserRepository.java # 仓储接口 │ │ ├── UserService.java # 领域服务 │ ├── coupon/ │ │ ├── Coupon.java │ │ ├── CouponRepository.java │ │ ├── CouponService.java │ ├── notify/ │ │ ├── SmsSender.java # 适配器接口 │ │ ├── NotifyService.java │ ├── application/ # 应用/编排层(领域编排/业务流程) │ ├── OpenAccountOrchestrator.java # 开户编排(聚合各领域服务) │ ├── CouponExpireOrchestrator.java # 优惠券到期编排 │ ├── trigger/ # 触发器层(接入点/外部入口) │ ├── OpenAccountController.java # HTTP 接口 │ ├── OpenAccountListener.java # MQ监听(如开户成功消息触发) │ ├── CouponExpireScheduler.java # 定时任务 │ ├── infrastructure/ # 基础设施层(技术实现细节) │ ├── repository/ │ │ ├── UserRepositoryImpl.java # 仓储实现(MyBatis/JPA等) │ │ ├── CouponRepositoryImpl.java │ ├── adapter/ │ │ ├── AliyunSmsSender.java # 适配器实现(接阿里云短信) │ │ ├── RedisCacheAdapter.java # 适配缓存 │ ├── config/ │ │ ├── DataSourceConfig.java # 数据库/Redis/MQ等配置 │ ├── common/ # 通用工具、基础VO、异常定义等 │ ├── ResultVO.java │ ├── BizException.java │ └── MainApplication.java # 程序入口 AI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding 点击"阅读原文"了解详情~ 阅读原文

上一篇:2023-12-19_万人试用AI新应用:真人视频转动漫、像素风,从未如此丝滑 下一篇:2025-06-12_AI都开始“教课”了?秘塔这波操作真的有点狠!

TAG标签:

14
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价